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.euclidean.twod.shape;
18  
19  import java.util.ArrayList;
20  import java.util.Comparator;
21  import java.util.List;
22  
23  import org.apache.commons.geometry.core.GeometryTestUtils;
24  import org.apache.commons.geometry.core.RegionLocation;
25  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
26  import org.apache.commons.geometry.euclidean.twod.Line;
27  import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
28  import org.apache.commons.geometry.euclidean.twod.LinecastPoint2D;
29  import org.apache.commons.geometry.euclidean.twod.Lines;
30  import org.apache.commons.geometry.euclidean.twod.PolarCoordinates;
31  import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
32  import org.apache.commons.geometry.euclidean.twod.Vector2D;
33  import org.apache.commons.geometry.euclidean.twod.path.LinePath;
34  import org.apache.commons.numbers.angle.Angle;
35  import org.apache.commons.numbers.core.Precision;
36  import org.junit.jupiter.api.Assertions;
37  import org.junit.jupiter.api.Test;
38  
39  class CircleTest {
40  
41      private static final double TEST_EPS = 1e-10;
42  
43      private static final Precision.DoubleEquivalence TEST_PRECISION =
44              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
45  
46      private static final Comparator<LineConvexSubset> SEGMENT_DIRECTION_COMPARATOR =
47          (a, b) -> Vector2D.COORDINATE_ASCENDING_ORDER.compare(
48              a.getLine().getDirection(),
49              b.getLine().getDirection());
50  
51      @Test
52      void testFrom() {
53          // arrange
54          final Vector2D center = Vector2D.of(1, 2);
55  
56          // act
57          final Circle c = Circle.from(center, 3, TEST_PRECISION);
58  
59          // act/assert
60          Assertions.assertFalse(c.isFull());
61          Assertions.assertFalse(c.isEmpty());
62  
63          Assertions.assertSame(center, c.getCenter());
64          Assertions.assertSame(center, c.getCentroid());
65  
66          Assertions.assertEquals(3, c.getRadius(), 0.0);
67  
68          Assertions.assertSame(TEST_PRECISION, c.getPrecision());
69      }
70  
71      @Test
72      void testFrom_illegalCenter() {
73          // act/assert
74          Assertions.assertThrows(IllegalArgumentException.class, () -> Circle.from(Vector2D.of(Double.POSITIVE_INFINITY, 1), 1, TEST_PRECISION));
75          Assertions.assertThrows(IllegalArgumentException.class, () -> Circle.from(Vector2D.of(Double.NaN, 1), 1, TEST_PRECISION));
76      }
77  
78      @Test
79      void testFrom_illegalRadius() {
80          // arrange
81          final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-2);
82  
83          // act/assert
84          Assertions.assertThrows(IllegalArgumentException.class, () -> Circle.from(Vector2D.ZERO, -1, TEST_PRECISION));
85          Assertions.assertThrows(IllegalArgumentException.class, () -> Circle.from(Vector2D.ZERO, 0, TEST_PRECISION));
86          Assertions.assertThrows(IllegalArgumentException.class, () -> Circle.from(Vector2D.ZERO, Double.POSITIVE_INFINITY, TEST_PRECISION));
87          Assertions.assertThrows(IllegalArgumentException.class, () -> Circle.from(Vector2D.ZERO, Double.NaN, TEST_PRECISION));
88          Assertions.assertThrows(IllegalArgumentException.class, () -> Circle.from(Vector2D.ZERO, 1e-3, precision));
89      }
90  
91      @Test
92      void testGeometricProperties() {
93          // arrange
94          final double r = 2;
95          final Circle c = Circle.from(Vector2D.of(1, 2), r, TEST_PRECISION);
96  
97          // act/assert
98          Assertions.assertEquals(2 * Math.PI * r, c.getBoundarySize(), TEST_EPS);
99          Assertions.assertEquals(Math.PI * r * r, c.getSize(), TEST_EPS);
100     }
101 
102     @Test
103     void testClassify() {
104         // arrange
105         final Circle c = Circle.from(Vector2D.of(1, 2), 1, TEST_PRECISION);
106 
107         // act/assert
108         EuclideanTestUtils.assertRegionLocation(c, RegionLocation.INSIDE,
109                 Vector2D.of(1, 2),
110                 Vector2D.of(0.5, 2), Vector2D.of(1.5, 2),
111                 Vector2D.of(1, 1.5), Vector2D.of(1, 2.5),
112                 Vector2D.of(0.5, 1.5), Vector2D.of(1.5, 2.5),
113                 Vector2D.of(0.5, 2.5), Vector2D.of(1.5, 1.5));
114 
115         EuclideanTestUtils.assertRegionLocation(c, RegionLocation.OUTSIDE,
116                 Vector2D.of(-0.5, 2), Vector2D.of(2.5, 2),
117                 Vector2D.of(1, 0.5), Vector2D.of(1, 3.5),
118                 Vector2D.of(0.25, 1.25), Vector2D.of(1.75, 2.75),
119                 Vector2D.of(0.25, 2.75), Vector2D.of(1.75, 1.25));
120 
121         for (double angle = 0; angle < Angle.TWO_PI; angle += 0.1) {
122             EuclideanTestUtils.assertRegionLocation(c, RegionLocation.BOUNDARY,
123                     c.getCenter().add(PolarCoordinates.of(1, angle).toCartesian()));
124         }
125     }
126 
127     @Test
128     void testContains() {
129         // arrange
130         final Circle c = Circle.from(Vector2D.of(1, 2), 1, TEST_PRECISION);
131 
132         // act/assert
133         checkContains(c, true,
134                 Vector2D.of(1, 2),
135                 Vector2D.of(0.5, 2), Vector2D.of(1.5, 2),
136                 Vector2D.of(1, 1.5), Vector2D.of(1, 2.5),
137                 Vector2D.of(0.5, 1.5), Vector2D.of(1.5, 2.5),
138                 Vector2D.of(0.5, 2.5), Vector2D.of(1.5, 1.5));
139 
140         for (double angle = 0; angle < Angle.TWO_PI; angle += 0.1) {
141             checkContains(c, true,
142                     c.getCenter().add(PolarCoordinates.of(1, angle).toCartesian()));
143         }
144 
145         checkContains(c, false,
146                 Vector2D.of(-0.5, 2), Vector2D.of(2.5, 2),
147                 Vector2D.of(1, 0.5), Vector2D.of(1, 3.5),
148                 Vector2D.of(0.25, 1.25), Vector2D.of(1.75, 2.75),
149                 Vector2D.of(0.25, 2.75), Vector2D.of(1.75, 1.25));
150     }
151 
152     @Test
153     void testProject() {
154         // arrange
155         final Vector2D center = Vector2D.of(1.5, 2.5);
156         final double radius = 3;
157         final Circle c = Circle.from(center, radius, TEST_PRECISION);
158 
159         EuclideanTestUtils.permute(-4, 4, 1, (x, y) -> {
160             final Vector2D pt = Vector2D.of(x, y);
161 
162             // act
163             final Vector2D projection = c.project(pt);
164 
165             // assert
166             Assertions.assertEquals(radius, center.distance(projection), TEST_EPS);
167             EuclideanTestUtils.assertCoordinatesEqual(center.directionTo(pt),
168                     center.directionTo(projection), TEST_EPS);
169         });
170     }
171 
172     @Test
173     void testProject_argumentEqualsCenter() {
174         // arrange
175         final Circle c = Circle.from(Vector2D.of(1, 2), 2, TEST_PRECISION);
176 
177         // act
178         final Vector2D projection = c.project(Vector2D.of(1, 2));
179 
180         // assert
181         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 2), projection, TEST_EPS);
182     }
183 
184     @Test
185     void testIntersections() {
186         // --- arrange
187         final Circle c = Circle.from(Vector2D.of(2, 1), 2, TEST_PRECISION);
188         final double sqrt3 = Math.sqrt(3);
189 
190         // --- act/assert
191         // descending horizontal lines
192         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, 4), Vector2D.of(5, 4), TEST_PRECISION));
193         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, 3), Vector2D.of(5, 3), TEST_PRECISION),
194                 Vector2D.of(2, 3));
195         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, 2), Vector2D.of(5, 2), TEST_PRECISION),
196                 Vector2D.of(2 - sqrt3, 2), Vector2D.of(2 + sqrt3, 2));
197         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, 1), Vector2D.of(5, 1), TEST_PRECISION),
198                 Vector2D.of(0, 1), Vector2D.of(4, 1));
199         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(5, 0), TEST_PRECISION),
200                 Vector2D.of(2 - sqrt3, 0), Vector2D.of(2 + sqrt3, 0));
201         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, -1), Vector2D.of(5, -1), TEST_PRECISION),
202                 Vector2D.of(2, -1));
203         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, -2), Vector2D.of(5, -2), TEST_PRECISION));
204 
205         // ascending vertical lines
206         checkIntersections(c, Lines.fromPoints(Vector2D.of(-1, -2), Vector2D.of(-1, 5), TEST_PRECISION));
207         checkIntersections(c, Lines.fromPoints(Vector2D.of(0, -2), Vector2D.of(0, 5), TEST_PRECISION),
208                 Vector2D.of(0, 1));
209         checkIntersections(c, Lines.fromPoints(Vector2D.of(1, -2), Vector2D.of(1, 5), TEST_PRECISION),
210                 Vector2D.of(1, 1 - sqrt3), Vector2D.of(1, 1 + sqrt3));
211         checkIntersections(c, Lines.fromPoints(Vector2D.of(2, -2), Vector2D.of(2, 5), TEST_PRECISION),
212                 Vector2D.of(2, -1), Vector2D.of(2, 3));
213         checkIntersections(c, Lines.fromPoints(Vector2D.of(3, -2), Vector2D.of(3, 5), TEST_PRECISION),
214                 Vector2D.of(3, 1 - sqrt3), Vector2D.of(3, 1 + sqrt3));
215         checkIntersections(c, Lines.fromPoints(Vector2D.of(4, -2), Vector2D.of(4, 5), TEST_PRECISION),
216                 Vector2D.of(4, 1));
217         checkIntersections(c, Lines.fromPoints(Vector2D.of(5, -2), Vector2D.of(5, 5), TEST_PRECISION));
218 
219         // diagonal from origin
220         final Vector2D center = c.getCenter();
221         checkIntersections(c, Lines.fromPoints(Vector2D.ZERO, c.getCenter(), TEST_PRECISION),
222                 center.withNorm(center.norm() - c.getRadius()), center.withNorm(center.norm() + c.getRadius()));
223     }
224 
225     @Test
226     void testLinecast() {
227         // arrange
228         final Circle c = Circle.from(Vector2D.of(2, 1), 2, TEST_PRECISION);
229         final double sqrt3 = Math.sqrt(3);
230 
231         // act/assert
232         checkLinecast(c, Lines.segmentFromPoints(Vector2D.of(-1, 0), Vector2D.of(5, 0), TEST_PRECISION),
233                 Vector2D.of(2 - sqrt3, 0), Vector2D.of(2 + sqrt3, 0));
234         checkLinecast(c, Lines.segmentFromPoints(Vector2D.of(-1, 3), Vector2D.of(5, 3), TEST_PRECISION),
235                 Vector2D.of(2, 3));
236         checkLinecast(c, Lines.segmentFromPoints(Vector2D.of(-1, -2), Vector2D.of(5, -2), TEST_PRECISION));
237     }
238 
239     @Test
240     void testLinecast_intersectionsNotInSegment() {
241         // arrange
242         final Circle c = Circle.from(Vector2D.of(2, 1), 2, TEST_PRECISION);
243         final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION);
244 
245         // act/assert
246         checkLinecast(c, line.segment(-1, 0));
247         checkLinecast(c, line.segment(1.5, 2.5));
248         checkLinecast(c, line.segment(1.5, 2.5));
249         checkLinecast(c, line.segment(4, 5));
250     }
251 
252     @Test
253     void testLinecast_segmentPointOnBoundary() {
254         // arrange
255         final Circle c = Circle.from(Vector2D.of(2, 1), 2, TEST_PRECISION);
256         final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION);
257         final double sqrt3 = Math.sqrt(3);
258         final double start = 2 - sqrt3;
259         final double end = 2 + sqrt3;
260 
261         // act/assert
262         checkLinecast(c, line.segment(start, 2), Vector2D.of(start, 0));
263         checkLinecast(c, line.segment(start, end), Vector2D.of(start, 0), Vector2D.of(end, 0));
264         checkLinecast(c, line.segment(end, 5), Vector2D.of(end, 0));
265     }
266 
267     @Test
268     void testToTree_threeSegments() {
269         // arrange
270         final Circle c = Circle.from(Vector2D.of(2, 1), 2, TEST_PRECISION);
271 
272         // act
273         final RegionBSPTree2D tree = c.toTree(3);
274 
275         // assert
276         checkBasicApproximationProperties(c, tree);
277 
278         final List<LineConvexSubset> segments = new ArrayList<>(tree.getBoundaries());
279         segments.sort(SEGMENT_DIRECTION_COMPARATOR);
280 
281         Assertions.assertEquals(3, segments.size());
282 
283         final double inc = Angle.TWO_PI / 3.0;
284         final Vector2D p0 = Vector2D.of(4, 1);
285         final Vector2D p1 = Vector2D.of(
286                 (2 * Math.cos(inc)) + 2,
287                 (2 * Math.sin(inc)) + 1);
288         final Vector2D p2 = Vector2D.of(
289                 (2 * Math.cos(2 * inc)) + 2,
290                 (2 * Math.sin(2 * inc)) + 1);
291 
292         assertFiniteSegment(segments.get(0), p0, p1);
293         assertFiniteSegment(segments.get(1), p1, p2);
294         assertFiniteSegment(segments.get(2), p2, p0);
295     }
296 
297     @Test
298     void testToTree_fourSegments() {
299         // arrange
300         final Circle c = Circle.from(Vector2D.of(2, 1), 2, TEST_PRECISION);
301 
302         // act
303         final RegionBSPTree2D tree = c.toTree(4);
304 
305         // assert
306         checkBasicApproximationProperties(c, tree);
307 
308         final List<LineConvexSubset> segments = new ArrayList<>(tree.getBoundaries());
309         segments.sort(SEGMENT_DIRECTION_COMPARATOR);
310 
311         Assertions.assertEquals(4, segments.size());
312 
313         final Vector2D p0 = Vector2D.of(4, 1);
314         final Vector2D p1 = Vector2D.of(2, 3);
315         final Vector2D p2 = Vector2D.of(0, 1);
316         final Vector2D p3 = Vector2D.of(2, -1);
317 
318         assertFiniteSegment(segments.get(0), p1, p2);
319         assertFiniteSegment(segments.get(1), p0, p1);
320         assertFiniteSegment(segments.get(2), p2, p3);
321         assertFiniteSegment(segments.get(3), p3, p0);
322     }
323 
324     @Test
325     void testToTree_multipleApproximationSizes() {
326         // -- arrange
327         final Circle c = Circle.from(Vector2D.of(-3, 5), 10, TEST_PRECISION);
328 
329         final int min = 5;
330         final int max = 100;
331 
332         RegionBSPTree2D tree;
333 
334         double sizeDiff;
335         double prevSizeDiff = Double.POSITIVE_INFINITY;
336 
337         for (int n = min; n <= max; ++n) {
338             // -- act
339             tree = c.toTree(n);
340 
341             // -- assert
342             checkBasicApproximationProperties(c, tree);
343 
344             // check that we get closer and closer to the correct size as we add more segments
345             sizeDiff = c.getSize() - tree.getSize();
346             Assertions.assertTrue(sizeDiff < prevSizeDiff, "Expected size difference to decrease");
347 
348             prevSizeDiff = sizeDiff;
349         }
350     }
351 
352     @Test
353     void testToTree_closeApproximation() {
354         // arrange
355         final Circle c = Circle.from(Vector2D.of(-2, 0), 1, TEST_PRECISION);
356 
357         // act
358         final RegionBSPTree2D tree = c.toTree(100);
359 
360         // assert
361         checkBasicApproximationProperties(c, tree);
362 
363         final double eps = 5e-3;
364         Assertions.assertEquals(c.getSize(), tree.getSize(), eps);
365         Assertions.assertEquals(c.getBoundarySize(), tree.getBoundarySize(), eps);
366         EuclideanTestUtils.assertCoordinatesEqual(c.getCentroid(), tree.getCentroid(), eps);
367     }
368 
369     @Test
370     void testToTree_invalidSegmentCount() {
371         // arrange
372         final Circle c = Circle.from(Vector2D.of(2, 1), 2, TEST_PRECISION);
373         final String baseMsg = "Circle approximation segment number must be greater than or equal to 3; was ";
374 
375         // act/assert
376         GeometryTestUtils.assertThrowsWithMessage(() -> {
377             c.toTree(2);
378         }, IllegalArgumentException.class, baseMsg + "2");
379         GeometryTestUtils.assertThrowsWithMessage(() -> {
380             c.toTree(-1);
381         }, IllegalArgumentException.class, baseMsg + "-1");
382     }
383 
384     @Test
385     void testHashCode() {
386         // arrange
387         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-2);
388 
389         final Circle a = Circle.from(Vector2D.of(1, 2), 3, TEST_PRECISION);
390         final Circle b = Circle.from(Vector2D.of(1, 1), 3, TEST_PRECISION);
391         final Circle c = Circle.from(Vector2D.of(1, 2), 4, TEST_PRECISION);
392         final Circle d = Circle.from(Vector2D.of(1, 2), 3, precision);
393         final Circle e = Circle.from(Vector2D.of(1, 2), 3, TEST_PRECISION);
394 
395         // act
396         final int hash = a.hashCode();
397 
398         // act/assert
399         Assertions.assertEquals(hash, a.hashCode());
400 
401         Assertions.assertNotEquals(hash, b.hashCode());
402         Assertions.assertNotEquals(hash, c.hashCode());
403         Assertions.assertNotEquals(hash, d.hashCode());
404 
405         Assertions.assertEquals(hash, e.hashCode());
406     }
407 
408     @Test
409     void testEquals() {
410         // arrange
411         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-2);
412 
413         final Circle a = Circle.from(Vector2D.of(1, 2), 3, TEST_PRECISION);
414         final Circle b = Circle.from(Vector2D.of(1, 1), 3, TEST_PRECISION);
415         final Circle c = Circle.from(Vector2D.of(1, 2), 4, TEST_PRECISION);
416         final Circle d = Circle.from(Vector2D.of(1, 2), 3, precision);
417         final Circle e = Circle.from(Vector2D.of(1, 2), 3, TEST_PRECISION);
418 
419         // act/assert
420         GeometryTestUtils.assertSimpleEqualsCases(a);
421 
422         Assertions.assertNotEquals(a, b);
423         Assertions.assertNotEquals(a, c);
424         Assertions.assertNotEquals(a, d);
425 
426         Assertions.assertEquals(a, e);
427     }
428 
429     @Test
430     void testToString() {
431         // arrange
432         final Circle c = Circle.from(Vector2D.of(1, 2), 3, TEST_PRECISION);
433 
434         // act
435         final String str = c.toString();
436 
437         // assert
438         Assertions.assertEquals("Circle[center= (1.0, 2.0), radius= 3.0]", str);
439     }
440 
441     private static void checkContains(final Circle circle, final boolean contains, final Vector2D... pts) {
442         for (final Vector2D pt : pts) {
443             Assertions.assertEquals(contains, circle.contains(pt),
444                     "Expected circle to " + (contains ? "" : "not") + "contain point " + pt);
445         }
446     }
447 
448     private static void checkIntersections(final Circle circle, final Line line, final Vector2D... expectedPts) {
449         // --- act
450         // compute the intersections forward and reverse
451         final List<Vector2D> actualPtsForward = circle.intersections(line);
452         final List<Vector2D> actualPtsReverse = circle.intersections(line.reverse());
453 
454         final Vector2D actualFirstForward = circle.firstIntersection(line);
455         final Vector2D actualFirstReverse = circle.firstIntersection(line.reverse());
456 
457         // --- assert
458         final int len = expectedPts.length;
459 
460         // check the lists
461         Assertions.assertEquals(len, actualPtsForward.size());
462         Assertions.assertEquals(len, actualPtsReverse.size());
463 
464         for (int i = 0; i < len; ++i) {
465             EuclideanTestUtils.assertCoordinatesEqual(expectedPts[i], actualPtsForward.get(i), TEST_EPS);
466             Assertions.assertEquals(circle.getRadius(), circle.getCenter().distance(actualPtsForward.get(i)), TEST_EPS);
467 
468             EuclideanTestUtils.assertCoordinatesEqual(expectedPts[len - i - 1], actualPtsReverse.get(i), TEST_EPS);
469             Assertions.assertEquals(circle.getRadius(), circle.getCenter().distance(actualPtsReverse.get(i)), TEST_EPS);
470         }
471 
472         // check the single intersection points
473         if (len > 0) {
474             Assertions.assertNotNull(actualFirstForward);
475             Assertions.assertNotNull(actualFirstReverse);
476 
477             EuclideanTestUtils.assertCoordinatesEqual(expectedPts[0], actualFirstForward, TEST_EPS);
478             EuclideanTestUtils.assertCoordinatesEqual(expectedPts[len - 1], actualFirstReverse, TEST_EPS);
479         } else {
480             Assertions.assertNull(actualFirstForward);
481             Assertions.assertNull(actualFirstReverse);
482         }
483     }
484 
485     private static void checkLinecast(final Circle c, final LineConvexSubset segment, final Vector2D... expectedPts) {
486         // check linecast
487         final List<LinecastPoint2D> results = c.linecast(segment);
488         Assertions.assertEquals(expectedPts.length, results.size());
489 
490         LinecastPoint2D actual;
491         Vector2D expected;
492         for (int i = 0; i < expectedPts.length; ++i) {
493             expected = expectedPts[i];
494             actual = results.get(i);
495 
496             EuclideanTestUtils.assertCoordinatesEqual(expected, actual.getPoint(), TEST_EPS);
497             EuclideanTestUtils.assertCoordinatesEqual(c.getCenter().directionTo(expected), actual.getNormal(), TEST_EPS);
498             Assertions.assertSame(segment.getLine(), actual.getLine());
499         }
500 
501         // check linecastFirst
502         final LinecastPoint2D firstResult = c.linecastFirst(segment);
503         if (expectedPts.length > 0) {
504             Assertions.assertEquals(results.get(0), firstResult);
505         } else {
506             Assertions.assertNull(firstResult);
507         }
508     }
509 
510     /**
511      * Check a number of standard properties for bsp trees generated as circle approximations.
512      */
513     private static void checkBasicApproximationProperties(final Circle c, final RegionBSPTree2D tree) {
514         Assertions.assertFalse(tree.isFull());
515         Assertions.assertFalse(tree.isEmpty());
516 
517         // all vertices must be inside the circle or on the boundary
518         final List<LinePath> paths = tree.getBoundaryPaths();
519         Assertions.assertEquals(1, paths.size());
520 
521         final LinePath path = paths.get(0);
522         Assertions.assertTrue(path.isFinite());
523 
524         for (final Vector2D vertex : path.getVertexSequence()) {
525             Assertions.assertTrue(c.contains(vertex), "Expected vertex to be contained in circle: " + vertex);
526         }
527 
528         // circle must contain centroid
529         EuclideanTestUtils.assertRegionLocation(c, RegionLocation.INSIDE, tree.getCentroid());
530 
531         // area must be less than the circle
532         Assertions.assertTrue(tree.getSize() < c.getSize(), "Expected approximation area to be less than circle");
533     }
534 
535     private static void assertFiniteSegment(final LineConvexSubset segment, final Vector2D start, final Vector2D end) {
536         Assertions.assertFalse(segment.isInfinite());
537         Assertions.assertTrue(segment.isFinite());
538 
539         EuclideanTestUtils.assertCoordinatesEqual(start, segment.getStartPoint(), TEST_EPS);
540         EuclideanTestUtils.assertCoordinatesEqual(end, segment.getEndPoint(), TEST_EPS);
541     }
542 }