11/*
2- * Copyright (c) 2020 Martin Davis.
2+ * Copyright (c) 2025 Martin Davis.
33 *
44 * All rights reserved. This program and the accompanying materials
55 * are made available under the terms of the Eclipse Public License 2.0
2828
2929/**
3030 * Constructs the Largest Empty Circle for a set
31- * of obstacle geometries, up to a given accuracy distance tolerance.
31+ * of obstacle geometries, up to a given accuracy distance tolerance
32+ * (which can be specified or determined automatically).
3233 * The obstacles may be any combination of point, linear and polygonal geometries.
3334 * <p>
3435 * The Largest Empty Circle (LEC) is the largest circle
6061 */
6162public class LargestEmptyCircle {
6263
64+ /**
65+ * Computes the center point of the Largest Empty Circle
66+ * interior-disjoint to a set of obstacles.
67+ * The obstacles may be any collection of points, lines and polygons.
68+ * The center of the LEC lies within the convex hull of the obstacles.
69+ *
70+ * @param obstacles a geometry representing the obstacles
71+ * @return the center point of the Largest Empty Circle
72+ */
73+ public static Point getCenter (Geometry obstacles ) {
74+ return getCenter (obstacles , null , 0.0 );
75+ }
76+
6377 /**
6478 * Computes the center point of the Largest Empty Circle
6579 * interior-disjoint to a set of obstacles,
@@ -75,6 +89,20 @@ public static Point getCenter(Geometry obstacles, double tolerance) {
7589 return getCenter (obstacles , null , tolerance );
7690 }
7791
92+ /**
93+ * Computes the center point of the Largest Empty Circle
94+ * interior-disjoint to a set of obstacles and within a polygonal boundary.
95+ * The obstacles may be any collection of points, lines and polygons.
96+ * The center of the LEC lies within the given boundary.
97+ *
98+ * @param obstacles a geometry representing the obstacles
99+ * @param boundary a polygonal geometry to contain the LEC center
100+ * @return the center point of the Largest Empty Circle
101+ */
102+ public static Point getCenter (Geometry obstacles , Geometry boundary ) {
103+ return getCenter (obstacles , boundary , 0.0 );
104+ }
105+
78106 /**
79107 * Computes the center point of the Largest Empty Circle
80108 * interior-disjoint to a set of obstacles and within a polygonal boundary,
@@ -152,7 +180,23 @@ public static LineString getRadiusLine(Geometry obstacles, Geometry boundary, do
152180 *
153181 * @param obstacles a non-empty geometry representing the obstacles
154182 * @param boundary a polygonal geometry (may be null or empty)
155- * @param tolerance a distance tolerance for computing the circle center point (a positive value)
183+ */
184+ public LargestEmptyCircle (Geometry obstacles , Geometry boundary ) {
185+ this (obstacles , boundary , 0.0 );
186+ }
187+
188+ /**
189+ * Creates a new instance of a Largest Empty Circle construction,
190+ * interior-disjoint to a set of obstacle geometries
191+ * and having its center within a polygonal boundary.
192+ * The obstacles may be any collection of points, lines and polygons.
193+ * If the boundary is null or empty the convex hull
194+ * of the obstacles is used as the boundary.
195+ * A zero tolerance aut0matically determines an approximation tolerance.
196+ *
197+ * @param obstacles a non-empty geometry representing the obstacles
198+ * @param boundary a polygonal geometry (may be null or empty)
199+ * @param tolerance a distance tolerance for computing the circle center point (a non-negative value)
156200 */
157201 public LargestEmptyCircle (Geometry obstacles , Geometry boundary , double tolerance ) {
158202 if (obstacles == null || obstacles .isEmpty ()) {
@@ -161,8 +205,8 @@ public LargestEmptyCircle(Geometry obstacles, Geometry boundary, double toleranc
161205 if (boundary != null && ! (boundary instanceof Polygonal )) {
162206 throw new IllegalArgumentException ("Boundary must be polygonal" );
163207 }
164- if (tolerance <= 0 ) {
165- throw new IllegalArgumentException ("Accuracy tolerance is non-positive : " + tolerance );
208+ if (tolerance < 0 ) {
209+ throw new IllegalArgumentException ("Accuracy tolerance is negative : " + tolerance );
166210 }
167211 this .obstacles = obstacles ;
168212 this .boundary = boundary ;
@@ -320,6 +364,9 @@ private void compute() {
320364 radiusPoint = factory .createPoint (radiusPt );
321365 }
322366
367+ //-- empirically determined to balance accuracy and speed
368+ private static final double AUTO_TOLERANCE_FRACTION = 0.001 ;
369+
323370 /**
324371 * Tests whether a cell may contain the circle center,
325372 * and thus should be refined (split into subcells
@@ -336,14 +383,24 @@ private boolean mayContainCircleCenter(Cell cell) {
336383 if (cell .isFullyOutside ())
337384 return false ;
338385
386+ /**
387+ * The tolerance can be automatically determined
388+ * as a fraction of the current farthest distance.
389+ * For a very small actual MIC distance this may cause many iterations,
390+ * but the iter limit prevents an infinite loop
391+ */
392+ double requiredTol = tolerance > 0
393+ ? tolerance
394+ : farthestCell .getDistance () * AUTO_TOLERANCE_FRACTION ;
395+
339396 /**
340397 * The cell is outside, but overlaps the boundary
341398 * so it may contain a point which should be checked.
342399 * This is only the case if the potential overlap distance
343400 * is larger than the tolerance.
344401 */
345402 if (cell .isOutside ()) {
346- boolean isOverlapSignificant = cell .getMaxDistance () > tolerance ;
403+ boolean isOverlapSignificant = cell .getMaxDistance () > requiredTol ;
347404 return isOverlapSignificant ;
348405 }
349406
@@ -353,7 +410,7 @@ private boolean mayContainCircleCenter(Cell cell) {
353410 * (up to tolerance).
354411 */
355412 double potentialIncrease = cell .getMaxDistance () - farthestCell .getDistance ();
356- return potentialIncrease > tolerance ;
413+ return potentialIncrease > requiredTol ;
357414 }
358415
359416 /**
0 commit comments