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