Skip to content

Commit 09ed81c

Browse files
authored
Add MaximumInscribedCircle.isRadiusWithin function (#1125)
1 parent bc821da commit 09ed81c

File tree

4 files changed

+127
-11
lines changed

4 files changed

+127
-11
lines changed

modules/app/src/main/java/org/locationtech/jtstest/function/ConstructionFunctions.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static double maximumDiameterLength(Geometry g) {
5252
//--------------------------------------------
5353

5454
@Metadata(description="Constructs the Maximum Inscribed Circle of a polygonal geometry")
55-
public static Geometry maximumInscribedCircle(Geometry g,
55+
public static Geometry maxInscribedCircle(Geometry g,
5656
@Metadata(title="Distance tolerance")
5757
double tolerance) {
5858
MaximumInscribedCircle mic = new MaximumInscribedCircle(g, tolerance);
@@ -63,22 +63,22 @@ public static Geometry maximumInscribedCircle(Geometry g,
6363
}
6464

6565
@Metadata(description="Constructs the center point of the Maximum Inscribed Circle of a polygonal geometry")
66-
public static Geometry maximumInscribedCircleCenter(Geometry g,
66+
public static Geometry maxInscribedCircleCenter(Geometry g,
6767
@Metadata(title="Distance tolerance")
6868
double tolerance) {
6969
return MaximumInscribedCircle.getCenter(g, tolerance);
7070
}
7171

7272
@Metadata(description="Constructs a radius line of the Maximum Inscribed Circle of a polygonal geometry")
73-
public static Geometry maximumInscribedCircleRadius(Geometry g,
73+
public static Geometry maxInscribedCircleRadius(Geometry g,
7474
@Metadata(title="Distance tolerance")
7575
double tolerance) {
7676
MaximumInscribedCircle mic = new MaximumInscribedCircle(g, tolerance);
7777
return mic.getRadiusLine();
7878
}
7979

8080
@Metadata(description="Computes the radius of the Maximum Inscribed Circle of a polygonal geometry")
81-
public static double maximumInscribedCircleRadiusLen(Geometry g,
81+
public static double maxInscribedCircleRadiusLen(Geometry g,
8282
@Metadata(title="Distance tolerance")
8383
double tolerance) {
8484
MaximumInscribedCircle mic = new MaximumInscribedCircle(g, tolerance);

modules/app/src/main/java/org/locationtech/jtstest/function/SelectionFunctions.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
import java.util.ArrayList;
1616
import java.util.List;
1717

18+
import org.locationtech.jts.algorithm.construct.MaximumInscribedCircle;
1819
import org.locationtech.jts.geom.Geometry;
1920
import org.locationtech.jts.geom.prep.PreparedGeometry;
2021
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
2122
import org.locationtech.jts.operation.distance.IndexedFacetDistance;
23+
import org.locationtech.jtstest.geomfunction.Metadata;
2224

2325
public class SelectionFunctions
2426
{
@@ -227,6 +229,17 @@ public boolean isTrue(Geometry g) {
227229
});
228230
}
229231

232+
public static Geometry maxInCircleRadiusWithin(Geometry a,
233+
@Metadata(title="Max Radius Length")
234+
double maximumRadius)
235+
{
236+
return select(a, new GeometryPredicate() {
237+
public boolean isTrue(Geometry g) {
238+
return MaximumInscribedCircle.isRadiusWithin(g, maximumRadius);
239+
}
240+
});
241+
}
242+
230243
public static Geometry select(Geometry geom, GeometryPredicate pred)
231244
{
232245
List selected = new ArrayList();

modules/core/src/main/java/org/locationtech/jts/algorithm/construct/MaximumInscribedCircle.java

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,31 @@
4343
* measure of how "narrow" a polygon is. It is the
4444
* distance at which the negative buffer becomes empty.
4545
* <p>
46+
* The class supports testing whether a polygon is "narrower"
47+
* than a specified distance via
48+
* {@link #isRadiusWithin(Geometry, double)} or
49+
* {@link #isRadiusWithin(double)}.
50+
* Testing for the maximum radius is generally much faster
51+
* than computing the actual radius value, since short-circuiting
52+
* is used to limit the approximation iterations.
53+
* <p>
4654
* The class supports polygons with holes and multipolygons.
4755
* <p>
4856
* The implementation uses a successive-approximation technique
4957
* over a grid of square cells covering the area geometry.
5058
* The grid is refined using a branch-and-bound algorithm.
5159
* Point containment and distance are computed in a performant
5260
* way by using spatial indexes.
53-
*
61+
*
5462
* <h3>Future Enhancements</h3>
5563
* <ul>
56-
* <li>Support a polygonal constraint on placement of center
64+
* <li>Support a polygonal constraint on placement of center point,
65+
* for example to produce circle-packing constructions,
66+
* or support multiple labels.
5767
* </ul>
5868
*
5969
* @author Martin Davis
70+
*
6071
* @see LargestEmptyCircle
6172
* @see InteriorPoint
6273
* @see Centroid
@@ -83,13 +94,28 @@ public static Point getCenter(Geometry polygonal, double tolerance) {
8394
*
8495
* @param polygonal a polygonal geometry
8596
* @param tolerance the distance tolerance for computing the center point
86-
* @return a line from the center to a point on the circle
97+
* @return a 2-point line from the center to a point on the circle
8798
*/
8899
public static LineString getRadiusLine(Geometry polygonal, double tolerance) {
89100
MaximumInscribedCircle mic = new MaximumInscribedCircle(polygonal, tolerance);
90101
return mic.getRadiusLine();
91102
}
92103

104+
/**
105+
* Tests if the radius of the maximum inscribed circle
106+
* is no longer than the specified distance.
107+
* This method determines the distance tolerance automatically
108+
* as a fraction of the maxRadius value.
109+
*
110+
* @param polygonal a polygonal geometry
111+
* @param maxRadius the radius value to test
112+
* @return true if the max in-circle radius is no longer than the max radius
113+
*/
114+
public static boolean isRadiusWithin(Geometry polygonal, double maxRadius) {
115+
MaximumInscribedCircle mic = new MaximumInscribedCircle(polygonal, -1);
116+
return mic.isRadiusWithin(maxRadius);
117+
}
118+
93119
/**
94120
* Computes the maximum number of iterations allowed.
95121
* Uses a heuristic based on the size of the input geometry
@@ -122,6 +148,7 @@ static long computeMaximumIterations(Geometry geom, double toleranceDist) {
122148
private Coordinate radiusPt;
123149
private Point centerPoint;
124150
private Point radiusPoint;
151+
private double maximumRadius = -1;;
125152

126153
/**
127154
* Creates a new instance of a Maximum Inscribed Circle computation.
@@ -131,9 +158,6 @@ static long computeMaximumIterations(Geometry geom, double toleranceDist) {
131158
* @throws IllegalArgumentException if the tolerance is non-positive, or the input geometry is non-polygonal or empty.
132159
*/
133160
public MaximumInscribedCircle(Geometry polygonal, double tolerance) {
134-
if (tolerance <= 0) {
135-
throw new IllegalArgumentException("Tolerance must be positive");
136-
}
137161
if (! (polygonal instanceof Polygon || polygonal instanceof MultiPolygon)) {
138162
throw new IllegalArgumentException("Input geometry must be a Polygon or MultiPolygon");
139163
}
@@ -146,6 +170,35 @@ public MaximumInscribedCircle(Geometry polygonal, double tolerance) {
146170
this.tolerance = tolerance;
147171
}
148172

173+
private static final double MAX_RADIUS_FRACTION = 0.0001;
174+
175+
/**
176+
* Tests if the radius of the maximum inscribed circle
177+
* is no longer than the specified distance.
178+
* This method determines the distance tolerance automatically
179+
* as a fraction of the maxRadius value.
180+
* After this method is called the center and radius
181+
* points provide locations demonstrating where
182+
* the radius exceeds the specified maximum.
183+
*
184+
* @param maxRadius the (non-negative) radius value to test
185+
* @return true if the max in-circle radius is no longer than the max radius
186+
*/
187+
public boolean isRadiusWithin(double maxRadius) {
188+
if (maxRadius < 0) {
189+
throw new IllegalArgumentException("Radius length must be non-negative");
190+
}
191+
//-- handle 0 corner case, to provide maximum domain
192+
if (maxRadius == 0) {
193+
return false;
194+
}
195+
maximumRadius = maxRadius;
196+
tolerance = maxRadius * MAX_RADIUS_FRACTION;
197+
compute();
198+
double radius = centerPt.distance(radiusPt);
199+
return radius <= maximumRadius;
200+
}
201+
149202
/**
150203
* Gets the center point of the maximum inscribed circle
151204
* (up to the tolerance distance).
@@ -228,6 +281,10 @@ private void compute() {
228281
return;
229282
}
230283

284+
//-- only needed for approximation
285+
if (tolerance <= 0) {
286+
throw new IllegalArgumentException("Tolerance must be positive");
287+
}
231288
computeApproximation();
232289
}
233290

@@ -266,14 +323,25 @@ private void computeApproximation() {
266323
//System.out.println(iter + "] Dist: " + cell.getDistance() + " Max D: " + cell.getMaxDistance() + " size: " + cell.getHSide());
267324
//TestBuilderProxy.showIndicator(inputGeom.getFactory().toGeometry(cell.getEnvelope()));
268325

269-
//-- if cell must be closer than furthest, terminate since all remaining cells in queue are even closer.
326+
//-- if cell must be closer than farthest, terminate since all remaining cells in queue are even closer.
270327
if (cell.getMaxDistance() < farthestCell.getDistance())
271328
break;
272329

273330
// update the circle center cell if the candidate is further from the boundary
274331
if (cell.getDistance() > farthestCell.getDistance()) {
275332
farthestCell = cell;
276333
}
334+
335+
//-- search termination when checking max radius predicate
336+
if (maximumRadius >= 0) {
337+
//-- found a inside point further than max radius
338+
if (farthestCell.getDistance() > maximumRadius)
339+
break;
340+
//-- no cells can have larger radius
341+
if (cell.getMaxDistance() < maximumRadius)
342+
break;
343+
}
344+
277345
/**
278346
* Refine this cell if the potential distance improvement
279347
* is greater than the required tolerance.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.locationtech.jts.algorithm.construct;
2+
3+
import org.locationtech.jts.geom.Geometry;
4+
5+
import junit.textui.TestRunner;
6+
import test.jts.GeometryTestCase;
7+
8+
public class MaxInscribedCircleRadiusWithinTest extends GeometryTestCase {
9+
public static void main(String args[]) {
10+
TestRunner.run(MaxInscribedCircleRadiusWithinTest.class);
11+
}
12+
13+
public MaxInscribedCircleRadiusWithinTest(String name) { super(name); }
14+
15+
public void testDiamond() {
16+
String wkt = "POLYGON ((150 250, 50 150, 150 50, 250 150, 150 250))";
17+
checkRadiusWithin(wkt, 0, false);
18+
checkRadiusWithin(wkt, 70, false);
19+
checkRadiusWithin(wkt, 71, true);
20+
checkRadiusWithin(wkt, 1000, true);
21+
}
22+
23+
public void testIsland() {
24+
String wkt = "POLYGON ((0.041 0.3017, 0.1251 0.3137, 0.3414 0.3297, 0.7217 0.3351, 0.8979 0.3537, 1.0351 0.3838, 1.2432 0.4438, 1.3694 0.4492, 1.5145 0.4325, 1.6167 0.4151, 1.8529 0.4178, 1.94 0.4078, 2.0601 0.3798, 2.2283 0.3784, 2.3084 0.3677, 2.3254 0.347, 2.3414 0.2856, 2.3294 0.2643, 2.2934 0.2483, 2.2633 0.2376, 2.2613 0.2216, 2.2854 0.2069, 2.3414 0.1949, 2.3474 0.1802, 2.3394 0.1615, 2.3034 0.1402, 2.3133 0.1268, 2.3654 0.1228, 2.3855 0.1095, 2.3835 0.0708, 2.2984 0.0541, 2.2844 0.0407, 2.2704 0.0301, 2.2563 0.0327, 2.2203 0.0501, 2.1622 0.0514, 2.1422 0.0461, 2.1382 0.0314, 2.0882 0.0301, 2.0221 0.0407, 1.9761 0.0301, 1.932 0.022, 1.863 0, 1.8409 0.0053, 1.8309 0.0214, 1.8189 0.0147, 1.7869 0.0013, 1.7528 0.004, 1.7108 0.024, 1.6848 0.0547, 1.6888 0.0721, 1.6688 0.0841, 1.6207 0.0948, 1.5907 0.1201, 1.5606 0.1508, 1.4766 0.1628, 1.3054 0.1595, 1.2233 0.1782, 1.0872 0.1742, 0.907 0.1728, 0.7618 0.1935, 0.6317 0.2095, 0.4715 0.2015, 0.3974 0.2055, 0.2282 0.2409, 0.08 0.2382, 0.018 0.2329, 0 0.2422, 0.01 0.2729, 0.022 0.297, 0.041 0.3017))";
25+
checkRadiusWithin(wkt, 0.1, false);
26+
checkRadiusWithin(wkt, 0.2, true);
27+
}
28+
29+
private void checkRadiusWithin(String wkt, double maxRadius, boolean expected) {
30+
Geometry geom = read(wkt);
31+
boolean actual = MaximumInscribedCircle.isRadiusWithin(geom, maxRadius);
32+
assertEquals(expected, actual);
33+
}
34+
35+
}

0 commit comments

Comments
 (0)