diff --git a/console/src/main/java/fiftyone/ipintelligence/examples/console/areas/Calculations.java b/console/src/main/java/fiftyone/ipintelligence/examples/console/areas/Calculations.java index 9a774ad..cd1053b 100644 --- a/console/src/main/java/fiftyone/ipintelligence/examples/console/areas/Calculations.java +++ b/console/src/main/java/fiftyone/ipintelligence/examples/console/areas/Calculations.java @@ -25,6 +25,8 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.TopologyException; import org.locationtech.jts.io.WKTReader; import org.locationtech.proj4j.CoordinateTransform; @@ -163,10 +165,10 @@ private static double getArea(Geometry geo, Rectangle rectangle) { } else { for (int i = 0; i < intersect.getNumGeometries(); i++) { - if (geo.getGeometryN(i).getArea() > 0) { + if (intersect.getGeometryN(i).getArea() > 0) { area += getArea( geo, - geo.getGeometryN(i), + intersect.getGeometryN(i), rectangle.getTransformation()); } } @@ -197,22 +199,49 @@ private static double getArea( private static Geometry transformGeometry( Geometry geometry, CoordinateTransform transform) { - GeometryFactory factory = geometry.getFactory(); - Coordinate[] coordinates = new Coordinate[ - geometry.getCoordinates().length]; + // A multipolygon or collection can arrive holding a single piece, + // for example from the TopologyException fallback or the intersection + // of a collection. Each polygon in it is transformed on its own. + if (geometry instanceof Polygon == false) { + List parts = new ArrayList<>(); + for (int i = 0; i < geometry.getNumGeometries(); i++) { + Geometry part = geometry.getGeometryN(i); + if (part.getDimension() == 2) { + parts.add(transformGeometry(part, transform)); + } + } + return geometry.getFactory().buildGeometry(parts); + } + // The outer boundary and each hole are separate rings. Joining their + // points into one ring does not close, so they are handled apart. + Polygon polygon = (Polygon) geometry; + LinearRing[] holes = new LinearRing[polygon.getNumInteriorRing()]; + for (int i = 0; i < holes.length; i++) { + holes[i] = transformRing(polygon.getInteriorRingN(i), transform); + } + return geometry.getFactory().createPolygon( + transformRing(polygon.getExteriorRing(), transform), + holes); + } + + private static LinearRing transformRing( + LinearRing ring, + CoordinateTransform transform) { + Coordinate[] source = ring.getCoordinates(); + Coordinate[] coordinates = new Coordinate[source.length]; for (int i = 0; i < coordinates.length; i++) { ProjCoordinate transformed = transform.transform( new ProjCoordinate( - geometry.getCoordinates()[i].getX(), - geometry.getCoordinates()[i].getY()), + source[i].getX(), + source[i].getY()), new ProjCoordinate()); coordinates[i] = new Coordinate( transformed.x, transformed.y); } - return factory.createPolygon(coordinates); + return ring.getFactory().createLinearRing(coordinates); } /** diff --git a/console/src/test/java/fiftyone/ipintelligence/examples/console/areas/CalculationsTest.java b/console/src/test/java/fiftyone/ipintelligence/examples/console/areas/CalculationsTest.java index 3056e12..41503c0 100644 --- a/console/src/test/java/fiftyone/ipintelligence/examples/console/areas/CalculationsTest.java +++ b/console/src/test/java/fiftyone/ipintelligence/examples/console/areas/CalculationsTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Polygon; import static org.junit.Assert.assertEquals; @@ -98,6 +99,51 @@ public void testWktPolygon() throws Exception { assertEquals(1, result.getGeometries()); } + @Test + public void testPolygonWithHole() throws Exception { + Polygon outer = createRectangle(0, 51, 1); + Polygon hole = createRectangle(0.25, 51.25, 0.5); + Polygon withHole = factory.createPolygon( + outer.getExteriorRing(), + new LinearRing[]{hole.getExteriorRing()}); + assertAreaEquals( + Calculations.getAreas(outer, 0, 0).getSquareKms() - + Calculations.getAreas(hole, 0, 0).getSquareKms(), + Calculations.getAreas(withHole, 0, 0).getSquareKms()); + } + + @Test + public void testPolygonSplitByGridCell() throws Exception { + // A U shape whose arms cross into the next grid cell, where they + // are two separate pieces. + Polygon shape = factory.createPolygon(new Coordinate[]{ + new Coordinate(0.2, 50.5), + new Coordinate(0.8, 50.5), + new Coordinate(0.8, 51.5), + new Coordinate(0.6, 51.5), + new Coordinate(0.6, 50.7), + new Coordinate(0.4, 50.7), + new Coordinate(0.4, 51.5), + new Coordinate(0.2, 51.5), + new Coordinate(0.2, 50.5)}); + Polygon whole = factory.createPolygon(new Coordinate[]{ + new Coordinate(0.2, 50.5), + new Coordinate(0.8, 50.5), + new Coordinate(0.8, 51.5), + new Coordinate(0.2, 51.5), + new Coordinate(0.2, 50.5)}); + Polygon gap = factory.createPolygon(new Coordinate[]{ + new Coordinate(0.4, 50.7), + new Coordinate(0.6, 50.7), + new Coordinate(0.6, 51.5), + new Coordinate(0.4, 51.5), + new Coordinate(0.4, 50.7)}); + assertAreaEquals( + Calculations.getAreas(whole, 0, 0).getSquareKms() - + Calculations.getAreas(gap, 0, 0).getSquareKms(), + Calculations.getAreas(shape, 0, 0).getSquareKms()); + } + @Test public void testContains() throws Exception { Result result = Calculations.getAreas(