|
23 | 23 | import java.util.Comparator; |
24 | 24 | import java.util.List; |
25 | 25 | import java.util.Objects; |
| 26 | +import java.util.function.Consumer; |
26 | 27 | import org.apache.logging.log4j.LogManager; |
27 | 28 | import org.apache.logging.log4j.Logger; |
28 | 29 | import org.locationtech.jts.algorithm.InteriorPointArea; |
|
33 | 34 | import org.locationtech.jts.geom.CoordinateArrays; |
34 | 35 | import org.locationtech.jts.geom.Envelope; |
35 | 36 | import org.locationtech.jts.geom.Geometry; |
| 37 | +import org.locationtech.jts.geom.GeometryCollection; |
36 | 38 | import org.locationtech.jts.geom.GeometryFactory; |
37 | 39 | import org.locationtech.jts.geom.LinearRing; |
38 | 40 | import org.locationtech.jts.geom.Location; |
@@ -201,33 +203,59 @@ public static Collection<Polygon> toJtsPolygons(Shape shape) { |
201 | 203 | // Build the polygon... |
202 | 204 | var polygon = island.toPolygon(); |
203 | 205 | // ... then make sure it is valid, fixing it if not. |
204 | | - Geometry fixedPolygon; |
| 206 | + Geometry fixedGeometry; |
205 | 207 | try { |
206 | | - fixedPolygon = GeometryPrecisionReducer.reduce(GeometryFixer.fix(polygon), precisionModel); |
| 208 | + fixedGeometry = GeometryPrecisionReducer.reduce(GeometryFixer.fix(polygon), precisionModel); |
207 | 209 | } catch (Throwable t) { |
208 | 210 | log.error("Failure while reducing polygon", t); |
209 | 211 | continue; |
210 | 212 | } |
211 | 213 |
|
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); |
217 | 228 | } |
| 229 | + } else { |
| 230 | + log.error( |
| 231 | + "Found unexpected geometry after fixing polygon: {}. Skipping", geometry.getClass()); |
218 | 232 | } |
219 | | - default -> |
220 | | - log.error( |
221 | | - "Found unexpected geometry after fixing polygon: {}. Skipping", |
222 | | - fixedPolygon.getClass()); |
223 | 233 | } |
224 | 234 | } |
225 | 235 |
|
226 | | - polygons.removeIf(Polygon::isEmpty); |
227 | | - |
228 | 236 | return polygons; |
229 | 237 | } |
230 | 238 |
|
| 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 | + |
231 | 259 | public static Point2D coordinateToPoint2D(Coordinate coordinate) { |
232 | 260 | return new Point2D.Double(coordinate.getX(), coordinate.getY()); |
233 | 261 | } |
|
0 commit comments