View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.spherical.twod;
23  
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.function.IntPredicate;
29  
30  import org.hipparchus.exception.LocalizedCoreFormats;
31  import org.hipparchus.exception.MathIllegalArgumentException;
32  import org.hipparchus.exception.MathIllegalStateException;
33  import org.hipparchus.exception.MathRuntimeException;
34  import org.hipparchus.geometry.LocalizedGeometryFormats;
35  import org.hipparchus.geometry.enclosing.EnclosingBall;
36  import org.hipparchus.geometry.euclidean.threed.Rotation;
37  import org.hipparchus.geometry.euclidean.threed.Vector3D;
38  import org.hipparchus.geometry.partitioning.Region;
39  import org.hipparchus.geometry.partitioning.Region.Location;
40  import org.hipparchus.geometry.partitioning.RegionFactory;
41  import org.hipparchus.geometry.partitioning.SubHyperplane;
42  import org.hipparchus.geometry.spherical.oned.ArcsSet;
43  import org.hipparchus.geometry.spherical.oned.Sphere1D;
44  import org.hipparchus.random.UnitSphereRandomVectorGenerator;
45  import org.hipparchus.random.Well1024a;
46  import org.hipparchus.util.FastMath;
47  import org.hipparchus.util.MathUtils;
48  import org.junit.Assert;
49  import org.junit.Test;
50  
51  public class SphericalPolygonsSetTest {
52  
53      @Test
54      public void testFullSphere() {
55          SphericalPolygonsSet full = new SphericalPolygonsSet(1.0e-10);
56          UnitSphereRandomVectorGenerator random =
57                  new UnitSphereRandomVectorGenerator(3, new Well1024a(0x852fd2a0ed8d2f6dl));
58          for (int i = 0; i < 1000; ++i) {
59              Vector3D v = new Vector3D(random.nextVector());
60              Assert.assertEquals(Location.INSIDE, full.checkPoint(new S2Point(v)));
61          }
62          Assert.assertEquals(4 * FastMath.PI, new SphericalPolygonsSet(0.01, new S2Point[0]).getSize(), 1.0e-10);
63          Assert.assertEquals(0, new SphericalPolygonsSet(0.01, new S2Point[0]).getBoundarySize(), 1.0e-10);
64          Assert.assertEquals(0, full.getBoundaryLoops().size());
65          Assert.assertTrue(full.getEnclosingCap().getRadius() > 0);
66          Assert.assertTrue(Double.isInfinite(full.getEnclosingCap().getRadius()));
67      }
68  
69      @Test
70      public void testEmpty() {
71          SphericalPolygonsSet empty =
72              (SphericalPolygonsSet) new RegionFactory<Sphere2D>().getComplement(new SphericalPolygonsSet(1.0e-10));
73          UnitSphereRandomVectorGenerator random =
74                  new UnitSphereRandomVectorGenerator(3, new Well1024a(0x76d9205d6167b6ddl));
75          for (int i = 0; i < 1000; ++i) {
76              Vector3D v = new Vector3D(random.nextVector());
77              Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(new S2Point(v)));
78          }
79          Assert.assertEquals(0, empty.getSize(), 1.0e-10);
80          Assert.assertEquals(0, empty.getBoundarySize(), 1.0e-10);
81          Assert.assertEquals(0, empty.getBoundaryLoops().size());
82          Assert.assertTrue(empty.getEnclosingCap().getRadius() < 0);
83          Assert.assertTrue(Double.isInfinite(empty.getEnclosingCap().getRadius()));
84      }
85  
86      @Test
87      public void testSouthHemisphere() {
88          double tol = 0.01;
89          double sinTol = FastMath.sin(tol);
90          SphericalPolygonsSet south = new SphericalPolygonsSet(Vector3D.MINUS_K, tol);
91          UnitSphereRandomVectorGenerator random =
92                  new UnitSphereRandomVectorGenerator(3, new Well1024a(0x6b9d4a6ad90d7b0bl));
93          for (int i = 0; i < 1000; ++i) {
94              Vector3D v = new Vector3D(random.nextVector());
95              if (v.getZ() < -sinTol) {
96                  Assert.assertEquals(Location.INSIDE, south.checkPoint(new S2Point(v)));
97              } else if (v.getZ() > sinTol) {
98                  Assert.assertEquals(Location.OUTSIDE, south.checkPoint(new S2Point(v)));
99              } else {
100                 Assert.assertEquals(Location.BOUNDARY, south.checkPoint(new S2Point(v)));
101             }
102         }
103         Assert.assertEquals(1, south.getBoundaryLoops().size());
104 
105         EnclosingBall<Sphere2D, S2Point> southCap = south.getEnclosingCap();
106         Assert.assertEquals(0.0, S2Point.MINUS_K.distance(southCap.getCenter()), 1.0e-10);
107         Assert.assertEquals(0.5 * FastMath.PI, southCap.getRadius(), 1.0e-10);
108 
109         EnclosingBall<Sphere2D, S2Point> northCap =
110                 ((SphericalPolygonsSet) new RegionFactory<Sphere2D>().getComplement(south)).getEnclosingCap();
111         Assert.assertEquals(0.0, S2Point.PLUS_K.distance(northCap.getCenter()), 1.0e-10);
112         Assert.assertEquals(0.5 * FastMath.PI, northCap.getRadius(), 1.0e-10);
113 
114     }
115 
116     @Test
117     public void testPositiveOctantByIntersection() {
118         double tol = 0.01;
119         double sinTol = FastMath.sin(tol);
120         RegionFactory<Sphere2D> factory = new RegionFactory<Sphere2D>();
121         SphericalPolygonsSet plusX = new SphericalPolygonsSet(Vector3D.PLUS_I, tol);
122         SphericalPolygonsSet plusY = new SphericalPolygonsSet(Vector3D.PLUS_J, tol);
123         SphericalPolygonsSet plusZ = new SphericalPolygonsSet(Vector3D.PLUS_K, tol);
124         SphericalPolygonsSet octant =
125                 (SphericalPolygonsSet) factory.intersection(factory.intersection(plusX, plusY), plusZ);
126         UnitSphereRandomVectorGenerator random =
127                 new UnitSphereRandomVectorGenerator(3, new Well1024a(0x9c9802fde3cbcf25l));
128         for (int i = 0; i < 1000; ++i) {
129             Vector3D v = new Vector3D(random.nextVector());
130             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
131                 Assert.assertEquals(Location.INSIDE, octant.checkPoint(new S2Point(v)));
132             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
133                 Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(new S2Point(v)));
134             } else {
135                 Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(new S2Point(v)));
136             }
137         }
138 
139         List<Vertex> loops = octant.getBoundaryLoops();
140         Assert.assertEquals(1, loops.size());
141         boolean xPFound = false;
142         boolean yPFound = false;
143         boolean zPFound = false;
144         boolean xVFound = false;
145         boolean yVFound = false;
146         boolean zVFound = false;
147         Vertex first = loops.get(0);
148         int count = 0;
149         for (Vertex v = first; count == 0 || v != first; v = v.getOutgoing().getEnd()) {
150             ++count;
151             Edge e = v.getIncoming();
152             Assert.assertTrue(v == e.getStart().getOutgoing().getEnd());
153             xPFound = xPFound || e.getCircle().getPole().distance(Vector3D.PLUS_I) < 1.0e-10;
154             yPFound = yPFound || e.getCircle().getPole().distance(Vector3D.PLUS_J) < 1.0e-10;
155             zPFound = zPFound || e.getCircle().getPole().distance(Vector3D.PLUS_K) < 1.0e-10;
156             Assert.assertEquals(0.5 * FastMath.PI, e.getLength(), 1.0e-10);
157             xVFound = xVFound || v.getLocation().getVector().distance(Vector3D.PLUS_I) < 1.0e-10;
158             yVFound = yVFound || v.getLocation().getVector().distance(Vector3D.PLUS_J) < 1.0e-10;
159             zVFound = zVFound || v.getLocation().getVector().distance(Vector3D.PLUS_K) < 1.0e-10;
160         }
161         Assert.assertTrue(xPFound);
162         Assert.assertTrue(yPFound);
163         Assert.assertTrue(zPFound);
164         Assert.assertTrue(xVFound);
165         Assert.assertTrue(yVFound);
166         Assert.assertTrue(zVFound);
167         Assert.assertEquals(3, count);
168 
169         Assert.assertEquals(0.0,
170                             ((S2Point) octant.getBarycenter()).distance(new S2Point(new Vector3D(1, 1, 1))),
171                             1.0e-10);
172         Assert.assertEquals(0.5 * FastMath.PI, octant.getSize(), 1.0e-10);
173 
174         EnclosingBall<Sphere2D, S2Point> cap = octant.getEnclosingCap();
175         Assert.assertEquals(0.0, octant.getBarycenter().distance(cap.getCenter()), 1.0e-10);
176         Assert.assertEquals(FastMath.acos(1.0 / FastMath.sqrt(3)), cap.getRadius(), 1.0e-10);
177 
178         EnclosingBall<Sphere2D, S2Point> reversedCap =
179                 ((SphericalPolygonsSet) factory.getComplement(octant)).getEnclosingCap();
180         Assert.assertEquals(0, reversedCap.getCenter().distance(new S2Point(new Vector3D(-1, -1, -1))), 1.0e-10);
181         Assert.assertEquals(FastMath.PI - FastMath.asin(1.0 / FastMath.sqrt(3)), reversedCap.getRadius(), 1.0e-10);
182 
183     }
184 
185     @Test
186     public void testPositiveOctantByVertices() {
187         double tol = 0.01;
188         double sinTol = FastMath.sin(tol);
189         SphericalPolygonsSet octant = new SphericalPolygonsSet(tol, S2Point.PLUS_I, S2Point.PLUS_J, S2Point.PLUS_K);
190         UnitSphereRandomVectorGenerator random =
191                 new UnitSphereRandomVectorGenerator(3, new Well1024a(0xb8fc5acc91044308l));
192         for (int i = 0; i < 1000; ++i) {
193             Vector3D v = new Vector3D(random.nextVector());
194             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
195                 Assert.assertEquals(Location.INSIDE, octant.checkPoint(new S2Point(v)));
196             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
197                 Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(new S2Point(v)));
198             } else {
199                 Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(new S2Point(v)));
200             }
201         }
202     }
203 
204     @Test
205     public void testNonConvex() {
206         double tol = 0.01;
207         double sinTol = FastMath.sin(tol);
208         RegionFactory<Sphere2D> factory = new RegionFactory<Sphere2D>();
209         SphericalPolygonsSet plusX = new SphericalPolygonsSet(Vector3D.PLUS_I, tol);
210         SphericalPolygonsSet plusY = new SphericalPolygonsSet(Vector3D.PLUS_J, tol);
211         SphericalPolygonsSet plusZ = new SphericalPolygonsSet(Vector3D.PLUS_K, tol);
212         SphericalPolygonsSet threeOctants =
213                 (SphericalPolygonsSet) factory.difference(plusZ, factory.intersection(plusX, plusY));
214 
215         UnitSphereRandomVectorGenerator random =
216                 new UnitSphereRandomVectorGenerator(3, new Well1024a(0x9c9802fde3cbcf25l));
217         for (int i = 0; i < 1000; ++i) {
218             Vector3D v = new Vector3D(random.nextVector());
219             if (((v.getX() < -sinTol) || (v.getY() < -sinTol)) && (v.getZ() > sinTol)) {
220                 Assert.assertEquals(Location.INSIDE, threeOctants.checkPoint(new S2Point(v)));
221             } else if (((v.getX() > sinTol) && (v.getY() > sinTol)) || (v.getZ() < -sinTol)) {
222                 Assert.assertEquals(Location.OUTSIDE, threeOctants.checkPoint(new S2Point(v)));
223             } else {
224                 Assert.assertEquals(Location.BOUNDARY, threeOctants.checkPoint(new S2Point(v)));
225             }
226         }
227 
228         List<Vertex> loops = threeOctants.getBoundaryLoops();
229         Assert.assertEquals(1, loops.size());
230         boolean xPFound = false;
231         boolean yPFound = false;
232         boolean zPFound = false;
233         boolean xVFound = false;
234         boolean yVFound = false;
235         boolean zVFound = false;
236         Vertex first = loops.get(0);
237         int count = 0;
238         double sumPoleX = 0;
239         double sumPoleY = 0;
240         double sumPoleZ = 0;
241         for (Vertex v = first; count == 0 || v != first; v = v.getOutgoing().getEnd()) {
242             ++count;
243             Edge e = v.getIncoming();
244             Assert.assertTrue(v == e.getStart().getOutgoing().getEnd());
245             if (e.getCircle().getPole().distance(Vector3D.MINUS_I) < 1.0e-10) {
246                 xPFound = true;
247                 sumPoleX += e.getLength();
248             } else if (e.getCircle().getPole().distance(Vector3D.MINUS_J) < 1.0e-10) {
249                 yPFound = true;
250                 sumPoleY += e.getLength();
251             } else {
252                 Assert.assertEquals(0.0, e.getCircle().getPole().distance(Vector3D.PLUS_K), 1.0e-10);
253                 zPFound = true;
254                 sumPoleZ += e.getLength();
255             }
256             xVFound = xVFound || v.getLocation().getVector().distance(Vector3D.PLUS_I) < 1.0e-10;
257             yVFound = yVFound || v.getLocation().getVector().distance(Vector3D.PLUS_J) < 1.0e-10;
258             zVFound = zVFound || v.getLocation().getVector().distance(Vector3D.PLUS_K) < 1.0e-10;
259         }
260         Assert.assertTrue(xPFound);
261         Assert.assertTrue(yPFound);
262         Assert.assertTrue(zPFound);
263         Assert.assertTrue(xVFound);
264         Assert.assertTrue(yVFound);
265         Assert.assertTrue(zVFound);
266         Assert.assertEquals(0.5 * FastMath.PI, sumPoleX, 1.0e-10);
267         Assert.assertEquals(0.5 * FastMath.PI, sumPoleY, 1.0e-10);
268         Assert.assertEquals(1.5 * FastMath.PI, sumPoleZ, 1.0e-10);
269 
270         Assert.assertEquals(1.5 * FastMath.PI, threeOctants.getSize(), 1.0e-10);
271 
272     }
273 
274     @Test
275     public void testModeratlyComplexShape() {
276         double tol = 0.01;
277         List<SubHyperplane<Sphere2D>> boundary = new ArrayList<SubHyperplane<Sphere2D>>();
278         boundary.add(create(Vector3D.MINUS_J, Vector3D.PLUS_I,  Vector3D.PLUS_K,  tol, 0.0, 0.5 * FastMath.PI));
279         boundary.add(create(Vector3D.MINUS_I, Vector3D.PLUS_K,  Vector3D.PLUS_J,  tol, 0.0, 0.5 * FastMath.PI));
280         boundary.add(create(Vector3D.PLUS_K,  Vector3D.PLUS_J,  Vector3D.MINUS_I, tol, 0.0, 0.5 * FastMath.PI));
281         boundary.add(create(Vector3D.MINUS_J, Vector3D.MINUS_I, Vector3D.MINUS_K, tol, 0.0, 0.5 * FastMath.PI));
282         boundary.add(create(Vector3D.MINUS_I, Vector3D.MINUS_K, Vector3D.MINUS_J, tol, 0.0, 0.5 * FastMath.PI));
283         boundary.add(create(Vector3D.PLUS_K,  Vector3D.MINUS_J, Vector3D.PLUS_I,  tol, 0.0, 0.5 * FastMath.PI));
284         SphericalPolygonsSet polygon = new SphericalPolygonsSet(boundary, tol);
285 
286         Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1,  1,  1).normalize())));
287         Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D(-1,  1,  1).normalize())));
288         Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D(-1, -1,  1).normalize())));
289         Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D( 1, -1,  1).normalize())));
290         Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1,  1, -1).normalize())));
291         Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D(-1,  1, -1).normalize())));
292         Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D(-1, -1, -1).normalize())));
293         Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1, -1, -1).normalize())));
294 
295         Assert.assertEquals(MathUtils.TWO_PI, polygon.getSize(), 1.0e-10);
296         Assert.assertEquals(3 * FastMath.PI, polygon.getBoundarySize(), 1.0e-10);
297 
298         List<Vertex> loops = polygon.getBoundaryLoops();
299         Assert.assertEquals(1, loops.size());
300         boolean pXFound = false;
301         boolean mXFound = false;
302         boolean pYFound = false;
303         boolean mYFound = false;
304         boolean pZFound = false;
305         boolean mZFound = false;
306         Vertex first = loops.get(0);
307         int count = 0;
308         for (Vertex v = first; count == 0 || v != first; v = v.getOutgoing().getEnd()) {
309             ++count;
310             Edge e = v.getIncoming();
311             Assert.assertTrue(v == e.getStart().getOutgoing().getEnd());
312             pXFound = pXFound || v.getLocation().getVector().distance(Vector3D.PLUS_I)  < 1.0e-10;
313             mXFound = mXFound || v.getLocation().getVector().distance(Vector3D.MINUS_I) < 1.0e-10;
314             pYFound = pYFound || v.getLocation().getVector().distance(Vector3D.PLUS_J)  < 1.0e-10;
315             mYFound = mYFound || v.getLocation().getVector().distance(Vector3D.MINUS_J) < 1.0e-10;
316             pZFound = pZFound || v.getLocation().getVector().distance(Vector3D.PLUS_K)  < 1.0e-10;
317             mZFound = mZFound || v.getLocation().getVector().distance(Vector3D.MINUS_K) < 1.0e-10;
318             Assert.assertEquals(0.5 * FastMath.PI, e.getLength(), 1.0e-10);
319         }
320         Assert.assertTrue(pXFound);
321         Assert.assertTrue(mXFound);
322         Assert.assertTrue(pYFound);
323         Assert.assertTrue(mYFound);
324         Assert.assertTrue(pZFound);
325         Assert.assertTrue(mZFound);
326         Assert.assertEquals(6, count);
327 
328     }
329 
330     @Test
331     public void testSeveralParts() {
332         double tol = 0.01;
333         double sinTol = FastMath.sin(tol);
334         List<SubHyperplane<Sphere2D>> boundary = new ArrayList<SubHyperplane<Sphere2D>>();
335 
336         // first part: +X, +Y, +Z octant
337         boundary.add(create(Vector3D.PLUS_J,  Vector3D.PLUS_K,  Vector3D.PLUS_I,  tol, 0.0, 0.5 * FastMath.PI));
338         boundary.add(create(Vector3D.PLUS_K,  Vector3D.PLUS_I,  Vector3D.PLUS_J,  tol, 0.0, 0.5 * FastMath.PI));
339         boundary.add(create(Vector3D.PLUS_I,  Vector3D.PLUS_J,  Vector3D.PLUS_K,  tol, 0.0, 0.5 * FastMath.PI));
340 
341         // first part: -X, -Y, -Z octant
342         boundary.add(create(Vector3D.MINUS_J, Vector3D.MINUS_I, Vector3D.MINUS_K, tol, 0.0, 0.5 * FastMath.PI));
343         boundary.add(create(Vector3D.MINUS_I, Vector3D.MINUS_K, Vector3D.MINUS_J, tol, 0.0, 0.5 * FastMath.PI));
344         boundary.add(create(Vector3D.MINUS_K, Vector3D.MINUS_J, Vector3D.MINUS_I,  tol, 0.0, 0.5 * FastMath.PI));
345 
346         SphericalPolygonsSet polygon = new SphericalPolygonsSet(boundary, tol);
347 
348         UnitSphereRandomVectorGenerator random =
349                 new UnitSphereRandomVectorGenerator(3, new Well1024a(0xcc5ce49949e0d3ecl));
350         for (int i = 0; i < 1000; ++i) {
351             Vector3D v = new Vector3D(random.nextVector());
352             if ((v.getX() < -sinTol) && (v.getY() < -sinTol) && (v.getZ() < -sinTol)) {
353                 Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(v)));
354             } else if ((v.getX() < sinTol) && (v.getY() < sinTol) && (v.getZ() < sinTol)) {
355                 Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(new S2Point(v)));
356             } else if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
357                 Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(v)));
358             } else if ((v.getX() > -sinTol) && (v.getY() > -sinTol) && (v.getZ() > -sinTol)) {
359                 Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(new S2Point(v)));
360             } else {
361                 Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(v)));
362             }
363         }
364 
365         Assert.assertEquals(FastMath.PI, polygon.getSize(), 1.0e-10);
366         Assert.assertEquals(3 * FastMath.PI, polygon.getBoundarySize(), 1.0e-10);
367 
368         // there should be two separate boundary loops
369         Assert.assertEquals(2, polygon.getBoundaryLoops().size());
370 
371     }
372 
373     @Test
374     public void testPartWithHole() {
375         double tol = 0.01;
376         double alpha = 0.7;
377         S2Point center = new S2Point(new Vector3D(1, 1, 1));
378         SphericalPolygonsSet hexa = new SphericalPolygonsSet(center.getVector(), Vector3D.PLUS_K, alpha, 6, tol);
379         SphericalPolygonsSet hole  = new SphericalPolygonsSet(tol,
380                                                               new S2Point(FastMath.PI / 6, FastMath.PI / 3),
381                                                               new S2Point(FastMath.PI / 3, FastMath.PI / 3),
382                                                               new S2Point(FastMath.PI / 4, FastMath.PI / 6));
383         SphericalPolygonsSet hexaWithHole =
384                 (SphericalPolygonsSet) new RegionFactory<Sphere2D>().difference(hexa, hole);
385 
386         for (double phi = center.getPhi() - alpha + 0.1; phi < center.getPhi() + alpha - 0.1; phi += 0.07) {
387             Location l = hexaWithHole.checkPoint(new S2Point(FastMath.PI / 4, phi));
388             if (phi < FastMath.PI / 6 || phi > FastMath.PI / 3) {
389                 Assert.assertEquals(Location.INSIDE,  l);
390             } else {
391                 Assert.assertEquals(Location.OUTSIDE, l);
392             }
393         }
394 
395         // there should be two separate boundary loops
396         Assert.assertEquals(2, hexaWithHole.getBoundaryLoops().size());
397 
398         Assert.assertEquals(hexa.getBoundarySize() + hole.getBoundarySize(), hexaWithHole.getBoundarySize(), 1.0e-10);
399         Assert.assertEquals(hexa.getSize() - hole.getSize(), hexaWithHole.getSize(), 1.0e-10);
400 
401     }
402 
403     @Test
404     public void testConcentricSubParts() {
405         double tol = 0.001;
406         Vector3D center = new Vector3D(1, 1, 1);
407         SphericalPolygonsSet hexaOut   = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.9,  6, tol);
408         SphericalPolygonsSet hexaIn    = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.8,  6, tol);
409         SphericalPolygonsSet pentaOut  = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.7,  5, tol);
410         SphericalPolygonsSet pentaIn   = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.6,  5, tol);
411         SphericalPolygonsSet quadriOut = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.5,  4, tol);
412         SphericalPolygonsSet quadriIn  = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.4,  4, tol);
413         SphericalPolygonsSet triOut    = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.25, 3, tol);
414         SphericalPolygonsSet triIn     = new SphericalPolygonsSet(center, Vector3D.PLUS_K, 0.15, 3, tol);
415 
416         RegionFactory<Sphere2D> factory = new RegionFactory<Sphere2D>();
417         SphericalPolygonsSet hexa   = (SphericalPolygonsSet) factory.difference(hexaOut,   hexaIn);
418         SphericalPolygonsSet penta  = (SphericalPolygonsSet) factory.difference(pentaOut,  pentaIn);
419         SphericalPolygonsSet quadri = (SphericalPolygonsSet) factory.difference(quadriOut, quadriIn);
420         SphericalPolygonsSet tri    = (SphericalPolygonsSet) factory.difference(triOut,    triIn);
421         SphericalPolygonsSet concentric =
422                 (SphericalPolygonsSet) factory.union(factory.union(hexa, penta), factory.union(quadri, tri));
423 
424         // there should be two separate boundary loops
425         Assert.assertEquals(8, concentric.getBoundaryLoops().size());
426 
427         Assert.assertEquals(hexaOut.getBoundarySize()   + hexaIn.getBoundarySize()   +
428                             pentaOut.getBoundarySize()  + pentaIn.getBoundarySize()  +
429                             quadriOut.getBoundarySize() + quadriIn.getBoundarySize() +
430                             triOut.getBoundarySize()    + triIn.getBoundarySize(),
431                             concentric.getBoundarySize(), 1.0e-10);
432         Assert.assertEquals(hexaOut.getSize()   - hexaIn.getSize()   +
433                             pentaOut.getSize()  - pentaIn.getSize()  +
434                             quadriOut.getSize() - quadriIn.getSize() +
435                             triOut.getSize()    - triIn.getSize(),
436                             concentric.getSize(), 1.0e-10);
437 
438         // we expect lots of sign changes as we traverse all concentric rings
439         double phi = new S2Point(center).getPhi();
440         Assert.assertEquals(+0.207, concentric.projectToBoundary(new S2Point(-0.60,  phi)).getOffset(), 0.01);
441         Assert.assertEquals(-0.048, concentric.projectToBoundary(new S2Point(-0.21,  phi)).getOffset(), 0.01);
442         Assert.assertEquals(+0.027, concentric.projectToBoundary(new S2Point(-0.10,  phi)).getOffset(), 0.01);
443         Assert.assertEquals(-0.041, concentric.projectToBoundary(new S2Point( 0.01,  phi)).getOffset(), 0.01);
444         Assert.assertEquals(+0.049, concentric.projectToBoundary(new S2Point( 0.16,  phi)).getOffset(), 0.01);
445         Assert.assertEquals(-0.038, concentric.projectToBoundary(new S2Point( 0.29,  phi)).getOffset(), 0.01);
446         Assert.assertEquals(+0.097, concentric.projectToBoundary(new S2Point( 0.48,  phi)).getOffset(), 0.01);
447         Assert.assertEquals(-0.022, concentric.projectToBoundary(new S2Point( 0.64,  phi)).getOffset(), 0.01);
448         Assert.assertEquals(+0.072, concentric.projectToBoundary(new S2Point( 0.79,  phi)).getOffset(), 0.01);
449         Assert.assertEquals(-0.022, concentric.projectToBoundary(new S2Point( 0.93,  phi)).getOffset(), 0.01);
450         Assert.assertEquals(+0.091, concentric.projectToBoundary(new S2Point( 1.08,  phi)).getOffset(), 0.01);
451         Assert.assertEquals(-0.037, concentric.projectToBoundary(new S2Point( 1.28,  phi)).getOffset(), 0.01);
452         Assert.assertEquals(+0.051, concentric.projectToBoundary(new S2Point( 1.40,  phi)).getOffset(), 0.01);
453         Assert.assertEquals(-0.041, concentric.projectToBoundary(new S2Point( 1.55,  phi)).getOffset(), 0.01);
454         Assert.assertEquals(+0.027, concentric.projectToBoundary(new S2Point( 1.67,  phi)).getOffset(), 0.01);
455         Assert.assertEquals(-0.044, concentric.projectToBoundary(new S2Point( 1.79,  phi)).getOffset(), 0.01);
456         Assert.assertEquals(+0.201, concentric.projectToBoundary(new S2Point( 2.16,  phi)).getOffset(), 0.01);
457 
458     }
459 
460     @Test
461     public void testGeographicalMap() {
462 
463         SphericalPolygonsSet continental = buildSimpleZone(new double[][] {
464           { 51.14850,  2.51357 }, { 50.94660,  1.63900 }, { 50.12717,  1.33876 }, { 49.34737, -0.98946 },
465           { 49.77634, -1.93349 }, { 48.64442, -1.61651 }, { 48.90169, -3.29581 }, { 48.68416, -4.59234 },
466           { 47.95495, -4.49155 }, { 47.57032, -2.96327 }, { 46.01491, -1.19379 }, { 44.02261, -1.38422 },
467           { 43.42280, -1.90135 }, { 43.03401, -1.50277 }, { 42.34338,  1.82679 }, { 42.47301,  2.98599 },
468           { 43.07520,  3.10041 }, { 43.39965,  4.55696 }, { 43.12889,  6.52924 }, { 43.69384,  7.43518 },
469           { 44.12790,  7.54959 }, { 45.02851,  6.74995 }, { 45.33309,  7.09665 }, { 46.42967,  6.50009 },
470           { 46.27298,  6.02260 }, { 46.72577,  6.03738 }, { 47.62058,  7.46675 }, { 49.01778,  8.09927 },
471           { 49.20195,  6.65822 }, { 49.44266,  5.89775 }, { 49.98537,  4.79922 }
472         });
473         SphericalPolygonsSet corsica = buildSimpleZone(new double[][] {
474           { 42.15249,  9.56001 }, { 43.00998,  9.39000 }, { 42.62812,  8.74600 }, { 42.25651,  8.54421 },
475           { 41.58361,  8.77572 }, { 41.38000,  9.22975 }
476         });
477         RegionFactory<Sphere2D> factory = new RegionFactory<Sphere2D>();
478         SphericalPolygonsSet zone = (SphericalPolygonsSet) factory.union(continental, corsica);
479         EnclosingBall<Sphere2D, S2Point> enclosing = zone.getEnclosingCap();
480         Vector3D enclosingCenter = ((S2Point) enclosing.getCenter()).getVector();
481 
482         double step = FastMath.toRadians(0.1);
483         for (Vertex loopStart : zone.getBoundaryLoops()) {
484             int count = 0;
485             for (Vertex v = loopStart; count == 0 || v != loopStart; v = v.getOutgoing().getEnd()) {
486                 ++count;
487                 for (int i = 0; i < FastMath.ceil(v.getOutgoing().getLength() / step); ++i) {
488                     Vector3D p = v.getOutgoing().getPointAt(i * step);
489                     Assert.assertTrue(Vector3D.angle(p, enclosingCenter) <= enclosing.getRadius());
490                 }
491             }
492         }
493 
494         S2Point supportPointA = s2Point(48.68416, -4.59234);
495         S2Point supportPointB = s2Point(41.38000,  9.22975);
496         Assert.assertEquals(enclosing.getRadius(), supportPointA.distance(enclosing.getCenter()), 1.0e-10);
497         Assert.assertEquals(enclosing.getRadius(), supportPointB.distance(enclosing.getCenter()), 1.0e-10);
498         Assert.assertEquals(0.5 * supportPointA.distance(supportPointB), enclosing.getRadius(), 1.0e-10);
499         Assert.assertEquals(2, enclosing.getSupportSize());
500 
501         EnclosingBall<Sphere2D, S2Point> continentalInscribed =
502                 ((SphericalPolygonsSet) factory.getComplement(continental)).getEnclosingCap();
503         Vector3D continentalCenter = ((S2Point) continentalInscribed.getCenter()).getVector();
504         Assert.assertEquals(2.2, FastMath.toDegrees(FastMath.PI - continentalInscribed.getRadius()), 0.1);
505         for (Vertex loopStart : continental.getBoundaryLoops()) {
506             int count = 0;
507             for (Vertex v = loopStart; count == 0 || v != loopStart; v = v.getOutgoing().getEnd()) {
508                 ++count;
509                 for (int i = 0; i < FastMath.ceil(v.getOutgoing().getLength() / step); ++i) {
510                     Vector3D p = v.getOutgoing().getPointAt(i * step);
511                     Assert.assertTrue(Vector3D.angle(p, continentalCenter) <= continentalInscribed.getRadius());
512                 }
513             }
514         }
515 
516         EnclosingBall<Sphere2D, S2Point> corsicaInscribed =
517                 ((SphericalPolygonsSet) factory.getComplement(corsica)).getEnclosingCap();
518         Vector3D corsicaCenter = ((S2Point) corsicaInscribed.getCenter()).getVector();
519         Assert.assertEquals(0.34, FastMath.toDegrees(FastMath.PI - corsicaInscribed.getRadius()), 0.01);
520         for (Vertex loopStart : corsica.getBoundaryLoops()) {
521             int count = 0;
522             for (Vertex v = loopStart; count == 0 || v != loopStart; v = v.getOutgoing().getEnd()) {
523                 ++count;
524                 for (int i = 0; i < FastMath.ceil(v.getOutgoing().getLength() / step); ++i) {
525                     Vector3D p = v.getOutgoing().getPointAt(i * step);
526                     Assert.assertTrue(Vector3D.angle(p, corsicaCenter) <= corsicaInscribed.getRadius());
527                 }
528             }
529         }
530 
531     }
532 
533     @Test
534     public void testZigZagBoundary() {
535         SphericalPolygonsSet zone = new SphericalPolygonsSet(1.0e-6,
536                                                              new S2Point(-0.12630940610562444, 0.8998192093789258),
537                                                              new S2Point(-0.12731320182988207, 0.8963735568774486),
538                                                              new S2Point(-0.1351107624622557,  0.8978258663483273),
539                                                              new S2Point(-0.13545331405131725, 0.8966781238246179),
540                                                              new S2Point(-0.14324883017454967, 0.8981309629283796),
541                                                              new S2Point(-0.14359875625524995, 0.896983965573036),
542                                                              new S2Point(-0.14749650541159384, 0.8977109994666864),
543                                                              new S2Point(-0.14785037758231825, 0.8965644005442432),
544                                                              new S2Point(-0.15369807257448784, 0.8976550608135502),
545                                                              new S2Point(-0.1526225554339386,  0.9010934265410458),
546                                                              new S2Point(-0.14679028466684121, 0.9000043396997698),
547                                                              new S2Point(-0.14643807494172612, 0.9011511073761742),
548                                                              new S2Point(-0.1386609051963748,  0.8996991539048602),
549                                                              new S2Point(-0.13831601655974668, 0.9008466623902937),
550                                                              new S2Point(-0.1305365419828323,  0.8993961857946309),
551                                                              new S2Point(-0.1301989630405964,  0.9005444294061787));
552         Assert.assertEquals(Region.Location.INSIDE, zone.checkPoint(new S2Point(-0.145, 0.898)));
553         Assert.assertEquals(6.463e-5, zone.getSize(),         1.0e-7);
554         Assert.assertEquals(5.487e-2, zone.getBoundarySize(), 1.0e-4);
555     }
556 
557     @Test
558     public void testGitHubIssue41() {
559         RegionFactory<Sphere2D> regionFactory = new RegionFactory<>();
560         S2Point[] s2pA = new S2Point[]{
561                 new S2Point(new Vector3D(0.2122954606, -0.629606302,  0.7473463333)),
562                 new S2Point(new Vector3D(0.2120220248, -0.6296445493, 0.747391733)),
563                 new S2Point(new Vector3D(0.2119838016, -0.6298173178, 0.7472569934)),
564                 new S2Point(new Vector3D(0.2122571927, -0.6297790738, 0.7472116182))};
565 
566         S2Point[] s2pB = new S2Point[]{
567                 new S2Point(new Vector3D(0.2120291561, -0.629952069,  0.7471305292)),
568                 new S2Point(new Vector3D(0.2123026002, -0.6299138005, 0.7470851423)),
569                 new S2Point(new Vector3D(0.2123408927, -0.6297410403, 0.7472198923)),
570                 new S2Point(new Vector3D(0.2120674039, -0.6297793122, 0.7472653037))};
571 
572         final double tol = 0.0001;
573         final SphericalPolygonsSet spsA = new SphericalPolygonsSet(tol, s2pA);
574         final SphericalPolygonsSet spsB = new SphericalPolygonsSet(tol, s2pB);
575         Assert.assertEquals(0.61254e-7, spsA.getSize(),          1.0e-12);
576         Assert.assertEquals(1.00437e-3, spsA.getBoundarySize(),  1.0e-08);
577         Assert.assertEquals(0.61269e-7, spsB.getSize(),          1.0e-12);
578         Assert.assertEquals(1.00452e-3, spsB.getBoundarySize(),  1.0e-08);
579         SphericalPolygonsSet union = (SphericalPolygonsSet) regionFactory.union(spsA, spsB);
580 
581         // as the tolerance is very large with respect to polygons,
582         // the union is not computed properly, which is EXPECTED
583         // so the thresholds for the tests are large.
584         // the reference values have been computed with a much lower tolerance
585         Assert.assertEquals(1.15628e-7, union.getSize(),         4.0e-9);
586         Assert.assertEquals(1.53824e-3, union.getBoundarySize(), 3.0e-4);
587 
588     }
589 
590     @Test
591     public void testGitHubIssue42A() {
592         // if building it was allowed (i.e. if the check for tolerance was removed)
593         // the BSP tree would wrong, it would include a large extra chunk that contains
594         // a point that should really be outside
595         try {
596             doTestGitHubIssue42(1.0e-100);
597         } catch (MathIllegalArgumentException miae) {
598             Assert.assertEquals(LocalizedGeometryFormats.TOO_SMALL_TOLERANCE, miae.getSpecifier());
599             Assert.assertEquals(1.0e-100, ((Double) miae.getParts()[0]).doubleValue(), 1.0e-110);
600             Assert.assertEquals("Sphere2D.SMALLEST_TOLERANCE", miae.getParts()[1]);
601             Assert.assertEquals(Sphere2D.SMALLEST_TOLERANCE, ((Double) miae.getParts()[2]).doubleValue(), 1.0e-20);
602         }
603     }
604 
605     @Test
606     public void testGitHubIssue42B() {
607         // the BSP tree is right, but size cannot be computed
608         try {
609             /* success of this call is dependent on numerical noise.
610              * If it fails it should fail predictably.
611              */
612             doTestGitHubIssue42(9.0e-16);
613         } catch (MathIllegalStateException e) {
614             Assert.assertEquals(e.getSpecifier(),
615                     LocalizedGeometryFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
616         }
617         // Computations in Edge.split use angles up to 4 PI
618         double tol = FastMath.ulp(4 * FastMath.PI);
619         // works when tol >= ulp(largest angle used)
620         doTestGitHubIssue42(tol);
621     }
622 
623     private void doTestGitHubIssue42(double tolerance) throws MathIllegalArgumentException {
624         S2Point[] s2pA = new S2Point[]{
625             new S2Point(new Vector3D(0.1504230736114679,  -0.6603084987333554, 0.7357754993377947)),
626             new S2Point(new Vector3D(0.15011191112224423, -0.6603400871954631, 0.7358106980616113)),
627             new S2Point(new Vector3D(0.15008035620222715, -0.6605195692153062, 0.7356560238085725)),
628             new S2Point(new Vector3D(0.1503914563063968,  -0.6604879854490165, 0.7356208472763267))
629         };
630         S2Point outsidePoint = new S2Point(new Vector3D( 2, s2pA[0].getVector(),
631                                                         -1, s2pA[1].getVector(),
632                                                         -1, s2pA[2].getVector(),
633                                                          2, s2pA[3].getVector()).normalize());
634         // test all permutations because order matters when tolerance is small
635         for (int i = 0; i < s2pA.length; i++) {
636             S2Point[] points = new S2Point[s2pA.length];
637             for (int j = 0; j < s2pA.length; j++) {
638                 points[j] = s2pA[(i + j) % s2pA.length];
639             }
640             final SphericalPolygonsSet spsA = new SphericalPolygonsSet(tolerance, points);
641             Assert.assertEquals(Location.OUTSIDE, spsA.checkPoint(outsidePoint));
642             Assert.assertEquals(7.4547e-8, spsA.getSize(), 1.0e-12);
643         }
644     }
645 
646     @Test
647     public void testZigZagBoundaryOversampledIssue46() {
648         final double tol = 1.0e-4;
649         // sample region, non-convex, not too big, not too small
650         final S2Point[] vertices = {
651                 new S2Point(-0.12630940610562444e1, (0.8998192093789258 - 0.89) * 100),
652                 new S2Point(-0.12731320182988207e1, (0.8963735568774486 - 0.89) * 100),
653                 new S2Point(-0.1351107624622557e1, (0.8978258663483273 - 0.89) * 100),
654                 new S2Point(-0.13545331405131725e1, (0.8966781238246179 - 0.89) * 100),
655                 new S2Point(-0.14324883017454967e1, (0.8981309629283796 - 0.89) * 100),
656                 new S2Point(-0.14359875625524995e1, (0.896983965573036 - 0.89) * 100),
657                 new S2Point(-0.14749650541159384e1, (0.8977109994666864 - 0.89) * 100),
658                 new S2Point(-0.14785037758231825e1, (0.8965644005442432 - 0.89) * 100),
659                 new S2Point(-0.15369807257448784e1, (0.8976550608135502 - 0.89) * 100),
660                 new S2Point(-0.1526225554339386e1, (0.9010934265410458 - 0.89) * 100),
661                 new S2Point(-0.14679028466684121e1, (0.9000043396997698 - 0.89) * 100),
662                 new S2Point(-0.14643807494172612e1, (0.9011511073761742 - 0.89) * 100),
663                 new S2Point(-0.1386609051963748e1, (0.8996991539048602 - 0.89) * 100),
664                 new S2Point(-0.13831601655974668e1, (0.9008466623902937 - 0.89) * 100),
665                 new S2Point(-0.1305365419828323e1, (0.8993961857946309 - 0.89) * 100),
666                 new S2Point(-0.1301989630405964e1, (0.9005444294061787 - 0.89) * 100)};
667         SphericalPolygonsSet zone = new SphericalPolygonsSet(tol, vertices);
668         // sample high resolution boundary
669         List<S2Point> points = new ArrayList<>();
670         final Vertex start = zone.getBoundaryLoops().get(0);
671         Vertex v = start;
672         double step = tol / 10;
673         do {
674             Edge outgoing = v.getOutgoing();
675             final double length = outgoing.getLength();
676             int n = (int) (length / step);
677             for (int i = 0; i < n; i++) {
678                 points.add(new S2Point(outgoing.getPointAt(i * step)));
679             }
680             v = outgoing.getEnd();
681         } while (v != start);
682         // create zone from high resolution boundary
683         zone = new SphericalPolygonsSet(tol, points.toArray(new S2Point[0]));
684         EnclosingBall<Sphere2D, S2Point> cap = zone.getEnclosingCap();
685         // check cap size is reasonable. The region is ~0.5 accross, could be < 0.25
686         Assert.assertTrue(cap.getRadius() < 0.5);
687         Assert.assertEquals(Location.INSIDE, zone.checkPoint(zone.getBarycenter()));
688         Assert.assertEquals(Location.INSIDE, zone.checkPoint(cap.getCenter()));
689         // extra tolerance at corners due to SPS tolerance being a hyperplaneThickness
690         // a factor of 3.1 corresponds to a edge intersection angle of ~19 degrees
691         final double cornerTol = 3.1 * tol;
692         for (S2Point vertex : vertices) {
693             // check original points are on the boundary
694             Assert.assertEquals("" + vertex, Location.BOUNDARY, zone.checkPoint(vertex));
695             double offset = FastMath.abs(zone.projectToBoundary(vertex).getOffset());
696             Assert.assertEquals("" + vertex + " offset: " + offset, 0, offset, cornerTol);
697             // check original points are within the cap
698             Assert.assertTrue(
699                     "vertex: " + vertex + " distance: " + (vertex.distance(cap.getCenter()) - cap.getRadius()),
700                     cap.contains(vertex, tol));
701         }
702     }
703 
704     @Test
705     public void testPositiveOctantByVerticesDetailIssue46() {
706         double tol = 0.01;
707         double sinTol = FastMath.sin(tol);
708         Circle x = new Circle(Vector3D.PLUS_I, tol);
709         Circle z = new Circle(Vector3D.PLUS_K, tol);
710         double length = FastMath.PI / 2;
711         double step = tol / 10;
712         // sample high resolution boundary
713         int n = (int) (length / step);
714         List<S2Point> points = new ArrayList<>();
715         for (int i = 0; i < n; i++) {
716             double t = i * step;
717             points.add(new S2Point(z.getPointAt(z.getPhase(Vector3D.PLUS_I) + t)));
718         }
719         for (int i = 0; i < n; i++) {
720             double t = i * step;
721             points.add(new S2Point(x.getPointAt(x.getPhase(Vector3D.PLUS_J) + t)));
722         }
723         points.add(S2Point.PLUS_K);
724 
725         SphericalPolygonsSet octant = new SphericalPolygonsSet(tol, points.toArray(new S2Point[0]));
726         UnitSphereRandomVectorGenerator random =
727                 new UnitSphereRandomVectorGenerator(3, new Well1024a(0xb8fc5acc91044308l));
728         /* Where exactly the boundaries fall depends on which points are kept from
729          * decimation, which can vary by up to tol. So a point up to 2*tol away from a
730          * input point may be on the boundary. All input points are guaranteed to be on
731          * the boundary, just not the center of the boundary.
732          */
733         for (int i = 0; i < 1000; ++i) {
734             Vector3D v = new Vector3D(random.nextVector());
735             final Location actual = octant.checkPoint(new S2Point(v));
736             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
737                 if ((v.getX() > 2*sinTol) && (v.getY() > 2*sinTol) && (v.getZ() > 2*sinTol)) {
738                     // certainly inside
739                     Assert.assertEquals("" + v, Location.INSIDE, actual);
740                 } else {
741                     // may be inside or boundary
742                     Assert.assertNotEquals("" + v, Location.OUTSIDE, actual);
743                 }
744             } else if ((v.getX() < 0) || (v.getY() < 0) || (v.getZ() < 0)) {
745                 if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
746                     // certainly outside
747                     Assert.assertEquals(Location.OUTSIDE, actual);
748                 } else {
749                     // may be outside or boundary
750                     Assert.assertNotEquals(Location.INSIDE, actual);
751                 }
752             } else {
753                 // certainly boundary
754                 Assert.assertEquals(Location.BOUNDARY, actual);
755             }
756         }
757         // all input points are on the boundary
758         for (S2Point point : points) {
759             Assert.assertEquals("" + point, Location.BOUNDARY, octant.checkPoint(point));
760         }
761     }
762 
763     /** Check that constructing a region from 0, 1, 2, 3 points along a circle works. */
764     @Test
765     public void testConstructingFromFewPointsIssue46() {
766         double tol  = 1e-9;
767         List<S2Point> points = new ArrayList<>();
768         Circle circle = new Circle(Vector3D.PLUS_K, tol);
769 
770         // no points
771         SphericalPolygonsSet sps = new SphericalPolygonsSet(tol, new S2Point[0]);
772         Assert.assertEquals(sps.getSize(), 4*FastMath.PI, tol);
773 
774         // one point, does not define a valid boundary
775         points.add(new S2Point(circle.getPointAt(0)));
776         try {
777             new SphericalPolygonsSet(tol, points.toArray(new S2Point[0]));
778             Assert.fail("expcected exception");
779         } catch (MathRuntimeException e) {
780             // expected
781         }
782 
783         // two points, defines hemisphere but not orientation
784         points.add(new S2Point(circle.getPointAt(FastMath.PI / 2)));
785         sps = new SphericalPolygonsSet(tol, points.toArray(new S2Point[0]));
786         Assert.assertEquals(sps.getSize(), 2*FastMath.PI, tol);
787 
788         // three points
789         points.add(0, new S2Point(circle.getPointAt(FastMath.PI)));
790         sps = new SphericalPolygonsSet(tol, points.toArray(new S2Point[0]));
791         Assert.assertEquals(sps.getSize(), 2*FastMath.PI, tol);
792         Assert.assertEquals(sps.getBarycenter().distance(new S2Point(Vector3D.PLUS_K)), 0, tol);
793 
794         // four points
795         points.add(1, new S2Point(circle.getPointAt(3 * FastMath.PI / 2)));
796         sps = new SphericalPolygonsSet(tol, points.toArray(new S2Point[0]));
797         Assert.assertEquals(sps.getSize(), 2*FastMath.PI, tol);
798         Assert.assertEquals(sps.getBarycenter().distance(new S2Point(Vector3D.PLUS_K)), 0, tol);
799 
800         // many points in semi-circle
801         sps = new SphericalPolygonsSet(tol,
802                 new S2Point(circle.getPointAt(0)),
803                 new S2Point(circle.getPointAt(-0.3)),
804                 new S2Point(circle.getPointAt(-0.2)),
805                 new S2Point(circle.getPointAt(-0.1)));
806         Assert.assertEquals(sps.getSize(), 2*FastMath.PI, tol);
807         Assert.assertEquals(sps.getBarycenter().distance(new S2Point(Vector3D.PLUS_K)), 0, tol);
808     }
809 
810     @Test
811     public void testDefensiveProgrammingCheck() {
812         // this tests defensive programming code that seems almost unreachable otherwise
813         try {
814             Method searchHelper = SphericalPolygonsSet.class.getDeclaredMethod("searchHelper",
815                                                                                IntPredicate.class,
816                                                                                Integer.TYPE, Integer.TYPE);
817             searchHelper.setAccessible(true);
818             searchHelper.invoke(null, (IntPredicate) (n -> true), 1, 0);
819             Assert.fail("an exception should have been thrown");
820         } catch (InvocationTargetException ite) {
821             MathIllegalArgumentException miae = (MathIllegalArgumentException) ite.getCause();
822             Assert.assertEquals(LocalizedCoreFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, miae.getSpecifier());
823         } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException e) {
824             Assert.fail(e.getLocalizedMessage());
825         }
826     }
827 
828     /**
829      * Tests the Hipparchus {@link RegionFactory#intersection(Region, Region)}
830      * method.
831      */
832     @Test
833     public void TestIntersectionOrder() {
834 
835         final S2Point[] vertices1 = {
836             new S2Point(0.0193428339344826, 1.5537209444301618),
837             new S2Point(0.0178197572212936, 1.553415699912148),
838             new S2Point(0.01628496406053076, 1.5531081515279537),
839             new S2Point(0.016284670226196844, 1.5531096373947835),
840             new S2Point(0.019342540199680208, 1.5537224293848613)
841         };
842 
843         final S2Point[] vertices2 = {
844             new S2Point(0.016, 1.555),
845             new S2Point(0.017453292519943295, 1.555),
846             new S2Point(0.017453292519943295, 1.5533430342749532),
847             new S2Point(0.016, 1.5533430342749532)
848         };
849 
850         final RegionFactory<Sphere2D> regionFactory = new RegionFactory<Sphere2D>();
851 
852         // thickness is small enough for proper computation of very small intersection
853         double thickness1 = 4.96740426e-11;
854         final SphericalPolygonsSet sps1 = new SphericalPolygonsSet(thickness1, vertices1);
855         final SphericalPolygonsSet sps2 = new SphericalPolygonsSet(thickness1, vertices2);
856         Assert.assertEquals(1.4886e-12, regionFactory.intersection(sps1, sps2).getSize(), 1.0e-15);
857         Assert.assertEquals(1.4881e-12, regionFactory.intersection(sps2, sps1).getSize(), 1.0e-15);
858 
859         // thickness is too large, very small intersection is not computed properly in one case
860         double thickness2 = 4.96740427e-11;
861         final SphericalPolygonsSet sps3 = new SphericalPolygonsSet(thickness2, vertices1);
862         final SphericalPolygonsSet sps4 = new SphericalPolygonsSet(thickness2, vertices2);
863         Assert.assertEquals(1.4886e-12, regionFactory.intersection(sps3, sps4).getSize(), 1.0e-15);
864         Assert.assertEquals(2.4077e-06, regionFactory.intersection(sps4, sps3).getSize(), 1.0e-10);
865 
866     }
867 
868     private SubCircle create(Vector3D pole, Vector3D x, Vector3D y,
869                              double tolerance, double ... limits) {
870         RegionFactory<Sphere1D> factory = new RegionFactory<Sphere1D>();
871         Circle circle = new Circle(pole, tolerance);
872         Circle phased =
873                 (Circle) Circle.getTransform(new Rotation(circle.getXAxis(), circle.getYAxis(), x, y)).apply(circle);
874         ArcsSet set = (ArcsSet) factory.getComplement(new ArcsSet(tolerance));
875         for (int i = 0; i < limits.length; i += 2) {
876             set = (ArcsSet) factory.union(set, new ArcsSet(limits[i], limits[i + 1], tolerance));
877         }
878         return new SubCircle(phased, set);
879     }
880 
881     private SphericalPolygonsSet buildSimpleZone(double[][] points) {
882         final S2Point[] vertices = new S2Point[points.length];
883         for (int i = 0; i < points.length; ++i) {
884             vertices[i] = s2Point(points[i][0], points[i][1]);
885         }
886         return new SphericalPolygonsSet(1.0e-10, vertices);
887     }
888 
889     private S2Point s2Point(double latitude, double longitude) {
890         return new S2Point(FastMath.toRadians(longitude), FastMath.toRadians(90.0 - latitude));
891     }
892 
893 }