Skip to content

Commit fcd54d2

Browse files
authored
Merge pull request #5994 from kwvanderlinde/bugfix/5992-broken-fow
Handle more general geometries when converting AWT shapes to JTS polygons
2 parents bbb815d + 83d136a commit fcd54d2

2 files changed

Lines changed: 144 additions & 13 deletions

File tree

common/src/main/java/net/rptools/lib/GeometryUtil.java

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Comparator;
2424
import java.util.List;
2525
import java.util.Objects;
26+
import java.util.function.Consumer;
2627
import org.apache.logging.log4j.LogManager;
2728
import org.apache.logging.log4j.Logger;
2829
import org.locationtech.jts.algorithm.InteriorPointArea;
@@ -33,6 +34,7 @@
3334
import org.locationtech.jts.geom.CoordinateArrays;
3435
import org.locationtech.jts.geom.Envelope;
3536
import org.locationtech.jts.geom.Geometry;
37+
import org.locationtech.jts.geom.GeometryCollection;
3638
import org.locationtech.jts.geom.GeometryFactory;
3739
import org.locationtech.jts.geom.LinearRing;
3840
import org.locationtech.jts.geom.Location;
@@ -201,33 +203,59 @@ public static Collection<Polygon> toJtsPolygons(Shape shape) {
201203
// Build the polygon...
202204
var polygon = island.toPolygon();
203205
// ... then make sure it is valid, fixing it if not.
204-
Geometry fixedPolygon;
206+
Geometry fixedGeometry;
205207
try {
206-
fixedPolygon = GeometryPrecisionReducer.reduce(GeometryFixer.fix(polygon), precisionModel);
208+
fixedGeometry = GeometryPrecisionReducer.reduce(GeometryFixer.fix(polygon), precisionModel);
207209
} catch (Throwable t) {
208210
log.error("Failure while reducing polygon", t);
209211
continue;
210212
}
211213

212-
switch (fixedPolygon) {
213-
case Polygon p -> polygons.add(p);
214-
case MultiPolygon mp -> {
215-
for (var n = 0; n < mp.getNumGeometries(); ++n) {
216-
polygons.add((Polygon) mp.getGeometryN(n));
214+
/*
215+
* Although `fixedGeometry` is usually a `Polygon` or a `MultiPolygon`, it _could_ be some
216+
* other `Geometry` or even an arbitrary `GeometryCollection`. We can only work with polygonal
217+
* elements, so we first need to flatten our geometry to a list and then pick out only the
218+
* polygons.
219+
*/
220+
221+
var flattened = new ArrayList<Geometry>();
222+
flatten(fixedGeometry, flattened::add);
223+
224+
for (var geometry : flattened) {
225+
if (geometry instanceof Polygon p) {
226+
if (!p.isEmpty()) {
227+
polygons.add(p);
217228
}
229+
} else {
230+
log.error(
231+
"Found unexpected geometry after fixing polygon: {}. Skipping", geometry.getClass());
218232
}
219-
default ->
220-
log.error(
221-
"Found unexpected geometry after fixing polygon: {}. Skipping",
222-
fixedPolygon.getClass());
223233
}
224234
}
225235

226-
polygons.removeIf(Polygon::isEmpty);
227-
228236
return polygons;
229237
}
230238

239+
/**
240+
* Recursively flattens a geometry by replacing any collection components with their children.
241+
*
242+
* <p>Any discovered non-collection geometry is added to {@code output}.
243+
*
244+
* @param geometry The geometry to flatten. If this is not a collection,
245+
* @param output The place to write the non-collection geometries.
246+
*/
247+
public static void flatten(Geometry geometry, Consumer<Geometry> output) {
248+
for (var n = 0; n < geometry.getNumGeometries(); ++n) {
249+
var child = geometry.getGeometryN(n);
250+
if (child instanceof GeometryCollection childCollection) {
251+
// Recurse on collections so we get their children.
252+
flatten(childCollection, output);
253+
} else {
254+
output.accept(child);
255+
}
256+
}
257+
}
258+
231259
public static Point2D coordinateToPoint2D(Coordinate coordinate) {
232260
return new Point2D.Double(coordinate.getX(), coordinate.getY());
233261
}

common/src/test/java/net/rptools/lib/GeometryUtilTest.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,109 @@ private static Iterable<Arguments> areaProvider() {
835835
}
836836
// endregion
837837

838+
// region A square "donut" with a defective bump-out at the top-right corner.
839+
{
840+
final var path = new Path2D.Double();
841+
path.moveTo(1800.0000000000000000, 2000.0000000000000000);
842+
path.lineTo(1800.0000000000000000, 2100.0000000000000000);
843+
path.lineTo(1700.0000000000000000, 2100.0000000000000000);
844+
path.lineTo(1700.0000000000000000, 2000.0000000000000000);
845+
path.closePath();
846+
847+
path.moveTo(1900.0000000000000000, 1900.0000000000000000);
848+
path.lineTo(1600.0000000000000000, 1900.0000000000000000);
849+
path.lineTo(1600.0000000000000000, 2200.0000000000000000);
850+
path.lineTo(1900.0000000000000000, 2200.0000000000000000);
851+
path.lineTo(1900.0000000000000000, 2000.0000000000000000);
852+
path.lineTo(1991.8686523437500000, 2000.0000000000000000);
853+
path.lineTo(1991.9971923828125000, 1998.3947753906250000);
854+
path.lineTo(1991.2067871093750000, 1991.4079589843750000);
855+
path.lineTo(1990.7553710937500000, 1984.2075195312500000);
856+
path.lineTo(1990.1141357421875000, 1981.7492675781250000);
857+
path.lineTo(1989.8403320312500000, 1979.3294677734375000);
858+
path.lineTo(1987.7099609375000000, 1972.5334472656250000);
859+
path.lineTo(1985.9122314453125000, 1965.6418457031250000);
860+
path.lineTo(1984.8612060546875000, 1963.4451904296875000);
861+
path.lineTo(1984.1013183593750000, 1961.0209960937500000);
862+
path.lineTo(1980.6657714843750000, 1954.6768798828125000);
863+
path.lineTo(1977.6309814453125000, 1948.3341064453125000);
864+
path.lineTo(1976.1545410156250000, 1946.3464355468750000);
865+
path.lineTo(1974.8978271484375000, 1944.0256347656250000);
866+
path.lineTo(1970.4443359375000000, 1938.6584472656250000);
867+
path.lineTo(1966.1066894531250000, 1932.8183593750000000);
868+
path.lineTo(1964.0657958984375000, 1930.9709472656250000);
869+
path.lineTo(1962.3472900390625000, 1928.8999023437500000);
870+
path.lineTo(1957.2095947265625000, 1924.7651367187500000);
871+
path.lineTo(1951.5350341796875000, 1919.6286621093750000);
872+
path.lineTo(1948.8482666015625000, 1918.0358886718750000);
873+
path.lineTo(1948.2611083984375000, 1917.5633544921875000);
874+
path.lineTo(1948.2611055629925000, 1917.5633569530470000);
875+
path.lineTo(1948.2611055629923000, 1917.5633569530470000);
876+
path.lineTo(1946.5672607421875000, 1916.2001953125000000);
877+
path.lineTo(1945.5327255706209000, 1915.6659069632760000);
878+
path.lineTo(1945.5327231053059000, 1915.6659038816322000);
879+
path.lineTo(1945.5327231053063000, 1915.6659038816322000);
880+
path.lineTo(1940.7324218750000000, 1913.1867675781250000);
881+
path.lineTo(1940.7324147182750000, 1913.1867740816188000);
882+
path.lineTo(1940.7324147182755000, 1913.1867740816188000);
883+
path.lineTo(1940.2384033203125000, 1912.9316406250000000);
884+
path.lineTo(1934.1110839843750000, 1909.2990722656250000);
885+
path.lineTo(1914.0823129507212000, 1900.0000000000000000);
886+
path.closePath();
887+
888+
final var area = new Area(path);
889+
890+
final var polygon =
891+
createPrecisePolygon(
892+
new Coordinate[] {
893+
new Coordinate(1900, 2200),
894+
new Coordinate(1900, 2000),
895+
new Coordinate(1991.86865, 2000),
896+
new Coordinate(1991.99719, 1998.39478),
897+
new Coordinate(1991.20679, 1991.40796),
898+
new Coordinate(1990.75537, 1984.20752),
899+
new Coordinate(1990.11414, 1981.74927),
900+
new Coordinate(1989.84033, 1979.32947),
901+
new Coordinate(1987.70996, 1972.53345),
902+
new Coordinate(1985.91223, 1965.64185),
903+
new Coordinate(1984.86121, 1963.44519),
904+
new Coordinate(1984.10132, 1961.021),
905+
new Coordinate(1980.66577, 1954.67688),
906+
new Coordinate(1977.63098, 1948.33411),
907+
new Coordinate(1976.15454, 1946.34644),
908+
new Coordinate(1974.89783, 1944.02563),
909+
new Coordinate(1970.44434, 1938.65845),
910+
new Coordinate(1966.10669, 1932.81836),
911+
new Coordinate(1964.0658, 1930.97095),
912+
new Coordinate(1962.34729, 1928.8999),
913+
new Coordinate(1957.20959, 1924.76514),
914+
new Coordinate(1951.53503, 1919.62866),
915+
new Coordinate(1948.84827, 1918.03589),
916+
new Coordinate(1948.26111, 1917.56336),
917+
new Coordinate(1946.56726, 1916.2002),
918+
new Coordinate(1945.53273, 1915.66591),
919+
new Coordinate(1945.53272, 1915.6659),
920+
new Coordinate(1940.73242, 1913.18677),
921+
new Coordinate(1940.73241, 1913.18677),
922+
new Coordinate(1940.2384, 1912.93164),
923+
new Coordinate(1934.11108, 1909.29907),
924+
new Coordinate(1914.08231, 1900),
925+
new Coordinate(1600, 1900),
926+
new Coordinate(1600, 2200),
927+
new Coordinate(1900, 2200),
928+
},
929+
new Coordinate[] {
930+
new Coordinate(1700, 2100),
931+
new Coordinate(1700, 2000),
932+
new Coordinate(1800, 2000),
933+
new Coordinate(1800, 2100),
934+
new Coordinate(1700, 2100),
935+
});
936+
937+
argumentsList.add(argumentSet("Point-like reduced geometry", area, List.of(polygon)));
938+
}
939+
// endregion
940+
838941
return argumentsList;
839942
}
840943

0 commit comments

Comments
 (0)