();
282 | parseVertices(str, vertices);
283 | return new S2Polyline(vertices);
284 | }
285 | }
286 | }
--------------------------------------------------------------------------------
/S2Geometry/S2Polyline.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Google.Common.Geometry
9 | {
10 | /**
11 | * An S2Polyline represents a sequence of zero or more vertices connected by
12 | * straight edges (geodesics). Edges of length 0 and 180 degrees are not
13 | * allowed, i.e. adjacent vertices should not be identical or antipodal.
14 | *
15 | * Note: Polylines do not have a Contains(S2Point) method, because
16 | * "containment" is not numerically well-defined except at the polyline
17 | * vertices.
18 | *
19 | */
20 |
21 | public struct S2Polyline : IS2Region, IEquatable
22 | {
23 | private readonly int _numVertices;
24 | private readonly S2Point[] _vertices;
25 |
26 | /**
27 | * Create a polyline that connects the given vertices. Empty polylines are
28 | * allowed. Adjacent vertices should not be identical or antipodal. All
29 | * vertices should be unit length.
30 | */
31 |
32 | public S2Polyline(IEnumerable vertices)
33 | {
34 | // assert isValid(vertices);
35 | _vertices = vertices.ToArray();
36 | _numVertices = _vertices.Length;
37 | }
38 |
39 | /**
40 | * Copy constructor.
41 | *
42 | * TODO(dbeaumont): Now that S2Polyline is immutable, remove this.
43 | */
44 |
45 | public S2Polyline(S2Polyline src)
46 | {
47 | _numVertices = src.NumVertices;
48 | _vertices = (S2Point[])src._vertices.Clone();
49 | }
50 |
51 | public int NumVertices
52 | {
53 | get { return _numVertices; }
54 | }
55 |
56 | public S1Angle ArcLengthAngle
57 | {
58 | get
59 | {
60 | double lengthSum = 0;
61 | for (var i = 1; i < NumVertices; ++i)
62 | {
63 | lengthSum += Vertex(i - 1).Angle(Vertex(i));
64 | }
65 | return S1Angle.FromRadians(lengthSum);
66 | }
67 | }
68 |
69 | public bool Equals(S2Polyline other)
70 | {
71 | if (_numVertices != other._numVertices)
72 | {
73 | return false;
74 | }
75 |
76 | for (var i = 0; i < _vertices.Length; i++)
77 | {
78 | if (!_vertices[i].Equals(other._vertices[i]))
79 | {
80 | return false;
81 | }
82 | }
83 | return true;
84 | }
85 |
86 | public S2Cap CapBound
87 | {
88 | get { return RectBound.CapBound; }
89 | }
90 |
91 |
92 | /** Return a bounding latitude-longitude rectangle. */
93 |
94 | public S2LatLngRect RectBound
95 | {
96 | get
97 | {
98 | var bounder = new RectBounder();
99 | for (var i = 0; i < NumVertices; ++i)
100 | {
101 | bounder.AddPoint(Vertex(i));
102 | }
103 | return bounder.Bound;
104 | }
105 | }
106 |
107 | /**
108 | * If this method returns true, the region completely contains the given cell.
109 | * Otherwise, either the region does not contain the cell or the containment
110 | * relationship could not be determined.
111 | */
112 |
113 | public bool Contains(S2Cell cell)
114 | {
115 | throw new NotSupportedException(
116 | "'containment' is not numerically well-defined " + "except at the polyline vertices");
117 | }
118 |
119 | /**
120 | * If this method returns false, the region does not intersect the given cell.
121 | * Otherwise, either region intersects the cell, or the intersection
122 | * relationship could not be determined.
123 | */
124 |
125 | public bool MayIntersect(S2Cell cell)
126 | {
127 | if (NumVertices == 0)
128 | {
129 | return false;
130 | }
131 |
132 | // We only need to check whether the cell contains vertex 0 for correctness,
133 | // but these tests are cheap compared to edge crossings so we might as well
134 | // check all the vertices.
135 | for (var i = 0; i < NumVertices; ++i)
136 | {
137 | if (cell.Contains(Vertex(i)))
138 | {
139 | return true;
140 | }
141 | }
142 | var cellVertices = new S2Point[4];
143 | for (var i = 0; i < 4; ++i)
144 | {
145 | cellVertices[i] = cell.GetVertex(i);
146 | }
147 | for (var j = 0; j < 4; ++j)
148 | {
149 | var crosser =
150 | new EdgeCrosser(cellVertices[j], cellVertices[(j + 1) & 3], Vertex(0));
151 | for (var i = 1; i < NumVertices; ++i)
152 | {
153 | if (crosser.RobustCrossing(Vertex(i)) >= 0)
154 | {
155 | // There is a proper crossing, or two vertices were the same.
156 | return true;
157 | }
158 | }
159 | }
160 | return false;
161 | }
162 |
163 | public override bool Equals(object obj)
164 | {
165 | if (ReferenceEquals(null, obj)) return false;
166 | if (obj.GetType() != GetType()) return false;
167 | return Equals((S2Polyline)obj);
168 | }
169 |
170 | public override int GetHashCode()
171 | {
172 | unchecked
173 | {
174 | unchecked
175 | {
176 | var code = (_numVertices*397);
177 | foreach (var v in _vertices)
178 | {
179 | code ^= v.GetHashCode();
180 | }
181 |
182 | return code;
183 | }
184 | }
185 | }
186 |
187 | public static bool operator ==(S2Polyline left, S2Polyline right)
188 | {
189 | return Equals(left, right);
190 | }
191 |
192 | public static bool operator !=(S2Polyline left, S2Polyline right)
193 | {
194 | return !Equals(left, right);
195 | }
196 |
197 | /**
198 | * Return true if the given vertices form a valid polyline.
199 | */
200 |
201 | public bool IsValidPolyline(IReadOnlyList vertices)
202 | {
203 | // All vertices must be unit length.
204 | var n = vertices.Count;
205 | for (var i = 0; i < n; ++i)
206 | {
207 | if (!S2.IsUnitLength(vertices[i]))
208 | {
209 | Debug.WriteLine("Vertex " + i + " is not unit length");
210 | return false;
211 | }
212 | }
213 |
214 | // Adjacent vertices must not be identical or antipodal.
215 | for (var i = 1; i < n; ++i)
216 | {
217 | if (vertices[i - 1].Equals(vertices[i])
218 | || vertices[i - 1].Equals(-vertices[i]))
219 | {
220 | Debug.WriteLine("Vertices " + (i - 1) + " and " + i + " are identical or antipodal");
221 | return false;
222 | }
223 | }
224 |
225 | return true;
226 | }
227 |
228 | public S2Point Vertex(int k)
229 | {
230 | // assert (k >= 0 && k < numVertices);
231 | return _vertices[k];
232 | }
233 |
234 | /**
235 | * Return the angle corresponding to the total arclength of the polyline on a
236 | * unit sphere.
237 | */
238 |
239 | /**
240 | * Return the point whose distance from vertex 0 along the polyline is the
241 | * given fraction of the polyline's total length. Fractions less than zero or
242 | * greater than one are clamped. The return value is unit length. This cost of
243 | * this function is currently linear in the number of vertices.
244 | */
245 |
246 | public S2Point Interpolate(double fraction)
247 | {
248 | // We intentionally let the (fraction >= 1) case fall through, since
249 | // we need to handle it in the loop below in any case because of
250 | // possible roundoff errors.
251 | if (fraction <= 0)
252 | {
253 | return Vertex(0);
254 | }
255 |
256 | double lengthSum = 0;
257 | for (var i = 1; i < NumVertices; ++i)
258 | {
259 | lengthSum += Vertex(i - 1).Angle(Vertex(i));
260 | }
261 | var target = fraction*lengthSum;
262 | for (var i = 1; i < NumVertices; ++i)
263 | {
264 | var length = Vertex(i - 1).Angle(Vertex(i));
265 | if (target < length)
266 | {
267 | // This code interpolates with respect to arc length rather than
268 | // straight-line distance, and produces a unit-length result.
269 | var f = Math.Sin(target)/Math.Sin(length);
270 | return (Vertex(i - 1)*(Math.Cos(target) - f*Math.Cos(length))) + (Vertex(i)*f);
271 | }
272 | target -= length;
273 | }
274 | return Vertex(NumVertices - 1);
275 | }
276 |
277 | // S2Region interface (see {@code S2Region} for details):
278 |
279 | /** Return a bounding spherical cap. */
280 |
281 | /**
282 | * Given a point, returns the index of the start point of the (first) edge on
283 | * the polyline that is closest to the given point. The polyline must have at
284 | * least one vertex. Throws IllegalStateException if this is not the case.
285 | */
286 |
287 | public int GetNearestEdgeIndex(S2Point point)
288 | {
289 | Preconditions.CheckState(NumVertices > 0, "Empty polyline");
290 |
291 | if (NumVertices == 1)
292 | {
293 | // If there is only one vertex, the "edge" is trivial, and it's the only one
294 | return 0;
295 | }
296 |
297 | // Initial value larger than any possible distance on the unit sphere.
298 | var minDistance = S1Angle.FromRadians(10);
299 | var minIndex = -1;
300 |
301 | // Find the line segment in the polyline that is closest to the point given.
302 | for (var i = 0; i < NumVertices - 1; ++i)
303 | {
304 | var distanceToSegment = S2EdgeUtil.GetDistance(point, Vertex(i), Vertex(i + 1));
305 | if (distanceToSegment < minDistance)
306 | {
307 | minDistance = distanceToSegment;
308 | minIndex = i;
309 | }
310 | }
311 | return minIndex;
312 | }
313 |
314 | /**
315 | * Given a point p and the index of the start point of an edge of this polyline,
316 | * returns the point on that edge that is closest to p.
317 | */
318 |
319 | public S2Point ProjectToEdge(S2Point point, int index)
320 | {
321 | Preconditions.CheckState(NumVertices > 0, "Empty polyline");
322 | Preconditions.CheckState(NumVertices == 1 || index < NumVertices - 1, "Invalid edge index");
323 | if (NumVertices == 1)
324 | {
325 | // If there is only one vertex, it is always closest to any given point.
326 | return Vertex(0);
327 | }
328 | return S2EdgeUtil.GetClosestPoint(point, Vertex(index), Vertex(index + 1));
329 | }
330 | }
331 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/S2Geometry/S2LatLng.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Google.Common.Geometry
8 | {
9 | /**
10 | * This class represents a point on the unit sphere as a pair of
11 | * latitude-longitude coordinates. Like the rest of the "geometry" package, the
12 | * intent is to represent spherical geometry as a mathematical abstraction, so
13 | * functions that are specifically related to the Earth's geometry (e.g.
14 | * easting/northing conversions) should be put elsewhere.
15 | *
16 | */
17 |
18 | public struct S2LatLng : IEquatable
19 | {
20 | public const double EarthRadiusMeters = 6367000.0;
21 |
22 | /** The center point the lat/lng coordinate system. */
23 | public static readonly S2LatLng Center = new S2LatLng(0.0, 0.0);
24 |
25 | private readonly double _latRadians;
26 | private readonly double _lngRadians;
27 |
28 | private S2LatLng(double latRadians, double lngRadians)
29 | {
30 | _latRadians = latRadians;
31 | _lngRadians = lngRadians;
32 | }
33 |
34 | /**
35 | * Basic constructor. The latitude and longitude must be within the ranges
36 | * allowed by is_valid() below.
37 | *
38 | * TODO(dbeaumont): Make this a static factory method (fromLatLng() ?).
39 | */
40 |
41 | public S2LatLng(S1Angle lat, S1Angle lng) : this(lat.Radians, lng.Radians)
42 | {
43 | }
44 |
45 |
46 | /**
47 | * Convert a point (not necessarily normalized) to an S2LatLng.
48 | *
49 | * TODO(dbeaumont): Make this a static factory method (fromPoint() ?).
50 | */
51 |
52 | public S2LatLng(S2Point p) : this(Math.Atan2(p.Z, Math.Sqrt(p.X*p.X + p.Y*p.Y)), Math.Atan2(p.Y, p.X))
53 | {
54 | // The latitude and longitude are already normalized. We use atan2 to
55 | // compute the latitude because the input vector is not necessarily unit
56 | // length, and atan2 is much more accurate than asin near the poles.
57 | // Note that atan2(0, 0) is defined to be zero.
58 | }
59 |
60 | internal S1Angle Lat
61 | {
62 | get { return S1Angle.FromRadians(_latRadians); }
63 | }
64 |
65 | /** Returns the latitude of this point as radians. */
66 |
67 | public double LatRadians
68 | {
69 | get { return _latRadians; }
70 | }
71 |
72 | /** Returns the latitude of this point as degrees. */
73 |
74 | public double LatDegrees
75 | {
76 | get { return 180.0/Math.PI*_latRadians; }
77 | }
78 |
79 | /** Returns the longitude of this point as a new S1Angle. */
80 |
81 | internal S1Angle Lng
82 | {
83 | get { return S1Angle.FromRadians(_lngRadians); }
84 | }
85 |
86 | /** Returns the longitude of this point as radians. */
87 |
88 | public double LngRadians
89 | {
90 | get { return _lngRadians; }
91 | }
92 |
93 | /** Returns the longitude of this point as degrees. */
94 |
95 | public double LngDegrees
96 | {
97 | get { return 180.0/Math.PI*_lngRadians; }
98 | }
99 |
100 | /**
101 | * Return true if the latitude is between -90 and 90 degrees inclusive and the
102 | * longitude is between -180 and 180 degrees inclusive.
103 | */
104 |
105 | public bool IsValid
106 | {
107 | get { return Math.Abs(Lat.Radians) <= S2.PiOver2 && Math.Abs(Lng.Radians) <= S2.Pi; }
108 | }
109 |
110 | /**
111 | * Returns a new S2LatLng based on this instance for which {@link #isValid()}
112 | * will be {@code true}.
113 | *
114 | * - Latitude is clipped to the range {@code [-90, 90]}
115 | *
- Longitude is normalized to be in the range {@code [-180, 180]}
116 | *
117 | * If the current point is valid then the returned point will have the same
118 | * coordinates.
119 | */
120 |
121 | public S2LatLng Normalized
122 | {
123 | get
124 | {
125 | // drem(x, 2 * S2.M_PI) reduces its argument to the range
126 | // [-S2.M_PI, S2.M_PI] inclusive, which is what we want here.
127 | return new S2LatLng(Math.Max(-S2.PiOver2, Math.Min(S2.PiOver2, Lat.Radians)),
128 | Math.IEEERemainder(Lng.Radians, 2*S2.Pi));
129 | }
130 | }
131 |
132 | public bool Equals(S2LatLng other)
133 | {
134 | return _lngRadians.Equals(other._lngRadians) && _latRadians.Equals(other._latRadians);
135 | }
136 |
137 | public override bool Equals(object obj)
138 | {
139 | if (ReferenceEquals(null, obj)) return false;
140 | return obj is S2LatLng && Equals((S2LatLng)obj);
141 | }
142 |
143 | public override int GetHashCode()
144 | {
145 | unchecked
146 | {
147 | return (_lngRadians.GetHashCode()*397) ^ _latRadians.GetHashCode();
148 | }
149 | }
150 |
151 | public static bool operator ==(S2LatLng left, S2LatLng right)
152 | {
153 | return left.Equals(right);
154 | }
155 |
156 | public static bool operator !=(S2LatLng left, S2LatLng right)
157 | {
158 | return !left.Equals(right);
159 | }
160 |
161 | /**
162 | * Approximate "effective" radius of the Earth in meters.
163 | */
164 |
165 | public static S2LatLng FromRadians(double latRadians, double lngRadians)
166 | {
167 | return new S2LatLng(latRadians, lngRadians);
168 | }
169 |
170 | public static S2LatLng FromDegrees(double latDegrees, double lngDegrees)
171 | {
172 | return new S2LatLng(S1Angle.FromDegrees(latDegrees), S1Angle.FromDegrees(lngDegrees));
173 | }
174 |
175 | public static S2LatLng FromE5(long latE5, long lngE5)
176 | {
177 | return new S2LatLng(S1Angle.E5(latE5), S1Angle.E5(lngE5));
178 | }
179 |
180 | public static S2LatLng FromE6(long latE6, long lngE6)
181 | {
182 | return new S2LatLng(S1Angle.E6(latE6), S1Angle.E6(lngE6));
183 | }
184 |
185 | public static S2LatLng FromE7(long latE7, long lngE7)
186 | {
187 | return new S2LatLng(S1Angle.E7(latE7), S1Angle.E7(lngE7));
188 | }
189 |
190 | public static S1Angle Latitude(S2Point p)
191 | {
192 | // We use atan2 rather than asin because the input vector is not necessarily
193 | // unit length, and atan2 is much more accurate than asin near the poles.
194 | return S1Angle.FromRadians(
195 | Math.Atan2(p[2], Math.Sqrt(p[0]*p[0] + p[1]*p[1])));
196 | }
197 |
198 | public static S1Angle Longitude(S2Point p)
199 | {
200 | // Note that atan2(0, 0) is defined to be zero.
201 | return S1Angle.FromRadians(Math.Atan2(p[1], p[0]));
202 | }
203 |
204 | /** This is internal to avoid ambiguity about which units are expected. */
205 |
206 | /** Returns the latitude of this point as a new S1Angle. */
207 |
208 | // Clamps the latitude to the range [-90, 90] degrees, and adds or subtracts
209 | // a multiple of 360 degrees to the longitude if necessary to reduce it to
210 | // the range [-180, 180].
211 |
212 | /** Convert an S2LatLng to the equivalent unit-length vector (S2Point). */
213 |
214 | public S2Point ToPoint()
215 | {
216 | var phi = Lat.Radians;
217 | var theta = Lng.Radians;
218 | var cosphi = Math.Cos(phi);
219 | return new S2Point(Math.Cos(theta)*cosphi, Math.Sin(theta)*cosphi, Math.Sin(phi));
220 | }
221 |
222 | /**
223 | * Return the distance (measured along the surface of the sphere) to the given
224 | * point.
225 | */
226 |
227 | public S1Angle GetDistance(S2LatLng o)
228 | {
229 | // This implements the Haversine formula, which is numerically stable for
230 | // small distances but only gets about 8 digits of precision for very large
231 | // distances (e.g. antipodal points). Note that 8 digits is still accurate
232 | // to within about 10cm for a sphere the size of the Earth.
233 | //
234 | // This could be fixed with another sin() and cos() below, but at that point
235 | // you might as well just convert both arguments to S2Points and compute the
236 | // distance that way (which gives about 15 digits of accuracy for all
237 | // distances).
238 |
239 | var lat1 = Lat.Radians;
240 | var lat2 = o.Lat.Radians;
241 | var lng1 = Lng.Radians;
242 | var lng2 = o.Lng.Radians;
243 | var dlat = Math.Sin(0.5*(lat2 - lat1));
244 | var dlng = Math.Sin(0.5*(lng2 - lng1));
245 | var x = dlat*dlat + dlng*dlng*Math.Cos(lat1)*Math.Cos(lat2);
246 | return S1Angle.FromRadians(2*Math.Atan2(Math.Sqrt(x), Math.Sqrt(Math.Max(0.0, 1.0 - x))));
247 | // Return the distance (measured along the surface of the sphere) to the
248 | // given S2LatLng. This is mathematically equivalent to:
249 | //
250 | // S1Angle::FromRadians(ToPoint().Angle(o.ToPoint())
251 | //
252 | // but this implementation is slightly more efficient.
253 | }
254 |
255 | /**
256 | * Returns the surface distance to the given point assuming a constant radius.
257 | */
258 |
259 | public double GetDistance(S2LatLng o, double radius)
260 | {
261 | // TODO(dbeaumont): Maybe check that radius >= 0 ?
262 | return GetDistance(o).Radians*radius;
263 | }
264 |
265 | /**
266 | * Returns the surface distance to the given point assuming the default Earth
267 | * radius of {@link #EARTH_RADIUS_METERS}.
268 | */
269 |
270 | public double GetEarthDistance(S2LatLng o)
271 | {
272 | return GetDistance(o, EarthRadiusMeters);
273 | }
274 |
275 | /**
276 | * Adds the given point to this point.
277 | * Note that there is no guarantee that the new point will be valid.
278 | */
279 |
280 | public static S2LatLng operator +(S2LatLng x, S2LatLng y)
281 | {
282 | return new S2LatLng(x._latRadians + y._latRadians, x._lngRadians + y._lngRadians);
283 | }
284 |
285 |
286 | /**
287 | * Subtracts the given point from this point.
288 | * Note that there is no guarantee that the new point will be valid.
289 | */
290 |
291 | public static S2LatLng operator -(S2LatLng x, S2LatLng y)
292 | {
293 | return new S2LatLng(x._latRadians - y._latRadians, x._lngRadians - y._lngRadians);
294 | }
295 |
296 | /**
297 | * Scales this point by the given scaling factor.
298 | * Note that there is no guarantee that the new point will be valid.
299 | */
300 |
301 | public static S2LatLng operator *(S2LatLng x, double m)
302 | {
303 | // TODO(dbeaumont): Maybe check that m >= 0 ?
304 | return new S2LatLng(x._latRadians*m, x._lngRadians*m);
305 | }
306 |
307 | /**
308 | * Returns true if both the latitude and longitude of the given point are
309 | * within {@code maxError} radians of this point.
310 | */
311 |
312 | public bool ApproxEquals(S2LatLng o, double maxError)
313 | {
314 | return (Math.Abs(_latRadians - o._latRadians) < maxError)
315 | && (Math.Abs(_lngRadians - o._lngRadians) < maxError);
316 | }
317 |
318 | /**
319 | * Returns true if the given point is within {@code 1e-9} radians of this
320 | * point. This corresponds to a distance of less than {@code 1cm} at the
321 | * surface of the Earth.
322 | */
323 |
324 | public bool ApproxEquals(S2LatLng o)
325 | {
326 | return ApproxEquals(o, 1e-9);
327 | }
328 |
329 | public override String ToString()
330 | {
331 | return "(" + _latRadians + ", " + _lngRadians + ")";
332 | }
333 |
334 | public String ToStringDegrees()
335 | {
336 | return "(" + LatDegrees + ", " + LngDegrees + ")";
337 | }
338 | }
339 | }
--------------------------------------------------------------------------------
/S2Geometry.Tests/S2CapTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Google.Common.Geometry;
7 | using NUnit.Framework;
8 |
9 | namespace S2Geometry.Tests
10 | {
11 | public class S2CapTest : GeometryTestCase
12 | {
13 | public S2Point getLatLngPoint(double latDegrees, double lngDegrees)
14 | {
15 | return S2LatLng.FromDegrees(latDegrees, lngDegrees).ToPoint();
16 | }
17 |
18 | // About 9 times the double-precision roundoff relative error.
19 | public const double EPS = 1e-15;
20 |
21 | public void testRectBound()
22 | {
23 | // Empty and full caps.
24 | Assert.True(S2Cap.Empty.RectBound.IsEmpty);
25 | Assert.True(S2Cap.Full.RectBound.IsFull);
26 |
27 | var kDegreeEps = 1e-13;
28 | // Maximum allowable error for latitudes and longitudes measured in
29 | // degrees. (assertDoubleNear uses a fixed tolerance that is too small.)
30 |
31 | // Cap that includes the south pole.
32 | var rect =
33 | S2Cap.FromAxisAngle(getLatLngPoint(-45, 57), S1Angle.FromDegrees(50)).RectBound;
34 | assertDoubleNear(rect.LatLo.Degrees, -90, kDegreeEps);
35 | assertDoubleNear(rect.LatHi.Degrees, 5, kDegreeEps);
36 | Assert.True(rect.Lng.IsFull);
37 |
38 | // Cap that is tangent to the north pole.
39 | rect = S2Cap.FromAxisAngle(S2Point.Normalize(new S2Point(1, 0, 1)), S1Angle.FromRadians(S2.PiOver4)).RectBound;
40 | assertDoubleNear(rect.Lat.Lo, 0);
41 | assertDoubleNear(rect.Lat.Hi, S2.PiOver2);
42 | Assert.True(rect.Lng.IsFull);
43 |
44 | rect = S2Cap
45 | .FromAxisAngle(S2Point.Normalize(new S2Point(1, 0, 1)), S1Angle.FromDegrees(45)).RectBound;
46 | assertDoubleNear(rect.LatLo.Degrees, 0, kDegreeEps);
47 | assertDoubleNear(rect.LatHi.Degrees, 90, kDegreeEps);
48 | Assert.True(rect.Lng.IsFull);
49 |
50 | // The eastern hemisphere.
51 | rect = S2Cap
52 | .FromAxisAngle(new S2Point(0, 1, 0), S1Angle.FromRadians(S2.PiOver2 + 5e-16)).RectBound;
53 | assertDoubleNear(rect.LatLo.Degrees, -90, kDegreeEps);
54 | assertDoubleNear(rect.LatHi.Degrees, 90, kDegreeEps);
55 | Assert.True(rect.Lng.IsFull);
56 |
57 | // A cap centered on the equator.
58 | rect = S2Cap.FromAxisAngle(getLatLngPoint(0, 50), S1Angle.FromDegrees(20)).RectBound;
59 | assertDoubleNear(rect.LatLo.Degrees, -20, kDegreeEps);
60 | assertDoubleNear(rect.LatHi.Degrees, 20, kDegreeEps);
61 | assertDoubleNear(rect.LngLo.Degrees, 30, kDegreeEps);
62 | assertDoubleNear(rect.LngHi.Degrees, 70, kDegreeEps);
63 |
64 | // A cap centered on the north pole.
65 | rect = S2Cap.FromAxisAngle(getLatLngPoint(90, 123), S1Angle.FromDegrees(10)).RectBound;
66 | assertDoubleNear(rect.LatLo.Degrees, 80, kDegreeEps);
67 | assertDoubleNear(rect.LatHi.Degrees, 90, kDegreeEps);
68 | Assert.True(rect.Lng.IsFull);
69 | }
70 |
71 | public void testCells()
72 | {
73 | // For each cube face, we construct some cells on
74 | // that face and some caps whose positions are relative to that face,
75 | // and then check for the expected intersection/containment results.
76 |
77 | // The distance from the center of a face to one of its vertices.
78 | var kFaceRadius = Math.Atan(S2.Sqrt2);
79 |
80 | for (var face = 0; face < 6; ++face)
81 | {
82 | // The cell consisting of the entire face.
83 | var rootCell = S2Cell.FromFacePosLevel(face, (byte)0, 0);
84 |
85 | // A leaf cell at the midpoint of the v=1 edge.
86 | var edgeCell = new S2Cell(S2Projections.FaceUvToXyz(face, 0, 1 - EPS));
87 |
88 | // A leaf cell at the u=1, v=1 corner.
89 | var cornerCell = new S2Cell(S2Projections.FaceUvToXyz(face, 1 - EPS, 1 - EPS));
90 |
91 | // Quick check for full and empty caps.
92 | Assert.True(S2Cap.Full.Contains(rootCell));
93 | Assert.True(!S2Cap.Empty.MayIntersect(rootCell));
94 |
95 | // Check intersections with the bounding caps of the leaf cells that are
96 | // adjacent to 'corner_cell' along the Hilbert curve. Because this corner
97 | // is at (u=1,v=1), the curve stays locally within the same cube face.
98 | var first = cornerCell.Id.Previous.Previous.Previous;
99 | var last = cornerCell.Id.Next.Next.Next.Next;
100 | for (var id = first; id 0.1);
117 | JavaAssert.Equal(covering.Contains(edgeCell), covering.MayIntersect(edgeCell));
118 | JavaAssert.Equal(covering.Contains(cornerCell), capFace == face);
119 | JavaAssert.Equal(
120 | covering.MayIntersect(cornerCell), center.DotProd(cornerCell.Center) > 0);
121 |
122 | // A cap that barely intersects the edges of 'cap_face'.
123 | var bulging = S2Cap.FromAxisAngle(center, S1Angle.FromRadians(S2.PiOver4 + EPS));
124 | Assert.True(!bulging.Contains(rootCell));
125 | JavaAssert.Equal(bulging.MayIntersect(rootCell), capFace != antiFace);
126 | JavaAssert.Equal(bulging.Contains(edgeCell), capFace == face);
127 | JavaAssert.Equal(bulging.MayIntersect(edgeCell), center.DotProd(edgeCell.Center) > 0.1);
128 | Assert.True(!bulging.Contains(cornerCell));
129 | Assert.True(!bulging.MayIntersect(cornerCell));
130 |
131 | // A singleton cap.
132 | var singleton = S2Cap.FromAxisAngle(center, S1Angle.FromRadians(0));
133 | JavaAssert.Equal(singleton.MayIntersect(rootCell), capFace == face);
134 | Assert.True(!singleton.MayIntersect(edgeCell));
135 | Assert.True(!singleton.MayIntersect(cornerCell));
136 | }
137 | }
138 | }
139 |
140 | [Test]
141 | public void S2CapBasicTest()
142 | {
143 | // Test basic properties of empty and full caps.
144 | var empty = S2Cap.Empty;
145 | var full = S2Cap.Full;
146 | Assert.True(empty.IsValid);
147 | Assert.True(empty.IsEmpty);
148 | Assert.True(empty.Complement.IsFull);
149 | Assert.True(full.IsValid);
150 | Assert.True(full.IsFull);
151 | Assert.True(full.Complement.IsEmpty);
152 | JavaAssert.Equal(full.Height, 2.0);
153 | assertDoubleNear(full.Angle.Degrees, 180);
154 |
155 | // Containment and intersection of empty and full caps.
156 | Assert.True(empty.Contains(empty));
157 | Assert.True(full.Contains(empty));
158 | Assert.True(full.Contains(full));
159 | Assert.True(!empty.InteriorIntersects(empty));
160 | Assert.True(full.InteriorIntersects(full));
161 | Assert.True(!full.InteriorIntersects(empty));
162 |
163 | // Singleton cap containing the x-axis.
164 | var xaxis = S2Cap.FromAxisHeight(new S2Point(1, 0, 0), 0);
165 | Assert.True(xaxis.Contains(new S2Point(1, 0, 0)));
166 | Assert.True(!xaxis.Contains(new S2Point(1, 1e-20, 0)));
167 | JavaAssert.Equal(xaxis.Angle.Radians, 0.0);
168 |
169 | // Singleton cap containing the y-axis.
170 | var yaxis = S2Cap.FromAxisAngle(new S2Point(0, 1, 0), S1Angle.FromRadians(0));
171 | Assert.True(!yaxis.Contains(xaxis.Axis));
172 | JavaAssert.Equal(xaxis.Height, 0.0);
173 |
174 | // Check that the complement of a singleton cap is the full cap.
175 | var xcomp = xaxis.Complement;
176 | Assert.True(xcomp.IsValid);
177 | Assert.True(xcomp.IsFull);
178 | Assert.True(xcomp.Contains(xaxis.Axis));
179 |
180 | // Check that the complement of the complement is *not* the original.
181 | Assert.True(xcomp.Complement.IsValid);
182 | Assert.True(xcomp.Complement.IsEmpty);
183 | Assert.True(!xcomp.Complement.Contains(xaxis.Axis));
184 |
185 | // Check that very small caps can be represented accurately.
186 | // Here "kTinyRad" is small enough that unit vectors perturbed by this
187 | // amount along a tangent do not need to be renormalized.
188 | var kTinyRad = 1e-10;
189 | var tiny =
190 | S2Cap.FromAxisAngle(S2Point.Normalize(new S2Point(1, 2, 3)), S1Angle.FromRadians(kTinyRad));
191 | var tangent = S2Point.Normalize(S2Point.CrossProd(tiny.Axis, new S2Point(3, 2, 1)));
192 | Assert.True(tiny.Contains(tiny.Axis + (tangent* 0.99*kTinyRad)));
193 | Assert.True(!tiny.Contains(tiny.Axis + (tangent* 1.01*kTinyRad)));
194 |
195 | // Basic tests on a hemispherical cap.
196 | var hemi = S2Cap.FromAxisHeight(S2Point.Normalize(new S2Point(1, 0, 1)), 1);
197 | JavaAssert.Equal(hemi.Complement.Axis, -hemi.Axis);
198 | JavaAssert.Equal(hemi.Complement.Height, 1.0);
199 | Assert.True(hemi.Contains(new S2Point(1, 0, 0)));
200 | Assert.True(!hemi.Complement.Contains(new S2Point(1, 0, 0)));
201 | Assert.True(hemi.Contains(S2Point.Normalize(new S2Point(1, 0, -(1 - EPS)))));
202 | Assert.True(!hemi.InteriorContains(S2Point.Normalize(new S2Point(1, 0, -(1 + EPS)))));
203 |
204 | // A concave cap.
205 | var concave = S2Cap.FromAxisAngle(getLatLngPoint(80, 10), S1Angle.FromDegrees(150));
206 | Assert.True(concave.Contains(getLatLngPoint(-70*(1 - EPS), 10)));
207 | Assert.True(!concave.Contains(getLatLngPoint(-70*(1 + EPS), 10)));
208 | Assert.True(concave.Contains(getLatLngPoint(-50*(1 - EPS), -170)));
209 | Assert.True(!concave.Contains(getLatLngPoint(-50*(1 + EPS), -170)));
210 |
211 | // Cap containment tests.
212 | Assert.True(!empty.Contains(xaxis));
213 | Assert.True(!empty.InteriorIntersects(xaxis));
214 | Assert.True(full.Contains(xaxis));
215 | Assert.True(full.InteriorIntersects(xaxis));
216 | Assert.True(!xaxis.Contains(full));
217 | Assert.True(!xaxis.InteriorIntersects(full));
218 | Assert.True(xaxis.Contains(xaxis));
219 | Assert.True(!xaxis.InteriorIntersects(xaxis));
220 | Assert.True(xaxis.Contains(empty));
221 | Assert.True(!xaxis.InteriorIntersects(empty));
222 | Assert.True(hemi.Contains(tiny));
223 | Assert.True(hemi.Contains(
224 | S2Cap.FromAxisAngle(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.PiOver4 - EPS))));
225 | Assert.True(!hemi.Contains(
226 | S2Cap.FromAxisAngle(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.PiOver4 + EPS))));
227 | Assert.True(concave.Contains(hemi));
228 | Assert.True(concave.InteriorIntersects(hemi.Complement));
229 | Assert.True(!concave.Contains(S2Cap.FromAxisHeight(-concave.Axis, 0.1)));
230 | }
231 | }
232 | }
--------------------------------------------------------------------------------
/S2Geometry.Tests/S2CellIdTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Google.Common.Geometry;
8 | using NUnit.Framework;
9 |
10 | namespace S2Geometry.Tests
11 | {
12 | public class S2CellIdTest : GeometryTestCase
13 | {
14 | private S2CellId getCellId(double latDegrees, double lngDegrees)
15 | {
16 | var id = S2CellId.FromLatLng(S2LatLng.FromDegrees(latDegrees, lngDegrees));
17 | Trace.WriteLine(Convert.ToString(unchecked ((long)id.Id), 16));
18 | return id;
19 | }
20 |
21 | public void testInverses()
22 | {
23 | Trace.WriteLine("TestInverses");
24 | // Check the conversion of random leaf cells to S2LatLngs and back.
25 | for (var i = 0; i < 200000; ++i)
26 | {
27 | var id = getRandomCellId(S2CellId.MaxLevel);
28 | Assert.True(id.IsLeaf && id.Level == S2CellId.MaxLevel);
29 | var center = id.ToLatLng();
30 | JavaAssert.Equal(S2CellId.FromLatLng(center).Id, id.Id);
31 | }
32 | }
33 |
34 | private const int kMaxExpandLevel = 3;
35 |
36 | private void expandCell(
37 | S2CellId parent, List cells, IDictionary parentMap)
38 | {
39 | cells.Add(parent);
40 | if (parent.Level == kMaxExpandLevel)
41 | {
42 | return;
43 | }
44 | var i = 0;
45 | var j = 0;
46 | int? orientation = 0;
47 | var face = parent.ToFaceIjOrientation(ref i, ref j, ref orientation);
48 | JavaAssert.Equal(face, parent.Face);
49 |
50 | var pos = 0;
51 | for (var child = parent.ChildBegin; !child.Equals(parent.ChildEnd);
52 | child = child.Next)
53 | {
54 | // Do some basic checks on the children
55 | JavaAssert.Equal(child.Level, parent.Level + 1);
56 | Assert.True(!child.IsLeaf);
57 | int? childOrientation = 0;
58 | JavaAssert.Equal(child.ToFaceIjOrientation(ref i, ref j, ref childOrientation), face);
59 | JavaAssert.Equal(
60 | childOrientation.Value, orientation.Value ^ S2.PosToOrientation(pos));
61 |
62 | parentMap.Add(child, parent);
63 | expandCell(child, cells, parentMap);
64 | ++pos;
65 | }
66 | }
67 |
68 | private const int MAX_WALK_LEVEL = 8;
69 |
70 | public void testAllNeighbors(S2CellId id, int level)
71 | {
72 | Assert.True(level >= id.Level && level < S2CellId.MaxLevel);
73 |
74 | // We compute GetAllNeighbors, and then add in all the children of "id"
75 | // at the given level. We then compare this against the result of finding
76 | // all the vertex neighbors of all the vertices of children of "id" at the
77 | // given level. These should give the same result.
78 | var all = new List();
79 | var expected = new List();
80 | id.GetAllNeighbors(level, all);
81 | var end = id.ChildEndForLevel(level + 1);
82 | for (var c = id.ChildBeginForLevel(level + 1); !c.Equals(end); c = c.Next)
83 | {
84 | all.Add(c.Parent);
85 | c.GetVertexNeighbors(level, expected);
86 | }
87 | // Sort the results and eliminate duplicates.
88 | all.Sort();
89 | expected.Sort();
90 | ISet allSet = new HashSet(all);
91 | ISet expectedSet = new HashSet(expected);
92 | var result = allSet.SetEquals(expectedSet);
93 | Assert.True(result);
94 | }
95 |
96 | [Test]
97 | public void S2CellIdTestBasic()
98 | {
99 | Trace.WriteLine("TestBasic");
100 | // Check default constructor.
101 | var id = new S2CellId();
102 | //JavaAssert.Equal(id.id(), 0);
103 | //Assert.True(!id.isValid());
104 |
105 | // Check basic accessor methods.
106 | id = S2CellId.FromFacePosLevel(3, 0x12345678, S2CellId.MaxLevel - 4);
107 | //Assert.True(id.isValid());
108 | //JavaAssert.Equal(id.face(), 3);
109 | // JavaAssert.Equal(id.pos(), 0x12345700);
110 | //JavaAssert.Equal(id.level(), S2CellId.MAX_LEVEL - 4);
111 | //Assert.True(!id.isLeaf());
112 |
113 | //// Check face definitions
114 | //JavaAssert.Equal(getCellId(0, 0).face(), 0);
115 | //JavaAssert.Equal(getCellId(0, 90).face(), 1);
116 | //JavaAssert.Equal(getCellId(90, 0).face(), 2);
117 | //JavaAssert.Equal(getCellId(0, 180).face(), 3);
118 | //JavaAssert.Equal(getCellId(0, -90).face(), 4);
119 | //JavaAssert.Equal(getCellId(-90, 0).face(), 5);
120 |
121 | //// Check parent/child relationships.
122 | //JavaAssert.Equal(id.childBegin(id.level() + 2).pos(), 0x12345610);
123 | //JavaAssert.Equal(id.childBegin().pos(), 0x12345640);
124 | //JavaAssert.Equal(id.parent().pos(), 0x12345400);
125 | //JavaAssert.Equal(id.parent(id.level() - 2).pos(), 0x12345000);
126 |
127 | //// Check ordering of children relative to parents.
128 | //Assert.True(id.childBegin().lessThan(id));
129 | //var childEnd = id.childEnd();
130 | //var childId = childEnd.id();
131 | //var id1 = id.id();
132 |
133 | //Assert.True(id.childEnd().greaterThan(id));
134 | //JavaAssert.Equal(id.childBegin().next().next().next().next(), id.childEnd());
135 | //JavaAssert.Equal(id.childBegin(S2CellId.MAX_LEVEL), id.rangeMin());
136 | //JavaAssert.Equal(id.childEnd(S2CellId.MAX_LEVEL), id.rangeMax().next());
137 |
138 | // Check wrapping from beginning of Hilbert curve to end and vice versa.
139 | // JavaAssert.Equal(S2CellId.begin(0).prevWrap(), S2CellId.end(0).prev());
140 |
141 | JavaAssert.Equal(S2CellId.Begin(S2CellId.MaxLevel).PreviousWithWrap,
142 | S2CellId.FromFacePosLevel(5, ~0UL >> S2CellId.FaceBits, S2CellId.MaxLevel));
143 |
144 | JavaAssert.Equal(S2CellId.End(4).Previous.NextWithWrap, S2CellId.Begin(4));
145 | JavaAssert.Equal(S2CellId.End(S2CellId.MaxLevel).Previous.NextWithWrap,
146 | S2CellId.FromFacePosLevel(0, 0, S2CellId.MaxLevel));
147 |
148 | // Check that cells are represented by the position of their center
149 | // along the Hilbert curve.
150 | JavaAssert.Equal(id.RangeMin.Id + id.RangeMax.Id, 2*id.Id);
151 | }
152 |
153 | [Test]
154 | public void testContainment()
155 | {
156 | Trace.WriteLine("TestContainment");
157 | IDictionary parentMap = new Dictionary();
158 | var cells = new List();
159 | for (var face = 0; face < 6; ++face)
160 | {
161 | expandCell(S2CellId.FromFacePosLevel(face, 0, 0), cells, parentMap);
162 | }
163 | for (var i = 0; i < cells.Count; ++i)
164 | {
165 | for (var j = 0; j < cells.Count; ++j)
166 | {
167 | var contained = true;
168 | for (var id = cells[j]; id != cells[i]; id = parentMap[id])
169 | {
170 | if (!parentMap.ContainsKey(id))
171 | {
172 | contained = false;
173 | break;
174 | }
175 | }
176 | JavaAssert.Equal(cells[i].Contains(cells[j]), contained);
177 | JavaAssert.Equal(cells[j] >= cells[i].RangeMin
178 | && cells[j] <= cells[i].RangeMax, contained);
179 | JavaAssert.Equal(cells[i].Intersects(cells[j]),
180 | cells[i].Contains(cells[j]) || cells[j].Contains(cells[i]));
181 | }
182 | }
183 | }
184 |
185 | [Test]
186 | public void testContinuity()
187 | {
188 | Trace.WriteLine("TestContinuity");
189 | // Make sure that sequentially increasing cell ids form a continuous
190 | // path over the surface of the sphere, i.e. there are no
191 | // discontinuous jumps from one region to another.
192 |
193 | var maxDist = S2Projections.MaxEdge.GetValue(MAX_WALK_LEVEL);
194 | var end = S2CellId.End(MAX_WALK_LEVEL);
195 | var id = S2CellId.Begin(MAX_WALK_LEVEL);
196 | for (; !id.Equals(end); id = id.Next)
197 | {
198 | Assert.True(id.ToPointRaw().Angle(id.NextWithWrap.ToPointRaw()) <= maxDist);
199 |
200 | // Check that the ToPointRaw() returns the center of each cell
201 | // in (s,t) coordinates.
202 | var p = id.ToPointRaw();
203 | var face = S2Projections.XyzToFace(p);
204 | var uv = S2Projections.ValidFaceXyzToUv(face, p);
205 | assertDoubleNear(Math.IEEERemainder(
206 | S2Projections.UvToSt(uv.X), 1.0/(1 << MAX_WALK_LEVEL)), 0);
207 | assertDoubleNear(Math.IEEERemainder(
208 | S2Projections.UvToSt(uv.Y), 1.0/(1 << MAX_WALK_LEVEL)), 0);
209 | }
210 | }
211 |
212 | [Test]
213 | public void testCoverage()
214 | {
215 | Trace.WriteLine("TestCoverage");
216 | // Make sure that random points on the sphere can be represented to the
217 | // expected level of accuracy, which in the worst case is sqrt(2/3) times
218 | // the maximum arc length between the points on the sphere associated with
219 | // adjacent values of "i" or "j". (It is sqrt(2/3) rather than 1/2 because
220 | // the cells at the corners of each face are stretched -- they have 60 and
221 | // 120 degree angles.)
222 |
223 | var maxDist = 0.5*S2Projections.MaxDiag.GetValue(S2CellId.MaxLevel);
224 | for (var i = 0; i < 1000000; ++i)
225 | {
226 | // randomPoint();
227 | var p = new S2Point(0.37861576725894824, 0.2772406863275093, 0.8830558887338725);
228 | var q = S2CellId.FromPoint(p).ToPointRaw();
229 |
230 | Assert.True(p.Angle(q) <= maxDist);
231 | }
232 | }
233 |
234 | //[Test]
235 | [Ignore("Not necessrily valid values. Fails on Java too.")]
236 | public void testNeighborLevel29()
237 | {
238 | // Note: These parameters fail on the Java version too. Not sure if this is a valid Cell anyway
239 | testAllNeighbors(new S2CellId(0x6000000000000004UL), 29);
240 | }
241 |
242 | [Test]
243 | public void testNeighbors()
244 | {
245 | Trace.WriteLine("TestNeighbors");
246 |
247 | // Check the edge neighbors of face 1.
248 | int[] outFaces = {5, 3, 2, 0};
249 |
250 | var faceNbrs = S2CellId.FromFacePosLevel(1, 0, 0).GetEdgeNeighbors();
251 | for (var i = 0; i < 4; ++i)
252 | {
253 | Assert.True(faceNbrs[i].IsFace);
254 | JavaAssert.Equal(faceNbrs[i].Face, outFaces[i]);
255 | }
256 |
257 | // Check the vertex neighbors of the center of face 2 at level 5.
258 | var nbrs = new List();
259 | S2CellId.FromPoint(new S2Point(0, 0, 1)).GetVertexNeighbors(5, nbrs);
260 | nbrs.Sort();
261 | for (var i = 0; i < 4; ++i)
262 | {
263 | JavaAssert.Equal(nbrs[i], S2CellId.FromFaceIj(
264 | 2, (1 << 29) - (i < 2 ? 1 : 0), (1 << 29) - ((i == 0 || i == 3) ? 1 : 0)).ParentForLevel(5));
265 | }
266 | nbrs.Clear();
267 |
268 | // Check the vertex neighbors of the corner of faces 0, 4, and 5.
269 | var id = S2CellId.FromFacePosLevel(0, 0, S2CellId.MaxLevel);
270 | id.GetVertexNeighbors(0, nbrs);
271 | nbrs.Sort();
272 |
273 | JavaAssert.Equal(nbrs.Count, 3);
274 | JavaAssert.Equal(nbrs[0], S2CellId.FromFacePosLevel(0, 0, 0));
275 | JavaAssert.Equal(nbrs[1], S2CellId.FromFacePosLevel(4, 0, 0));
276 | JavaAssert.Equal(nbrs[2], S2CellId.FromFacePosLevel(5, 0, 0));
277 |
278 | // Check that GetAllNeighbors produces results that are consistent
279 | // with GetVertexNeighbors for a bunch of random cells.
280 | for (var i = 0; i < 1000; ++i)
281 | {
282 | var id1 = getRandomCellId();
283 | var toTest = id1;
284 | if (id1.IsLeaf)
285 | {
286 | toTest = id1.Parent;
287 | }
288 |
289 | // TestAllNeighbors computes approximately 2**(2*(diff+1)) cell id1s,
290 | // so it's not reasonable to use large values of "diff".
291 | var maxDiff = Math.Min(6, S2CellId.MaxLevel - toTest.Level - 1);
292 | var level = toTest.Level + random(maxDiff);
293 | testAllNeighbors(toTest, level);
294 | }
295 | }
296 |
297 | [Test]
298 | public void testToToken()
299 | {
300 | JavaAssert.Equal("000000000000010a", new S2CellId(266).ToToken());
301 | JavaAssert.Equal("80855c", new S2CellId(unchecked ((ulong)-9185834709882503168L)).ToToken());
302 | }
303 |
304 | [Test]
305 | public void testTokens()
306 | {
307 | Trace.WriteLine("TestTokens");
308 |
309 | // Test random cell ids at all levels.
310 | for (var i = 0; i < 10000; ++i)
311 | {
312 | var id = getRandomCellId();
313 | if (!id.IsValid)
314 | {
315 | continue;
316 | }
317 | var token = id.ToToken();
318 | Assert.True(token.Length <= 16);
319 | JavaAssert.Equal(S2CellId.FromToken(token), id);
320 | }
321 | // Check that invalid cell ids can be encoded.
322 | var token1 = S2CellId.None.ToToken();
323 | JavaAssert.Equal(S2CellId.FromToken(token1), S2CellId.None);
324 | }
325 | }
326 | }
--------------------------------------------------------------------------------
/S2Geometry.Tests/S2Test.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Google.Common.Geometry;
7 | using NUnit.Framework;
8 |
9 | namespace S2Geometry.Tests
10 | {
11 | public class S2Test : GeometryTestCase
12 | {
13 | // Test helper methods for testing the traversal order.
14 | private static int swapAxes(int ij)
15 | {
16 | return ((ij >> 1) & 1) + ((ij & 1) << 1);
17 | }
18 |
19 | private static int invertBits(int ij)
20 | {
21 | return ij ^ 3;
22 | }
23 |
24 | // Note: obviously, I could have defined a bundle of metrics like this in the
25 | // S2 class itself rather than just for testing. However, it's not clear that
26 | // this is useful other than for testing purposes, and I find
27 | // S2.kMinWidth.GetMaxLevel(width) to be slightly more readable than
28 | // than S2.kWidth.min().GetMaxLevel(width). Also, there is no fundamental
29 | // reason that we need to analyze the minimum, maximum, and average values of
30 | // every metric; it would be perfectly reasonable to just define one of these.
31 |
32 | public class MetricBundle
33 | {
34 | public S2CellMetric avg_;
35 | public S2CellMetric max_;
36 | public S2CellMetric min_;
37 |
38 | public MetricBundle(S2CellMetric min, S2CellMetric max, S2CellMetric avg)
39 | {
40 | min_ = min;
41 | max_ = max;
42 | avg_ = avg;
43 | }
44 | }
45 |
46 | public void testMinMaxAvg(MetricBundle bundle)
47 | {
48 | assertTrue(bundle.min_.Deriv() < bundle.avg_.Deriv());
49 | assertTrue(bundle.avg_.Deriv() < bundle.max_.Deriv());
50 | }
51 |
52 | public void testLessOrEqual(MetricBundle a, MetricBundle b)
53 | {
54 | assertTrue(a.min_.Deriv() <= b.min_.Deriv());
55 | assertTrue(a.max_.Deriv() <= b.max_.Deriv());
56 | assertTrue(a.avg_.Deriv() <= b.avg_.Deriv());
57 | }
58 |
59 | [Test]
60 | public void testAngleArea()
61 | {
62 | var pz = new S2Point(0, 0, 1);
63 | var p000 = new S2Point(1, 0, 0);
64 | var p045 = new S2Point(1, 1, 0);
65 | var p090 = new S2Point(0, 1, 0);
66 | var p180 = new S2Point(-1, 0, 0);
67 | assertDoubleNear(S2.Angle(p000, pz, p045), S2.PiOver4);
68 | assertDoubleNear(S2.Angle(p045, pz, p180), 3*S2.PiOver4);
69 | assertDoubleNear(S2.Angle(p000, pz, p180), S2.Pi);
70 | assertDoubleNear(S2.Angle(pz, p000, pz), 0);
71 | assertDoubleNear(S2.Angle(pz, p000, p045), S2.PiOver2);
72 |
73 | assertDoubleNear(S2.Area(p000, p090, pz), S2.PiOver2);
74 | assertDoubleNear(S2.Area(p045, pz, p180), 3*S2.PiOver4);
75 |
76 | // Make sure that area() has good *relative* accuracy even for
77 | // very small areas.
78 | var eps = 1e-10;
79 | var pepsx = new S2Point(eps, 0, 1);
80 | var pepsy = new S2Point(0, eps, 1);
81 | var expected1 = 0.5*eps*eps;
82 | assertDoubleNear(S2.Area(pepsx, pepsy, pz), expected1, 1e-14*expected1);
83 |
84 | // Make sure that it can handle degenerate triangles.
85 | var pr = new S2Point(0.257, -0.5723, 0.112);
86 | var pq = new S2Point(-0.747, 0.401, 0.2235);
87 | assertEquals(S2.Area(pr, pr, pr), 0.0);
88 | // TODO: The following test is not exact in optimized mode because the
89 | // compiler chooses to mix 64-bit and 80-bit intermediate results.
90 | assertDoubleNear(S2.Area(pr, pq, pr), 0);
91 | assertEquals(S2.Area(p000, p045, p090), 0.0);
92 |
93 | double maxGirard = 0;
94 | for (var i = 0; i < 10000; ++i)
95 | {
96 | var p0 = randomPoint();
97 | var d1 = randomPoint();
98 | var d2 = randomPoint();
99 | var p1 = p0 + (d1 * 1e-15);
100 | var p2 = p0 + (d2 * 1e-15);
101 | // The actual displacement can be as much as 1.2e-15 due to roundoff.
102 | // This yields a maximum triangle area of about 0.7e-30.
103 | assertTrue(S2.Area(p0, p1, p2) < 0.7e-30);
104 | maxGirard = Math.Max(maxGirard, S2.GirardArea(p0, p1, p2));
105 | }
106 | Console.WriteLine("Worst case Girard for triangle area 1e-30: " + maxGirard);
107 |
108 | // Try a very long and skinny triangle.
109 | var p045eps = new S2Point(1, 1, eps);
110 | var expected2 = 5.8578643762690495119753e-11; // Mathematica.
111 | assertDoubleNear(S2.Area(p000, p045eps, p090), expected2, 1e-9*expected2);
112 |
113 | // Triangles with near-180 degree edges that sum to a quarter-sphere.
114 | var eps2 = 1e-10;
115 | var p000eps2 = new S2Point(1, 0.1*eps2, eps2);
116 | var quarterArea1 =
117 | S2.Area(p000eps2, p000, p090) + S2.Area(p000eps2, p090, p180) + S2.Area(p000eps2, p180, pz)
118 | + S2.Area(p000eps2, pz, p000);
119 | assertDoubleNear(quarterArea1, S2.Pi);
120 |
121 | // Four other triangles that sum to a quarter-sphere.
122 | var p045eps2 = new S2Point(1, 1, eps2);
123 | var quarterArea2 =
124 | S2.Area(p045eps2, p000, p090) + S2.Area(p045eps2, p090, p180) + S2.Area(p045eps2, p180, pz)
125 | + S2.Area(p045eps2, pz, p000);
126 | assertDoubleNear(quarterArea2, S2.Pi);
127 | }
128 |
129 | [Test]
130 | public void testCCW()
131 | {
132 | var a = new S2Point(0.72571927877036835, 0.46058825605889098, 0.51106749730504852);
133 | var b = new S2Point(0.7257192746638208, 0.46058826573818168, 0.51106749441312738);
134 | var c = new S2Point(0.72571927671709457, 0.46058826089853633, 0.51106749585908795);
135 | assertTrue(S2.RobustCcw(a, b, c) != 0);
136 | }
137 |
138 | [Test]
139 | public void testExp()
140 | {
141 | for (var i = 0; i < 10; ++i)
142 | {
143 | assertEquals(i + 1, S2.Exp(Math.Pow(2, i)));
144 | }
145 |
146 | for (var i = 0; i < 10; ++i)
147 | {
148 | assertEquals(i + 1, S2.Exp(-Math.Pow(2, i)));
149 | }
150 |
151 | assertEquals(0, S2.Exp(0));
152 | assertEquals(2, S2.Exp(3));
153 | assertEquals(3, S2.Exp(5));
154 | }
155 |
156 | [Test]
157 | public void testFaceUVtoXYZ()
158 | {
159 | // Check that each face appears exactly once.
160 | var sum = new S2Point();
161 | for (var face = 0; face < 6; ++face)
162 | {
163 | var center = S2Projections.FaceUvToXyz(face, 0, 0);
164 | assertEquals(S2Projections.GetNorm(face), center);
165 | assertEquals(Math.Abs(center[center.LargestAbsComponent]), 1.0);
166 | sum = sum + S2Point.Fabs(center);
167 | }
168 | assertEquals(sum, new S2Point(2, 2, 2));
169 |
170 | // Check that each face has a right-handed coordinate system.
171 | for (var face = 0; face < 6; ++face)
172 | {
173 | assertEquals(
174 | S2Point.CrossProd(S2Projections.GetUAxis(face), S2Projections.GetVAxis(face)).DotProd(
175 | S2Projections.FaceUvToXyz(face, 0, 0)), 1.0);
176 | }
177 |
178 | // Check that the Hilbert curves on each face combine to form a
179 | // continuous curve over the entire cube.
180 | for (var face = 0; face < 6; ++face)
181 | {
182 | // The Hilbert curve on each face starts at (-1,-1) and terminates
183 | // at either (1,-1) (if axes not swapped) or (-1,1) (if swapped).
184 | var sign = ((face & S2.SwapMask) != 0) ? -1 : 1;
185 | assertEquals(S2Projections.FaceUvToXyz(face, sign, -sign),
186 | S2Projections.FaceUvToXyz((face + 1)%6, -1, -1));
187 | }
188 | }
189 |
190 | [Test]
191 | public void testMetrics()
192 | {
193 | var angleSpan = new MetricBundle(
194 | S2Projections.MinAngleSpan, S2Projections.MaxAngleSpan, S2Projections.AvgAngleSpan);
195 | var width =
196 | new MetricBundle(S2Projections.MinWidth, S2Projections.MaxWidth, S2Projections.AvgWidth);
197 | var edge =
198 | new MetricBundle(S2Projections.MinEdge, S2Projections.MaxEdge, S2Projections.AvgEdge);
199 | var diag =
200 | new MetricBundle(S2Projections.MinDiag, S2Projections.MaxDiag, S2Projections.AvgDiag);
201 | var area =
202 | new MetricBundle(S2Projections.MinArea, S2Projections.MaxArea, S2Projections.AvgArea);
203 |
204 | // First, check that min <= avg <= max for each metric.
205 | testMinMaxAvg(angleSpan);
206 | testMinMaxAvg(width);
207 | testMinMaxAvg(edge);
208 | testMinMaxAvg(diag);
209 | testMinMaxAvg(area);
210 |
211 | // Check that the maximum aspect ratio of an individual cell is consistent
212 | // with the global minimums and maximums.
213 | assertTrue(S2Projections.MaxEdgeAspect >= 1.0);
214 | assertTrue(S2Projections.MaxEdgeAspect
215 | < S2Projections.MaxEdge.Deriv()/S2Projections.MinEdge.Deriv());
216 | assertTrue(S2Projections.MaxDiagAspect >= 1);
217 | assertTrue(S2Projections.MaxDiagAspect
218 | < S2Projections.MaxDiag.Deriv()/S2Projections.MinDiag.Deriv());
219 |
220 | // Check various conditions that are provable mathematically.
221 | testLessOrEqual(width, angleSpan);
222 | testLessOrEqual(width, edge);
223 | testLessOrEqual(edge, diag);
224 |
225 | assertTrue(S2Projections.MinArea.Deriv()
226 | >= S2Projections.MinWidth.Deriv()*S2Projections.MinEdge.Deriv() - 1e-15);
227 | assertTrue(S2Projections.MaxArea.Deriv()
228 | < S2Projections.MaxWidth.Deriv()*S2Projections.MaxEdge.Deriv() + 1e-15);
229 |
230 | // GetMinLevelForLength() and friends have built-in assertions, we just need
231 | // to call these functions to test them.
232 | //
233 | // We don't actually check that the metrics are correct here, e.g. that
234 | // GetMinWidth(10) is a lower bound on the width of cells at level 10.
235 | // It is easier to check these properties in s2cell_unittest, since
236 | // S2Cell has methods to compute the cell vertices, etc.
237 |
238 | for (var level = -2; level <= S2CellId.MaxLevel + 3; ++level)
239 | {
240 | var dWidth = (2*S2Projections.MinWidth.Deriv())*Math.Pow(2, -level);
241 | if (level >= S2CellId.MaxLevel + 3)
242 | {
243 | dWidth = 0;
244 | }
245 |
246 | // Check boundary cases (exactly equal to a threshold value).
247 | var expectedLevel = Math.Max(0, Math.Min(S2CellId.MaxLevel, level));
248 | assertEquals(S2Projections.MinWidth.GetMinLevel(dWidth), expectedLevel);
249 | assertEquals(S2Projections.MinWidth.GetMaxLevel(dWidth), expectedLevel);
250 | assertEquals(S2Projections.MinWidth.GetClosestLevel(dWidth), expectedLevel);
251 |
252 | // Also check non-boundary cases.
253 | assertEquals(S2Projections.MinWidth.GetMinLevel(1.2*dWidth), expectedLevel);
254 | assertEquals(S2Projections.MinWidth.GetMaxLevel(0.8*dWidth), expectedLevel);
255 | assertEquals(S2Projections.MinWidth.GetClosestLevel(1.2*dWidth), expectedLevel);
256 | assertEquals(S2Projections.MinWidth.GetClosestLevel(0.8*dWidth), expectedLevel);
257 |
258 | // Same thing for area1.
259 | var area1 = (4*S2Projections.MinArea.Deriv())*Math.Pow(4, -level);
260 | if (level <= -3)
261 | {
262 | area1 = 0;
263 | }
264 | assertEquals(S2Projections.MinArea.GetMinLevel(area1), expectedLevel);
265 | assertEquals(S2Projections.MinArea.GetMaxLevel(area1), expectedLevel);
266 | assertEquals(S2Projections.MinArea.GetClosestLevel(area1), expectedLevel);
267 | assertEquals(S2Projections.MinArea.GetMinLevel(1.2*area1), expectedLevel);
268 | assertEquals(S2Projections.MinArea.GetMaxLevel(0.8*area1), expectedLevel);
269 | assertEquals(S2Projections.MinArea.GetClosestLevel(1.2*area1), expectedLevel);
270 | assertEquals(S2Projections.MinArea.GetClosestLevel(0.8*area1), expectedLevel);
271 | }
272 | }
273 |
274 | [Test]
275 | public void testSTUV()
276 | {
277 | // Check boundary conditions.
278 | for (double x = -1; x <= 1; ++x)
279 | {
280 | assertEquals(S2Projections.StToUv(x), x);
281 | assertEquals(S2Projections.UvToSt(x), x);
282 | }
283 | // Check that UVtoST and STtoUV are inverses.
284 | for (double x = -1; x <= 1; x += 0.0001)
285 | {
286 | assertDoubleNear(S2Projections.UvToSt(S2Projections.StToUv(x)), x);
287 | assertDoubleNear(S2Projections.StToUv(S2Projections.UvToSt(x)), x);
288 | }
289 | }
290 |
291 | [Test]
292 | public void testTraversalOrder()
293 | {
294 | for (var r = 0; r < 4; ++r)
295 | {
296 | for (var i = 0; i < 4; ++i)
297 | {
298 | // Check consistency with respect to swapping axes.
299 | assertEquals(S2.IjToPos(r, i),
300 | S2.IjToPos(r ^ S2.SwapMask, swapAxes(i)));
301 | assertEquals(S2.PosToIj(r, i),
302 | swapAxes(S2.PosToIj(r ^ S2.SwapMask, i)));
303 |
304 | // Check consistency with respect to reversing axis directions.
305 | assertEquals(S2.IjToPos(r, i),
306 | S2.IjToPos(r ^ S2.InvertMask, invertBits(i)));
307 | assertEquals(S2.PosToIj(r, i),
308 | invertBits(S2.PosToIj(r ^ S2.InvertMask, i)));
309 |
310 | // Check that the two tables are inverses of each other.
311 | assertEquals(S2.IjToPos(r, S2.PosToIj(r, i)), i);
312 | assertEquals(S2.PosToIj(r, S2.IjToPos(r, i)), i);
313 | }
314 | }
315 | }
316 |
317 | [Test]
318 | public void testUVAxes()
319 | {
320 | // Check that axes are consistent with FaceUVtoXYZ.
321 | for (var face = 0; face < 6; ++face)
322 | {
323 | assertEquals(S2Projections.GetUAxis(face),
324 | S2Projections.FaceUvToXyz(face, 1, 0) - S2Projections.FaceUvToXyz(face, 0, 0));
325 | assertEquals(S2Projections.GetVAxis(face),
326 | S2Projections.FaceUvToXyz(face, 0, 1) - S2Projections.FaceUvToXyz(face, 0, 0));
327 | }
328 | }
329 |
330 | [Test]
331 | public void testUVNorms()
332 | {
333 | // Check that GetUNorm and GetVNorm compute right-handed normals for
334 | // an edge in the increasing U or V direction.
335 | for (var face = 0; face < 6; ++face)
336 | {
337 | for (double x = -1; x <= 1; x += 1/1024.0)
338 | {
339 | assertDoubleNear(
340 | S2Point.CrossProd(
341 | S2Projections.FaceUvToXyz(face, x, -1), S2Projections.FaceUvToXyz(face, x, 1))
342 | .Angle(S2Projections.GetUNorm(face, x)), 0);
343 | assertDoubleNear(
344 | S2Point.CrossProd(
345 | S2Projections.FaceUvToXyz(face, -1, x), S2Projections.FaceUvToXyz(face, 1, x))
346 | .Angle(S2Projections.GetVNorm(face, x)), 0);
347 | }
348 | }
349 | }
350 | }
351 | }
--------------------------------------------------------------------------------
/S2Geometry.Tests/S2CellUnionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Google.Common.Geometry;
7 | using NUnit.Framework;
8 |
9 | namespace S2Geometry.Tests
10 | {
11 | public class S2CellUnionTest : GeometryTestCase
12 | {
13 | // Test helper methods for testing the traversal order.
14 | private static int swapAxes(int ij)
15 | {
16 | return ((ij >> 1) & 1) + ((ij & 1) << 1);
17 | }
18 |
19 | private static int invertBits(int ij)
20 | {
21 | return ij ^ 3;
22 | }
23 |
24 | // Note: obviously, I could have defined a bundle of metrics like this in the
25 | // S2 class itself rather than just for testing. However, it's not clear that
26 | // this is useful other than for testing purposes, and I find
27 | // S2.kMinWidth.GetMaxLevel(width) to be slightly more readable than
28 | // than S2.kWidth.Min().GetMaxLevel(width). Also, there is no fundamental
29 | // reason that we need to analyze the minimum, maximum, and average values of
30 | // every metric; it would be perfectly reasonable to just define one of these.
31 |
32 | public class MetricBundle
33 | {
34 | public S2CellMetric avg_;
35 | public S2CellMetric max_;
36 | public S2CellMetric min_;
37 |
38 | public MetricBundle(S2CellMetric Min, S2CellMetric Max, S2CellMetric avg)
39 | {
40 | min_ = Min;
41 | max_ = Max;
42 | avg_ = avg;
43 | }
44 | }
45 |
46 | public void testMinMaxAvg(MetricBundle bundle)
47 | {
48 | assertTrue(bundle.min_.Deriv() < bundle.avg_.Deriv());
49 | assertTrue(bundle.avg_.Deriv() < bundle.max_.Deriv());
50 | }
51 |
52 | public void testLessOrEqual(MetricBundle a, MetricBundle b)
53 | {
54 | assertTrue(a.min_.Deriv() <= b.min_.Deriv());
55 | assertTrue(a.max_.Deriv() <= b.max_.Deriv());
56 | assertTrue(a.avg_.Deriv() <= b.avg_.Deriv());
57 | }
58 |
59 | [Test]
60 | public void testAngleArea()
61 | {
62 | var pz = new S2Point(0, 0, 1);
63 | var p000 = new S2Point(1, 0, 0);
64 | var p045 = new S2Point(1, 1, 0);
65 | var p090 = new S2Point(0, 1, 0);
66 | var p180 = new S2Point(-1, 0, 0);
67 | assertDoubleNear(S2.Angle(p000, pz, p045), S2.PiOver4);
68 | assertDoubleNear(S2.Angle(p045, pz, p180), 3*S2.PiOver4);
69 | assertDoubleNear(S2.Angle(p000, pz, p180), S2.Pi);
70 | assertDoubleNear(S2.Angle(pz, p000, pz), 0);
71 | assertDoubleNear(S2.Angle(pz, p000, p045), S2.PiOver2);
72 |
73 | assertDoubleNear(S2.Area(p000, p090, pz), S2.PiOver2);
74 | assertDoubleNear(S2.Area(p045, pz, p180), 3*S2.PiOver4);
75 |
76 | // Make sure that area() has good *relative* accuracy even for
77 | // very small areas.
78 | var eps = 1e-10;
79 | var pepsx = new S2Point(eps, 0, 1);
80 | var pepsy = new S2Point(0, eps, 1);
81 | var expected1 = 0.5*eps*eps;
82 | assertDoubleNear(S2.Area(pepsx, pepsy, pz), expected1, 1e-14*expected1);
83 |
84 | // Make sure that it can handle degenerate triangles.
85 | var pr = new S2Point(0.257, -0.5723, 0.112);
86 | var pq = new S2Point(-0.747, 0.401, 0.2235);
87 | assertEquals(S2.Area(pr, pr, pr), 0.0);
88 | // TODO: The following test is not exact in optimized mode because the
89 | // compiler chooses to mix 64-bit and 80-bit intermediate results.
90 | assertDoubleNear(S2.Area(pr, pq, pr), 0);
91 | assertEquals(S2.Area(p000, p045, p090), 0.0);
92 |
93 | double maxGirard = 0;
94 | for (var i = 0; i < 10000; ++i)
95 | {
96 | var p0 = randomPoint();
97 | var d1 = randomPoint();
98 | var d2 = randomPoint();
99 | var p1 = p0 + (d1 * 1e-15);
100 | var p2 = p0 + (d2 * 1e-15);
101 | // The actual displacement can be as much as 1.2e-15 due to roundoff.
102 | // This yields a maximum triangle area of about 0.7e-30.
103 | assertTrue(S2.Area(p0, p1, p2) < 0.7e-30);
104 | maxGirard = Math.Max(maxGirard, S2.GirardArea(p0, p1, p2));
105 | }
106 | Console.WriteLine("Worst case Girard for triangle area 1e-30: " + maxGirard);
107 |
108 | // Try a very long and skinny triangle.
109 | var p045eps = new S2Point(1, 1, eps);
110 | var expected2 = 5.8578643762690495119753e-11; // Mathematica.
111 | assertDoubleNear(S2.Area(p000, p045eps, p090), expected2, 1e-9*expected2);
112 |
113 | // Triangles with near-180 degree edges that sum to a quarter-sphere.
114 | var eps2 = 1e-10;
115 | var p000eps2 = new S2Point(1, 0.1*eps2, eps2);
116 | var quarterArea1 =
117 | S2.Area(p000eps2, p000, p090) + S2.Area(p000eps2, p090, p180) + S2.Area(p000eps2, p180, pz)
118 | + S2.Area(p000eps2, pz, p000);
119 | assertDoubleNear(quarterArea1, S2.Pi);
120 |
121 | // Four other triangles that sum to a quarter-sphere.
122 | var p045eps2 = new S2Point(1, 1, eps2);
123 | var quarterArea2 =
124 | S2.Area(p045eps2, p000, p090) + S2.Area(p045eps2, p090, p180) + S2.Area(p045eps2, p180, pz)
125 | + S2.Area(p045eps2, pz, p000);
126 | assertDoubleNear(quarterArea2, S2.Pi);
127 | }
128 |
129 | [Test]
130 | public void testCCW()
131 | {
132 | var a = new S2Point(0.72571927877036835, 0.46058825605889098, 0.51106749730504852);
133 | var b = new S2Point(0.7257192746638208, 0.46058826573818168, 0.51106749441312738);
134 | var c = new S2Point(0.72571927671709457, 0.46058826089853633, 0.51106749585908795);
135 | assertTrue(S2.RobustCcw(a, b, c) != 0);
136 | }
137 |
138 | [Test]
139 | public void testExp()
140 | {
141 | for (var i = 0; i < 10; ++i)
142 | {
143 | assertEquals(i + 1, S2.Exp(Math.Pow(2, i)));
144 | }
145 |
146 | for (var i = 0; i < 10; ++i)
147 | {
148 | assertEquals(i + 1, S2.Exp(-Math.Pow(2, i)));
149 | }
150 |
151 | assertEquals(0, S2.Exp(0));
152 | assertEquals(2, S2.Exp(3));
153 | assertEquals(3, S2.Exp(5));
154 | }
155 |
156 | [Test]
157 | public void testFaceUVtoXYZ()
158 | {
159 | // Check that each face appears exactly once.
160 | var sum = new S2Point();
161 | for (var face = 0; face < 6; ++face)
162 | {
163 | var center = S2Projections.FaceUvToXyz(face, 0, 0);
164 | assertEquals(S2Projections.GetNorm(face), center);
165 | assertEquals(Math.Abs(center[center.LargestAbsComponent]), 1.0);
166 | sum = sum + S2Point.Fabs(center);
167 | }
168 | assertEquals(sum, new S2Point(2, 2, 2));
169 |
170 | // Check that each face has a right-handed coordinate system.
171 | for (var face = 0; face < 6; ++face)
172 | {
173 | assertEquals(
174 | S2Point.CrossProd(S2Projections.GetUAxis(face), S2Projections.GetVAxis(face)).DotProd(
175 | S2Projections.FaceUvToXyz(face, 0, 0)), 1.0);
176 | }
177 |
178 | // Check that the Hilbert curves on each face combine to form a
179 | // continuous curve over the entire cube.
180 | for (var face = 0; face < 6; ++face)
181 | {
182 | // The Hilbert curve on each face starts at (-1,-1) and terminates
183 | // at either (1,-1) (if axes not swapped) or (-1,1) (if swapped).
184 | var sign = ((face & S2.SwapMask) != 0) ? -1 : 1;
185 | assertEquals(S2Projections.FaceUvToXyz(face, sign, -sign),
186 | S2Projections.FaceUvToXyz((face + 1)%6, -1, -1));
187 | }
188 | }
189 |
190 | [Test]
191 | public void testMetrics()
192 | {
193 | var angleSpan = new MetricBundle(
194 | S2Projections.MinAngleSpan, S2Projections.MaxAngleSpan, S2Projections.AvgAngleSpan);
195 | var width =
196 | new MetricBundle(S2Projections.MinWidth, S2Projections.MaxWidth, S2Projections.AvgWidth);
197 | var edge =
198 | new MetricBundle(S2Projections.MinEdge, S2Projections.MaxEdge, S2Projections.AvgEdge);
199 | var diag =
200 | new MetricBundle(S2Projections.MinDiag, S2Projections.MaxDiag, S2Projections.AvgDiag);
201 | var area =
202 | new MetricBundle(S2Projections.MinArea, S2Projections.MaxArea, S2Projections.AvgArea);
203 |
204 | // First, check that Min <= avg <= Max for each metric.
205 | testMinMaxAvg(angleSpan);
206 | testMinMaxAvg(width);
207 | testMinMaxAvg(edge);
208 | testMinMaxAvg(diag);
209 | testMinMaxAvg(area);
210 |
211 | // Check that the maximum aspect ratio of an individual cell is consistent
212 | // with the global minimums and maximums.
213 | assertTrue(S2Projections.MaxEdgeAspect >= 1.0);
214 | assertTrue(S2Projections.MaxEdgeAspect
215 | < S2Projections.MaxEdge.Deriv()/S2Projections.MinEdge.Deriv());
216 | assertTrue(S2Projections.MaxDiagAspect >= 1);
217 | assertTrue(S2Projections.MaxDiagAspect
218 | < S2Projections.MaxDiag.Deriv()/S2Projections.MinDiag.Deriv());
219 |
220 | // Check various conditions that are provable mathematically.
221 | testLessOrEqual(width, angleSpan);
222 | testLessOrEqual(width, edge);
223 | testLessOrEqual(edge, diag);
224 |
225 | assertTrue(S2Projections.MinArea.Deriv()
226 | >= S2Projections.MinWidth.Deriv()*S2Projections.MinEdge.Deriv() - 1e-15);
227 | assertTrue(S2Projections.MaxArea.Deriv()
228 | < S2Projections.MaxWidth.Deriv()*S2Projections.MaxEdge.Deriv() + 1e-15);
229 |
230 | // GetMinLevelForLength() and friends have built-in assertions, we just need
231 | // to call these functions to test them.
232 | //
233 | // We don't actually check that the metrics are correct here, e.g. that
234 | // GetMinWidth(10) is a lower bound on the width of cells at level 10.
235 | // It is easier to check these properties in s2cell_unittest, since
236 | // S2Cell has methods to compute the cell vertices, etc.
237 |
238 | for (var level = -2; level <= S2CellId.MaxLevel + 3; ++level)
239 | {
240 | var dWidth = (2*S2Projections.MinWidth.Deriv())*Math.Pow(2, -level);
241 | if (level >= S2CellId.MaxLevel + 3)
242 | {
243 | dWidth = 0;
244 | }
245 |
246 | // Check boundary cases (exactly equal to a threshold value).
247 | var expectedLevel = Math.Max(0, Math.Min(S2CellId.MaxLevel, level));
248 | assertEquals(S2Projections.MinWidth.GetMinLevel(dWidth), expectedLevel);
249 | assertEquals(S2Projections.MinWidth.GetMaxLevel(dWidth), expectedLevel);
250 | assertEquals(S2Projections.MinWidth.GetClosestLevel(dWidth), expectedLevel);
251 |
252 | // Also check non-boundary cases.
253 | assertEquals(S2Projections.MinWidth.GetMinLevel(1.2*dWidth), expectedLevel);
254 | assertEquals(S2Projections.MinWidth.GetMaxLevel(0.8*dWidth), expectedLevel);
255 | assertEquals(S2Projections.MinWidth.GetClosestLevel(1.2*dWidth), expectedLevel);
256 | assertEquals(S2Projections.MinWidth.GetClosestLevel(0.8*dWidth), expectedLevel);
257 |
258 | // Same thing for area1.
259 | var area1 = (4*S2Projections.MinArea.Deriv())*Math.Pow(4, -level);
260 | if (level <= -3)
261 | {
262 | area1 = 0;
263 | }
264 | assertEquals(S2Projections.MinArea.GetMinLevel(area1), expectedLevel);
265 | assertEquals(S2Projections.MinArea.GetMaxLevel(area1), expectedLevel);
266 | assertEquals(S2Projections.MinArea.GetClosestLevel(area1), expectedLevel);
267 | assertEquals(S2Projections.MinArea.GetMinLevel(1.2*area1), expectedLevel);
268 | assertEquals(S2Projections.MinArea.GetMaxLevel(0.8*area1), expectedLevel);
269 | assertEquals(S2Projections.MinArea.GetClosestLevel(1.2*area1), expectedLevel);
270 | assertEquals(S2Projections.MinArea.GetClosestLevel(0.8*area1), expectedLevel);
271 | }
272 | }
273 |
274 | [Test]
275 | public void testSTUV()
276 | {
277 | // Check boundary conditions.
278 | for (double x = -1; x <= 1; ++x)
279 | {
280 | assertEquals(S2Projections.StToUv(x), x);
281 | assertEquals(S2Projections.UvToSt(x), x);
282 | }
283 | // Check that UVtoST and STtoUV are inverses.
284 | for (double x = -1; x <= 1; x += 0.0001)
285 | {
286 | assertDoubleNear(S2Projections.UvToSt(S2Projections.StToUv(x)), x);
287 | assertDoubleNear(S2Projections.StToUv(S2Projections.UvToSt(x)), x);
288 | }
289 | }
290 |
291 | [Test]
292 | public void testTraversalOrder()
293 | {
294 | for (var r = 0; r < 4; ++r)
295 | {
296 | for (var i = 0; i < 4; ++i)
297 | {
298 | // Check consistency with respect to swapping axes.
299 | assertEquals(S2.IjToPos(r, i),
300 | S2.IjToPos(r ^ S2.SwapMask, swapAxes(i)));
301 | assertEquals(S2.PosToIj(r, i),
302 | swapAxes(S2.PosToIj(r ^ S2.SwapMask, i)));
303 |
304 | // Check consistency with respect to reversing axis directions.
305 | assertEquals(S2.IjToPos(r, i),
306 | S2.IjToPos(r ^ S2.InvertMask, invertBits(i)));
307 | assertEquals(S2.PosToIj(r, i),
308 | invertBits(S2.PosToIj(r ^ S2.InvertMask, i)));
309 |
310 | // Check that the two tables are inverses of each other.
311 | assertEquals(S2.IjToPos(r, S2.PosToIj(r, i)), i);
312 | assertEquals(S2.PosToIj(r, S2.IjToPos(r, i)), i);
313 | }
314 | }
315 | }
316 |
317 | [Test]
318 | public void testUVAxes()
319 | {
320 | // Check that axes are consistent with FaceUVtoXYZ.
321 | for (var face = 0; face < 6; ++face)
322 | {
323 | assertEquals(S2Projections.GetUAxis(face),
324 | S2Projections.FaceUvToXyz(face, 1, 0) - S2Projections.FaceUvToXyz(face, 0, 0));
325 | assertEquals(S2Projections.GetVAxis(face),
326 | S2Projections.FaceUvToXyz(face, 0, 1) - S2Projections.FaceUvToXyz(face, 0, 0));
327 | }
328 | }
329 |
330 | [Test]
331 | public void testUVNorms()
332 | {
333 | // Check that GetUNorm and GetVNorm compute right-handed normals for
334 | // an edge in the increasing U or V direction.
335 | for (var face = 0; face < 6; ++face)
336 | {
337 | for (double x = -1; x <= 1; x += 1/1024.0)
338 | {
339 | assertDoubleNear(
340 | S2Point.CrossProd(
341 | S2Projections.FaceUvToXyz(face, x, -1), S2Projections.FaceUvToXyz(face, x, 1))
342 | .Angle(S2Projections.GetUNorm(face, x)), 0);
343 | assertDoubleNear(
344 | S2Point.CrossProd(
345 | S2Projections.FaceUvToXyz(face, -1, x), S2Projections.FaceUvToXyz(face, 1, x))
346 | .Angle(S2Projections.GetVNorm(face, x)), 0);
347 | }
348 | }
349 | }
350 | }
351 | }
--------------------------------------------------------------------------------