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    *      http://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  package org.apache.commons.geometry.spherical.twod;
18  
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.regex.Pattern;
25  import java.util.stream.Collectors;
26  
27  import org.apache.commons.geometry.core.GeometryTestUtils;
28  import org.apache.commons.geometry.core.RegionLocation;
29  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
30  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
31  import org.apache.commons.geometry.euclidean.threed.Vector3D;
32  import org.apache.commons.geometry.spherical.SphericalTestUtils;
33  import org.apache.commons.numbers.angle.PlaneAngleRadians;
34  import org.junit.Assert;
35  import org.junit.Test;
36  
37  public class GreatArcPathTest {
38  
39      private static final double TEST_EPS = 1e-10;
40  
41      private static final DoublePrecisionContext TEST_PRECISION =
42              new EpsilonDoublePrecisionContext(TEST_EPS);
43  
44      @Test
45      public void testEmpty() {
46          // act
47          final GreatArcPath path = GreatArcPath.empty();
48  
49          // assert
50          Assert.assertTrue(path.isEmpty());
51          Assert.assertFalse(path.isClosed());
52  
53          Assert.assertNull(path.getStartVertex());
54          Assert.assertNull(path.getEndVertex());
55  
56          Assert.assertNull(path.getStartArc());
57          Assert.assertNull(path.getEndArc());
58  
59          Assert.assertEquals(0, path.getArcs().size());
60          Assert.assertEquals(0, path.getVertices().size());
61      }
62  
63      @Test
64      public void testFromVertices_boolean_empty() {
65          // act
66          final GreatArcPath path = GreatArcPath.fromVertices(Collections.emptyList(), true, TEST_PRECISION);
67  
68          // assert
69          Assert.assertTrue(path.isEmpty());
70  
71          Assert.assertNull(path.getStartVertex());
72          Assert.assertNull(path.getEndVertex());
73  
74          Assert.assertNull(path.getStartArc());
75          Assert.assertNull(path.getEndArc());
76  
77          Assert.assertEquals(0, path.getArcs().size());
78          Assert.assertEquals(0, path.getVertices().size());
79      }
80  
81      @Test
82      public void testFromVertices_boolean_notClosed() {
83          // arrange
84          final List<Point2S> points = Arrays.asList(
85                  Point2S.PLUS_I,
86                  Point2S.PLUS_K,
87                  Point2S.PLUS_J);
88  
89          // act
90          final GreatArcPath path = GreatArcPath.fromVertices(points, false, TEST_PRECISION);
91  
92          // assert
93          Assert.assertFalse(path.isEmpty());
94          Assert.assertFalse(path.isClosed());
95  
96          SphericalTestUtils.assertPointsEq(Point2S.PLUS_I, path.getStartVertex(), TEST_EPS);
97          SphericalTestUtils.assertPointsEq(Point2S.PLUS_J, path.getEndVertex(), TEST_EPS);
98  
99          final List<GreatArc> arcs = path.getArcs();
100         Assert.assertEquals(2, arcs.size());
101         assertArc(arcs.get(0), Point2S.PLUS_I, Point2S.PLUS_K);
102         assertArc(arcs.get(1), Point2S.PLUS_K, Point2S.PLUS_J);
103 
104         assertPoints(points, path.getVertices());
105     }
106 
107     @Test
108     public void testFromVertices_boolean_closed() {
109         // arrange
110         final List<Point2S> points = Arrays.asList(
111                 Point2S.PLUS_I,
112                 Point2S.PLUS_K,
113                 Point2S.PLUS_J);
114 
115         // act
116         final GreatArcPath path = GreatArcPath.fromVertices(points, true, TEST_PRECISION);
117 
118         // assert
119         Assert.assertFalse(path.isEmpty());
120         Assert.assertTrue(path.isClosed());
121 
122         SphericalTestUtils.assertPointsEq(Point2S.PLUS_I, path.getStartVertex(), TEST_EPS);
123         SphericalTestUtils.assertPointsEq(Point2S.PLUS_I, path.getEndVertex(), TEST_EPS);
124 
125         final List<GreatArc> arcs = path.getArcs();
126         Assert.assertEquals(3, arcs.size());
127         assertArc(arcs.get(0), Point2S.PLUS_I, Point2S.PLUS_K);
128         assertArc(arcs.get(1), Point2S.PLUS_K, Point2S.PLUS_J);
129         assertArc(arcs.get(2), Point2S.PLUS_J, Point2S.PLUS_I);
130 
131         assertPoints(Arrays.asList(
132                 Point2S.PLUS_I,
133                 Point2S.PLUS_K,
134                 Point2S.PLUS_J,
135                 Point2S.PLUS_I), path.getVertices());
136     }
137 
138     @Test
139     public void testFromVertices_boolean_closed_pointsConsideredEqual() {
140         // arrange
141         final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-2);
142 
143         final Point2S almostPlusI = Point2S.of(1e-4, PlaneAngleRadians.PI_OVER_TWO);
144 
145         final List<Point2S> points = Arrays.asList(
146                 Point2S.PLUS_I,
147                 Point2S.PLUS_K,
148                 Point2S.PLUS_J,
149                 almostPlusI);
150 
151         // act
152         final GreatArcPath path = GreatArcPath.fromVertices(points, true, precision);
153 
154         // assert
155         Assert.assertFalse(path.isEmpty());
156         Assert.assertTrue(path.isClosed());
157 
158         SphericalTestUtils.assertPointsEq(Point2S.PLUS_I, path.getStartVertex(), TEST_EPS);
159         SphericalTestUtils.assertPointsEq(almostPlusI, path.getEndVertex(), TEST_EPS);
160 
161         final List<GreatArc> arcs = path.getArcs();
162         Assert.assertEquals(3, arcs.size());
163         assertArc(arcs.get(0), Point2S.PLUS_I, Point2S.PLUS_K);
164         assertArc(arcs.get(1), Point2S.PLUS_K, Point2S.PLUS_J);
165         assertArc(arcs.get(2), Point2S.PLUS_J, almostPlusI);
166 
167         assertPoints(Arrays.asList(
168                 Point2S.PLUS_I,
169                 Point2S.PLUS_K,
170                 Point2S.PLUS_J,
171                 almostPlusI), path.getVertices());
172     }
173 
174     @Test
175     public void testFromVertices() {
176         // arrange
177         final List<Point2S> points = Arrays.asList(
178                 Point2S.MINUS_I,
179                 Point2S.MINUS_J,
180                 Point2S.PLUS_I);
181 
182         // act
183         final GreatArcPath path = GreatArcPath.fromVertices(points, TEST_PRECISION);
184 
185         // assert
186         Assert.assertFalse(path.isEmpty());
187         Assert.assertFalse(path.isClosed());
188 
189         SphericalTestUtils.assertPointsEq(Point2S.MINUS_I, path.getStartVertex(), TEST_EPS);
190         SphericalTestUtils.assertPointsEq(Point2S.PLUS_I, path.getEndVertex(), TEST_EPS);
191 
192         final List<GreatArc> arcs = path.getArcs();
193         Assert.assertEquals(2, arcs.size());
194         assertArc(arcs.get(0), Point2S.MINUS_I, Point2S.MINUS_J);
195         assertArc(arcs.get(1), Point2S.MINUS_J, Point2S.PLUS_I);
196 
197         assertPoints(points, path.getVertices());
198     }
199 
200     @Test
201     public void testFromVertexLoop() {
202         // arrange
203         final List<Point2S> points = Arrays.asList(
204                 Point2S.MINUS_I,
205                 Point2S.MINUS_J,
206                 Point2S.MINUS_K);
207 
208         // act
209         final GreatArcPath path = GreatArcPath.fromVertexLoop(points, TEST_PRECISION);
210 
211         // assert
212         Assert.assertFalse(path.isEmpty());
213         Assert.assertTrue(path.isClosed());
214 
215         SphericalTestUtils.assertPointsEq(Point2S.MINUS_I, path.getStartVertex(), TEST_EPS);
216         SphericalTestUtils.assertPointsEq(Point2S.MINUS_I, path.getEndVertex(), TEST_EPS);
217 
218         final List<GreatArc> arcs = path.getArcs();
219         Assert.assertEquals(3, arcs.size());
220         assertArc(arcs.get(0), Point2S.MINUS_I, Point2S.MINUS_J);
221         assertArc(arcs.get(1), Point2S.MINUS_J, Point2S.MINUS_K);
222         assertArc(arcs.get(2), Point2S.MINUS_K, Point2S.MINUS_I);
223 
224         assertPoints(Arrays.asList(
225                 Point2S.MINUS_I,
226                 Point2S.MINUS_J,
227                 Point2S.MINUS_K,
228                 Point2S.MINUS_I), path.getVertices());
229     }
230 
231     @Test
232     public void testFromArcs() {
233         // arrange
234         final Point2S ptA = Point2S.PLUS_I;
235         final Point2S ptB = Point2S.of(1, PlaneAngleRadians.PI_OVER_TWO);
236         final Point2S ptC = Point2S.of(1, PlaneAngleRadians.PI_OVER_TWO - 1);
237         final Point2S ptD = Point2S.of(2, PlaneAngleRadians.PI_OVER_TWO - 1);
238 
239         final GreatArc a = GreatCircles.arcFromPoints(ptA, ptB, TEST_PRECISION);
240         final GreatArc b = GreatCircles.arcFromPoints(ptB, ptC, TEST_PRECISION);
241         final GreatArc c = GreatCircles.arcFromPoints(ptC, ptD, TEST_PRECISION);
242 
243         // act
244         final GreatArcPath path = GreatArcPath.fromArcs(a, b, c);
245 
246         // assert
247         Assert.assertFalse(path.isEmpty());
248         Assert.assertFalse(path.isClosed());
249 
250         SphericalTestUtils.assertPointsEq(ptA, path.getStartVertex(), TEST_EPS);
251         SphericalTestUtils.assertPointsEq(ptD, path.getEndVertex(), TEST_EPS);
252 
253         final List<GreatArc> arcs = path.getArcs();
254         Assert.assertEquals(3, arcs.size());
255         assertArc(arcs.get(0), ptA, ptB);
256         assertArc(arcs.get(1), ptB, ptC);
257         assertArc(arcs.get(2), ptC, ptD);
258 
259         assertPoints(Arrays.asList(ptA, ptB, ptC, ptD), path.getVertices());
260     }
261 
262     @Test
263     public void testFromArcs_full() {
264         // arrange
265         final GreatArc fullArc = GreatCircles.fromPole(Vector3D.Unit.PLUS_X, TEST_PRECISION).span();
266 
267         // act
268         final GreatArcPath path = GreatArcPath.fromArcs(fullArc);
269 
270         // assert
271         Assert.assertFalse(path.isEmpty());
272         Assert.assertFalse(path.isClosed());
273 
274         Assert.assertSame(fullArc, path.getStartArc());
275         Assert.assertSame(fullArc, path.getEndArc());
276 
277         Assert.assertNull(path.getStartVertex());
278         Assert.assertNull(path.getEndVertex());
279 
280         final List<GreatArc> arcs = path.getArcs();
281         Assert.assertEquals(1, arcs.size());
282 
283         Assert.assertSame(fullArc, arcs.get(0));
284     }
285 
286     @Test
287     public void testBoundaryStream() {
288         // arrange
289         final GreatArc fullArc = GreatCircles.fromPole(Vector3D.Unit.PLUS_X, TEST_PRECISION).span();
290         final GreatArcPath path = GreatArcPath.fromArcs(fullArc);
291 
292         // act
293         final List<GreatArc> arcs = path.boundaryStream().collect(Collectors.toList());
294 
295         // assert
296         Assert.assertEquals(1, arcs.size());
297         Assert.assertSame(fullArc, arcs.get(0));
298     }
299 
300     @Test
301     public void testBoundaryStream_noBoundaries() {
302         // arrange
303         final GreatArcPath path = GreatArcPath.empty();
304 
305         // act
306         final List<GreatArc> arcs = path.boundaryStream().collect(Collectors.toList());
307 
308         // assert
309         Assert.assertEquals(0, arcs.size());
310     }
311 
312     @Test
313     public void testToTree_empty() {
314         // act
315         final RegionBSPTree2S tree = GreatArcPath.empty().toTree();
316 
317         // assert
318         Assert.assertFalse(tree.isFull());
319         Assert.assertTrue(tree.isEmpty());
320     }
321 
322     @Test
323     public void testToTree_halfSpace() {
324         // arrange
325         final GreatArcPath path = GreatArcPath.builder(TEST_PRECISION)
326                 .append(Point2S.PLUS_I)
327                 .append(Point2S.PLUS_J)
328                 .build();
329 
330         // act
331         final RegionBSPTree2S tree = path.toTree();
332 
333         // assert
334         Assert.assertFalse(tree.isFull());
335         Assert.assertFalse(tree.isEmpty());
336 
337         Assert.assertEquals(PlaneAngleRadians.TWO_PI, tree.getSize(), TEST_EPS);
338         SphericalTestUtils.assertPointsEq(Point2S.PLUS_K, tree.getCentroid(), TEST_EPS);
339 
340         SphericalTestUtils.checkClassify(tree, RegionLocation.INSIDE, Point2S.PLUS_K);
341         SphericalTestUtils.checkClassify(tree, RegionLocation.OUTSIDE, Point2S.MINUS_K);
342     }
343 
344     @Test
345     public void testToTree_triangle() {
346         // arrange
347         final GreatArcPath path = GreatArcPath.builder(TEST_PRECISION)
348                 .append(Point2S.PLUS_I)
349                 .append(Point2S.PLUS_J)
350                 .append(Point2S.PLUS_K)
351                 .close();
352 
353         // act
354         final RegionBSPTree2S tree = path.toTree();
355 
356         // assert
357         Assert.assertFalse(tree.isFull());
358         Assert.assertFalse(tree.isEmpty());
359 
360         Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, tree.getSize(), TEST_EPS);
361 
362         final Point2S bc = Point2S.from(Point2S.PLUS_I.getVector()
363                 .add(Point2S.PLUS_J.getVector())
364                 .add(Point2S.PLUS_K.getVector()));
365 
366         SphericalTestUtils.assertPointsEq(bc, tree.getCentroid(), TEST_EPS);
367 
368         SphericalTestUtils.checkClassify(tree, RegionLocation.INSIDE, Point2S.of(0.5, 0.5));
369         SphericalTestUtils.checkClassify(tree, RegionLocation.OUTSIDE,
370                 Point2S.MINUS_K, Point2S.MINUS_I, Point2S.MINUS_J);
371     }
372 
373     @Test
374     public void testBuilder_append() {
375         // arrange
376         final Point2S a = Point2S.PLUS_I;
377         final Point2S b = Point2S.PLUS_J;
378         final Point2S c = Point2S.PLUS_K;
379         final Point2S d = Point2S.of(-1, PlaneAngleRadians.PI_OVER_TWO);
380         final Point2S e = Point2S.of(0, 0.6 * PlaneAngleRadians.PI);
381 
382         final GreatArcPath.Builder builder = GreatArcPath.builder(TEST_PRECISION);
383 
384         // act
385         final GreatArcPath path = builder.append(GreatCircles.arcFromPoints(a, b, TEST_PRECISION))
386             .appendVertices(c, d)
387             .append(e)
388             .append(GreatCircles.arcFromPoints(e, a, TEST_PRECISION))
389             .build();
390 
391         // assert
392         Assert.assertFalse(path.isEmpty());
393         Assert.assertTrue(path.isClosed());
394 
395         SphericalTestUtils.assertPointsEq(a, path.getStartVertex(), TEST_EPS);
396         SphericalTestUtils.assertPointsEq(a, path.getEndVertex(), TEST_EPS);
397 
398         final List<GreatArc> arcs = path.getArcs();
399         Assert.assertEquals(5, arcs.size());
400         assertArc(arcs.get(0), a, b);
401         assertArc(arcs.get(1), b, c);
402         assertArc(arcs.get(2), c, d);
403         assertArc(arcs.get(3), d, e);
404         assertArc(arcs.get(4), e, a);
405 
406         assertPoints(Arrays.asList(a, b, c, d, e, a), path.getVertices());
407     }
408 
409     @Test
410     public void testBuilder_prepend() {
411         // arrange
412         final Point2S a = Point2S.PLUS_I;
413         final Point2S b = Point2S.PLUS_J;
414         final Point2S c = Point2S.PLUS_K;
415         final Point2S d = Point2S.of(-1, PlaneAngleRadians.PI_OVER_TWO);
416         final Point2S e = Point2S.of(0, 0.6 * PlaneAngleRadians.PI);
417 
418         final GreatArcPath.Builder builder = GreatArcPath.builder(TEST_PRECISION);
419 
420         // act
421         final GreatArcPath path = builder.prepend(GreatCircles.arcFromPoints(e, a, TEST_PRECISION))
422             .prependPoints(Arrays.asList(c, d))
423             .prepend(b)
424             .prepend(GreatCircles.arcFromPoints(a, b, TEST_PRECISION))
425             .build();
426 
427         // assert
428         Assert.assertFalse(path.isEmpty());
429         Assert.assertTrue(path.isClosed());
430 
431         SphericalTestUtils.assertPointsEq(a, path.getStartVertex(), TEST_EPS);
432         SphericalTestUtils.assertPointsEq(a, path.getEndVertex(), TEST_EPS);
433 
434         final List<GreatArc> arcs = path.getArcs();
435         Assert.assertEquals(5, arcs.size());
436         assertArc(arcs.get(0), a, b);
437         assertArc(arcs.get(1), b, c);
438         assertArc(arcs.get(2), c, d);
439         assertArc(arcs.get(3), d, e);
440         assertArc(arcs.get(4), e, a);
441 
442         assertPoints(Arrays.asList(a, b, c, d, e, a), path.getVertices());
443     }
444 
445     @Test
446     public void testBuilder_appendAndPrepend_points() {
447         // arrange
448         final Point2S a = Point2S.PLUS_I;
449         final Point2S b = Point2S.PLUS_J;
450         final Point2S c = Point2S.PLUS_K;
451         final Point2S d = Point2S.of(-1, PlaneAngleRadians.PI_OVER_TWO);
452         final Point2S e = Point2S.of(0, 0.6 * PlaneAngleRadians.PI);
453 
454         final GreatArcPath.Builder builder = GreatArcPath.builder(TEST_PRECISION);
455 
456         // act
457         final GreatArcPath path = builder.prepend(a)
458                 .append(b)
459                 .prepend(e)
460                 .append(c)
461                 .prepend(d)
462                 .build();
463 
464         // assert
465         Assert.assertFalse(path.isEmpty());
466         Assert.assertFalse(path.isClosed());
467 
468         SphericalTestUtils.assertPointsEq(d, path.getStartVertex(), TEST_EPS);
469         SphericalTestUtils.assertPointsEq(c, path.getEndVertex(), TEST_EPS);
470 
471         final List<GreatArc> arcs = path.getArcs();
472         Assert.assertEquals(4, arcs.size());
473         assertArc(arcs.get(0), d, e);
474         assertArc(arcs.get(1), e, a);
475         assertArc(arcs.get(2), a, b);
476         assertArc(arcs.get(3), b, c);
477 
478         assertPoints(Arrays.asList(d, e, a, b, c), path.getVertices());
479     }
480 
481     @Test
482     public void testBuilder_appendAndPrepend_mixedArguments() {
483         // arrange
484         final Point2S a = Point2S.PLUS_I;
485         final Point2S b = Point2S.PLUS_J;
486         final Point2S c = Point2S.PLUS_K;
487         final Point2S d = Point2S.of(-1, PlaneAngleRadians.PI_OVER_TWO);
488         final Point2S e = Point2S.of(0, 0.6 * PlaneAngleRadians.PI);
489 
490         final GreatArcPath.Builder builder = GreatArcPath.builder(TEST_PRECISION);
491 
492         // act
493         final GreatArcPath path = builder.append(GreatCircles.arcFromPoints(a, b, TEST_PRECISION))
494                 .prepend(GreatCircles.arcFromPoints(e, a, TEST_PRECISION))
495                 .append(c)
496                 .prepend(d)
497                 .append(GreatCircles.arcFromPoints(c, d, TEST_PRECISION))
498                 .build();
499 
500         // assert
501         Assert.assertFalse(path.isEmpty());
502         Assert.assertTrue(path.isClosed());
503 
504         SphericalTestUtils.assertPointsEq(d, path.getStartVertex(), TEST_EPS);
505         SphericalTestUtils.assertPointsEq(d, path.getEndVertex(), TEST_EPS);
506 
507         final List<GreatArc> arcs = path.getArcs();
508         Assert.assertEquals(5, arcs.size());
509         assertArc(arcs.get(0), d, e);
510         assertArc(arcs.get(1), e, a);
511         assertArc(arcs.get(2), a, b);
512         assertArc(arcs.get(3), b, c);
513         assertArc(arcs.get(4), c, d);
514 
515         assertPoints(Arrays.asList(d, e, a, b, c, d), path.getVertices());
516     }
517 
518     @Test
519     public void testBuilder_points_noPrecisionGiven() {
520         // act/assert
521         GeometryTestUtils.assertThrows(() -> {
522             GreatArcPath.builder(null)
523                 .append(Point2S.PLUS_I)
524                 .append(Point2S.PLUS_J);
525         }, IllegalStateException.class, "Unable to create arc: no point precision specified");
526 
527         GeometryTestUtils.assertThrows(() -> {
528             GreatArcPath.builder(null)
529                 .prepend(Point2S.PLUS_I)
530                 .prepend(Point2S.PLUS_J);
531         }, IllegalStateException.class, "Unable to create arc: no point precision specified");
532     }
533 
534     @Test
535     public void testBuilder_arcsNotConnected() {
536         // act/assert
537         GeometryTestUtils.assertThrows(() -> {
538             GreatArcPath.builder(TEST_PRECISION)
539                 .append(Point2S.PLUS_I)
540                 .append(Point2S.PLUS_J)
541                 .append(GreatCircles.arcFromPoints(Point2S.PLUS_K, Point2S.MINUS_J, TEST_PRECISION));
542         }, IllegalStateException.class, Pattern.compile("^Path arcs are not connected.*"));
543 
544         GeometryTestUtils.assertThrows(() -> {
545             GreatArcPath.builder(TEST_PRECISION)
546                 .prepend(Point2S.PLUS_I)
547                 .prepend(Point2S.PLUS_J)
548                 .prepend(GreatCircles.arcFromPoints(Point2S.PLUS_K, Point2S.MINUS_J, TEST_PRECISION));
549         }, IllegalStateException.class, Pattern.compile("^Path arcs are not connected.*"));
550     }
551 
552     @Test
553     public void testBuilder_addToFullArc() {
554         // act/assert
555         GeometryTestUtils.assertThrows(() -> {
556             GreatArcPath.builder(TEST_PRECISION)
557                 .append(GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION).span())
558                 .append(Point2S.PLUS_J);
559         }, IllegalStateException.class, Pattern.compile("^Cannot add point .* after full arc.*"));
560 
561         GeometryTestUtils.assertThrows(() -> {
562             GreatArcPath.builder(TEST_PRECISION)
563                 .prepend(GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION).span())
564                 .prepend(Point2S.PLUS_J);
565         }, IllegalStateException.class, Pattern.compile("^Cannot add point .* before full arc.*"));
566     }
567 
568     @Test
569     public void testBuilder_onlySinglePointGiven() {
570         // act/assert
571         GeometryTestUtils.assertThrows(() -> {
572             GreatArcPath.builder(TEST_PRECISION)
573                 .append(Point2S.PLUS_J)
574                 .build();
575         }, IllegalStateException.class, Pattern.compile("^Unable to create path; only a single point provided.*"));
576 
577         GeometryTestUtils.assertThrows(() -> {
578             GreatArcPath.builder(TEST_PRECISION)
579                 .prepend(Point2S.PLUS_J)
580                 .build();
581         }, IllegalStateException.class,  Pattern.compile("^Unable to create path; only a single point provided.*"));
582     }
583 
584     @Test
585     public void testBuilder_cannotClose() {
586         // act/assert
587         GeometryTestUtils.assertThrows(() -> {
588             GreatArcPath.builder(TEST_PRECISION)
589                 .append(GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION).span())
590                 .close();
591         }, IllegalStateException.class, "Unable to close path: path is full");
592     }
593 
594     @Test
595     public void testToString_empty() {
596         // arrange
597         final GreatArcPath path = GreatArcPath.empty();
598 
599         // act
600         final String str = path.toString();
601 
602         // assert
603         Assert.assertEquals("GreatArcPath[empty= true]", str);
604     }
605 
606     @Test
607     public void testToString_singleFullArc() {
608         // arrange
609         final GreatArcPath path = GreatArcPath.fromArcs(GreatCircles.fromPole(Vector3D.Unit.PLUS_Z, TEST_PRECISION).span());
610 
611         // act
612         final String str = path.toString();
613 
614         // assert
615         GeometryTestUtils.assertContains("GreatArcPath[full= true, circle= GreatCircle[", str);
616     }
617 
618     @Test
619     public void testToString_nonFullArcs() {
620         // arrange
621         final GreatArcPath path = GreatArcPath.builder(TEST_PRECISION)
622                 .append(Point2S.PLUS_I)
623                 .append(Point2S.PLUS_J)
624                 .build();
625 
626         // act
627         final String str = path.toString();
628 
629         // assert
630         GeometryTestUtils.assertContains("ArcPath[vertices= [", str);
631     }
632 
633     private static void assertArc(final GreatArc arc, final Point2S start, final Point2S end) {
634         SphericalTestUtils.assertPointsEq(start, arc.getStartPoint(), TEST_EPS);
635         SphericalTestUtils.assertPointsEq(end, arc.getEndPoint(), TEST_EPS);
636     }
637 
638     private static void assertPoints(final Collection<Point2S> expected, final Collection<Point2S> actual) {
639         Assert.assertEquals(expected.size(), actual.size());
640 
641         final Iterator<Point2S> expIt = expected.iterator();
642         final Iterator<Point2S> actIt = actual.iterator();
643 
644         while (expIt.hasNext() && actIt.hasNext()) {
645             SphericalTestUtils.assertPointsEq(expIt.next(), actIt.next(), TEST_EPS);
646         }
647     }
648 }