diff --git a/modules/app/src/main/java/org/locationtech/jtstest/function/CoverageFunctions.java b/modules/app/src/main/java/org/locationtech/jtstest/function/CoverageFunctions.java index 74f52f6a1b..8b9d01aa20 100644 --- a/modules/app/src/main/java/org/locationtech/jtstest/function/CoverageFunctions.java +++ b/modules/app/src/main/java/org/locationtech/jtstest/function/CoverageFunctions.java @@ -64,6 +64,13 @@ public static Geometry simplify(Geometry coverage, double tolerance) { Geometry[] result = CoverageSimplifier.simplify(cov, tolerance); return FunctionsUtil.buildGeometry(result); } + + @Metadata(description="Simplify a coverage by providing one tolerance per geometry") + public static Geometry simplifyDynamicTolerance(Geometry coverage, String tolerances) { + Geometry[] cov = toGeometryArray(coverage); + Geometry[] result = CoverageSimplifier.simplify(cov, tolerances); + return FunctionsUtil.buildGeometry(result); + } @Metadata(description="Simplify inner edges of a coverage") public static Geometry simplifyinner(Geometry coverage, double tolerance) { @@ -71,6 +78,13 @@ public static Geometry simplifyinner(Geometry coverage, double tolerance) { Geometry[] result = CoverageSimplifier.simplifyInner(cov, tolerance); return FunctionsUtil.buildGeometry(result); } + + @Metadata(description="Simplify inner edges of a coverage by providing one tolerance per geometry") + public static Geometry simplifyinnerDynamicTolerance(Geometry coverage, String tolerances) { + Geometry[] cov = toGeometryArray(coverage); + Geometry[] result = CoverageSimplifier.simplifyInner(cov, tolerances); + return FunctionsUtil.buildGeometry(result); + } static Geometry extractPolygons(Geometry geom) { List components = PolygonExtracter.getPolygons(geom); diff --git a/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageEdge.java b/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageEdge.java index 2208200b29..a3969bcdb4 100644 --- a/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageEdge.java +++ b/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageEdge.java @@ -30,15 +30,15 @@ */ class CoverageEdge { - public static CoverageEdge createEdge(Coordinate[] ring) { + public static CoverageEdge createEdge(Coordinate[] ring, double tolerance) { Coordinate[] pts = extractEdgePoints(ring, 0, ring.length - 1); - CoverageEdge edge = new CoverageEdge(pts, true); + CoverageEdge edge = new CoverageEdge(pts, true, tolerance); return edge; } - public static CoverageEdge createEdge(Coordinate[] ring, int start, int end) { + public static CoverageEdge createEdge(Coordinate[] ring, int start, int end, double tolerance) { Coordinate[] pts = extractEdgePoints(ring, start, end); - CoverageEdge edge = new CoverageEdge(pts, false); + CoverageEdge edge = new CoverageEdge(pts, false, tolerance); return edge; } @@ -137,9 +137,12 @@ else if (i > pts.length - 1) { private int ringCount = 0; private boolean isFreeRing = true; - public CoverageEdge(Coordinate[] pts, boolean isFreeRing) { + private double tolerance = -1; + + public CoverageEdge(Coordinate[] pts, boolean isFreeRing, double tolerance) { this.pts = pts; this.isFreeRing = isFreeRing; + this.tolerance = tolerance; } public void incRingCount() { @@ -160,6 +163,10 @@ public boolean isFreeRing() { return isFreeRing; } + public double getTolerance() { return tolerance; } + + public void setTolerance(double tolerance) { this.tolerance = tolerance; } + public void setCoordinates(Coordinate[] pts) { this.pts = pts; } diff --git a/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageRingEdges.java b/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageRingEdges.java index ba5f852219..13b12d27b6 100644 --- a/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageRingEdges.java +++ b/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageRingEdges.java @@ -51,21 +51,30 @@ class CoverageRingEdges { * Create a new instance for a given coverage. * * @param coverage the set of polygonal geometries in the coverage + * @param tolerances the simplification tolerances for each geometry * @return the edges of the coverage */ + public static CoverageRingEdges create(Geometry[] coverage, List tolerances) { + CoverageRingEdges edges = new CoverageRingEdges(coverage, tolerances); + return edges; + } + public static CoverageRingEdges create(Geometry[] coverage) { - CoverageRingEdges edges = new CoverageRingEdges(coverage); + CoverageRingEdges edges = new CoverageRingEdges(coverage, new ArrayList(0)); return edges; } private Geometry[] coverage; private Map> ringEdgesMap; private List edges; - - public CoverageRingEdges(Geometry[] coverage) { + + private List coverageTolerances; + + public CoverageRingEdges(Geometry[] coverage, List tolerances) { this.coverage = coverage; ringEdgesMap = new HashMap>(); edges = new ArrayList(); + coverageTolerances = tolerances; build(); } @@ -94,7 +103,8 @@ private void build() { Set boundarySegs = CoverageBoundarySegmentFinder.findBoundarySegments(coverage); nodes.addAll(findBoundaryNodes(boundarySegs)); HashMap uniqueEdgeMap = new HashMap(); - for (Geometry geom : coverage) { + for (int icoverage = 0; icoverage < coverage.length; icoverage++) { + Geometry geom = coverage[icoverage]; for (int ipoly = 0; ipoly < geom.getNumGeometries(); ipoly++) { Polygon poly = (Polygon) geom.getGeometryN(ipoly); @@ -104,23 +114,23 @@ private void build() { //-- extract shell LinearRing shell = poly.getExteriorRing(); - addRingEdges(shell, nodes, boundarySegs, uniqueEdgeMap); + addRingEdges(icoverage, shell, nodes, boundarySegs, uniqueEdgeMap); //-- extract holes for (int ihole = 0; ihole < poly.getNumInteriorRing(); ihole++) { LinearRing hole = poly.getInteriorRingN(ihole); //-- skip empty rings. Missing rings are copied in result if (hole.isEmpty()) continue; - addRingEdges(hole, nodes, boundarySegs, uniqueEdgeMap); + addRingEdges(icoverage, hole, nodes, boundarySegs, uniqueEdgeMap); } } } } - private void addRingEdges(LinearRing ring, Set nodes, Set boundarySegs, + private void addRingEdges(int coverageId, LinearRing ring, Set nodes, Set boundarySegs, HashMap uniqueEdgeMap) { addBoundaryInnerNodes(ring, boundarySegs, nodes); - List ringEdges = extractRingEdges(ring, uniqueEdgeMap, nodes); + List ringEdges = extractRingEdges(coverageId, ring, uniqueEdgeMap, nodes); if (ringEdges != null) ringEdgesMap.put(ring, ringEdges); } @@ -149,8 +159,8 @@ private void addBoundaryInnerNodes(LinearRing ring, Set boundarySeg } } - private List extractRingEdges(LinearRing ring, - HashMap uniqueEdgeMap, + private List extractRingEdges(int coverageId, LinearRing ring, + HashMap uniqueEdgeMap, Set nodes) { // System.out.println(ring); List ringEdges = new ArrayList(); @@ -164,7 +174,7 @@ private List extractRingEdges(LinearRing ring, int first = findNextNodeIndex(pts, -1, nodes); if (first < 0) { //-- ring does not contain a node, so edge is entire ring - CoverageEdge edge = createEdge(pts, uniqueEdgeMap); + CoverageEdge edge = createEdge(coverageId, pts, uniqueEdgeMap); ringEdges.add(edge); } else { @@ -172,7 +182,7 @@ private List extractRingEdges(LinearRing ring, int end = start; do { end = findNextNodeIndex(pts, start, nodes); - CoverageEdge edge = createEdge(pts, start, end, uniqueEdgeMap); + CoverageEdge edge = createEdge(coverageId, pts, start, end, uniqueEdgeMap); // System.out.println(ringEdges.size() + " : " + edge); ringEdges.add(edge); start = end; @@ -181,14 +191,18 @@ private List extractRingEdges(LinearRing ring, return ringEdges; } - private CoverageEdge createEdge(Coordinate[] ring, HashMap uniqueEdgeMap) { + private CoverageEdge createEdge(int coverageId, Coordinate[] ring, HashMap uniqueEdgeMap) { CoverageEdge edge; LineSegment edgeKey = CoverageEdge.key(ring); if (uniqueEdgeMap.containsKey(edgeKey)) { edge = uniqueEdgeMap.get(edgeKey); + if (!coverageTolerances.isEmpty()){ + edge.setTolerance((edge.getTolerance() < coverageTolerances.get(coverageId)) ? edge.getTolerance() : coverageTolerances.get(coverageId)); + } } else { - edge = CoverageEdge.createEdge(ring); + double tolerance = coverageTolerances.isEmpty() ? -1 : coverageTolerances.get(coverageId); + edge = CoverageEdge.createEdge(ring, tolerance); uniqueEdgeMap.put(edgeKey, edge); edges.add(edge); } @@ -196,14 +210,18 @@ private CoverageEdge createEdge(Coordinate[] ring, HashMap uniqueEdgeMap) { + private CoverageEdge createEdge(int coverageId, Coordinate[] ring, int start, int end, HashMap uniqueEdgeMap) { CoverageEdge edge; LineSegment edgeKey = (end == start) ? CoverageEdge.key(ring) : CoverageEdge.key(ring, start, end); if (uniqueEdgeMap.containsKey(edgeKey)) { edge = uniqueEdgeMap.get(edgeKey); + if (!coverageTolerances.isEmpty()){ + edge.setTolerance((edge.getTolerance() < coverageTolerances.get(coverageId)) ? edge.getTolerance() : coverageTolerances.get(coverageId)); + } } else { - edge = CoverageEdge.createEdge(ring, start, end); + double tolerance = coverageTolerances.isEmpty() ? -1 : coverageTolerances.get(coverageId); + edge = CoverageEdge.createEdge(ring, start, end, tolerance); uniqueEdgeMap.put(edgeKey, edge); edges.add(edge); } @@ -278,7 +296,7 @@ private Set findBoundaryNodes(Set boundarySegments) { /** * Recreates the polygon coverage from the current edge values. - * + * * @return an array of polygonal geometries representing the coverage */ public Geometry[] buildCoverage() { diff --git a/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageSimplifier.java b/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageSimplifier.java index 6a0134e814..b24a24f374 100644 --- a/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageSimplifier.java +++ b/modules/core/src/main/java/org/locationtech/jts/coverage/CoverageSimplifier.java @@ -11,8 +11,11 @@ */ package org.locationtech.jts.coverage; +import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; import java.util.List; +import java.util.stream.Collectors; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; @@ -64,7 +67,37 @@ public static Geometry[] simplify(Geometry[] coverage, double tolerance) { CoverageSimplifier simplifier = new CoverageSimplifier(coverage); return simplifier.simplify(tolerance); } - + + /** + * Simplifies the boundaries of a set of polygonal geometries forming a coverage, + * preserving the coverage topology. + * + * @param coverage a set of polygonal geometries forming a coverage + * @param tolerances the simplification tolerances for each coverage + * @return the simplified polygons + */ + public static Geometry[] simplify(Geometry[] coverage, List tolerances) { + CoverageSimplifier simplifier = new CoverageSimplifier(coverage); + return simplifier.simplify(tolerances); + } + + /** + * Simplifies the boundaries of a set of polygonal geometries forming a coverage, + * preserving the coverage topology. + * + * @param coverage a set of polygonal geometries forming a coverage + * @param tolerances comma-separated string of simplification tolerances for each coverage + * @return the simplified polygons + */ + public static Geometry[] simplify(Geometry[] coverage, String tolerances) { + List tolerancesList = Arrays.stream(tolerances.split(",")) + .map(Double::parseDouble) + .collect(Collectors.toList()); + + CoverageSimplifier simplifier = new CoverageSimplifier(coverage); + return simplifier.simplify(tolerancesList); + } + /** * Simplifies the inner boundaries of a set of polygonal geometries forming a coverage, * preserving the coverage topology. @@ -78,6 +111,38 @@ public static Geometry[] simplifyInner(Geometry[] coverage, double tolerance) { CoverageSimplifier simplifier = new CoverageSimplifier(coverage); return simplifier.simplifyInner(tolerance); } + + /** + * Simplifies the inner boundaries of a set of polygonal geometries forming a coverage, + * preserving the coverage topology. + * Edges which form the exterior boundary of the coverage are left unchanged. + * + * @param coverage a set of polygonal geometries forming a coverage + * @param tolerances the simplification tolerances for each coverage + * @return the simplified polygons + */ + public static Geometry[] simplifyInner(Geometry[] coverage, List tolerances) { + CoverageSimplifier simplifier = new CoverageSimplifier(coverage); + return simplifier.simplifyInner(tolerances); + } + + /** + * Simplifies the inner boundaries of a set of polygonal geometries forming a coverage, + * preserving the coverage topology. + * Edges which form the exterior boundary of the coverage are left unchanged. + * + * @param coverage a set of polygonal geometries forming a coverage + * @param tolerances comma-separated string of simplification tolerances for each coverage + * @return the simplified polygons + */ + public static Geometry[] simplifyInner(Geometry[] coverage, String tolerances) { + List tolerancesList = Arrays.stream(tolerances.split(",")) + .map(Double::parseDouble) + .collect(Collectors.toList()); + + CoverageSimplifier simplifier = new CoverageSimplifier(coverage); + return simplifier.simplifyInner(tolerancesList); + } private Geometry[] input; private GeometryFactory geomFactory; @@ -104,7 +169,25 @@ public Geometry[] simplify(double tolerance) { Geometry[] result = cov.buildCoverage(); return result; } - + + /** + * Computes the simplified coverage, preserving the coverage topology. + * + * @param tolerances the simplification tolerances for each coverage + * @return the simplified polygons + */ + public Geometry[] simplify(List tolerances) { + if (input.length != tolerances.size()){ + throw new IllegalArgumentException( + String.format("Mismatch between provided tolerances (%d) and input geometry count (%d)", tolerances.size(), input.length)); + } + + CoverageRingEdges cov = CoverageRingEdges.create(input, tolerances); + simplifyEdges(cov.getEdges(), null); + Geometry[] result = cov.buildCoverage(); + return result; + } + /** * Computes the inner-boundary simplified coverage, * preserving the coverage topology, @@ -124,6 +207,30 @@ public Geometry[] simplifyInner(double tolerance) { return result; } + /** + * Computes the inner-boundary simplified coverage, + * preserving the coverage topology, + * and leaving outer boundary edges unchanged. + * + * @param tolerances the simplification tolerances for each coverage + * @return the simplified polygons + */ + public Geometry[] simplifyInner(List tolerances) { + if (input.length != tolerances.size()){ + throw new IllegalArgumentException( + String.format("Mismatch between provided tolerances (%d) and input geometry count (%d)", tolerances.size(), input.length)); + } + + CoverageRingEdges cov = CoverageRingEdges.create(input, tolerances); + List innerEdges = cov.selectEdges(2); + List outerEdges = cov.selectEdges(1); + MultiLineString constraintEdges = CoverageEdge.createLines(outerEdges, geomFactory); + + simplifyEdges(innerEdges, constraintEdges); + Geometry[] result = cov.buildCoverage(); + return result; + } + private void simplifyEdges(List edges, MultiLineString constraints, double tolerance) { MultiLineString lines = CoverageEdge.createLines(edges, geomFactory); BitSet freeRings = getFreeRings(edges); @@ -133,6 +240,16 @@ private void simplifyEdges(List edges, MultiLineString constraints setCoordinates(edges, linesSimp); } + private void simplifyEdges(List edges, MultiLineString constraints) { + MultiLineString lines = CoverageEdge.createLines(edges, geomFactory); + BitSet freeRings = getFreeRings(edges); + List tolerances = getTolerances(edges); + MultiLineString linesSimp = TPVWSimplifier.simplify(lines, freeRings, constraints, tolerances); + //Assert: mlsSimp.getNumGeometries = edges.length + + setCoordinates(edges, linesSimp); + } + private void setCoordinates(List edges, MultiLineString lines) { for (int i = 0; i < edges.size(); i++) { edges.get(i).setCoordinates(lines.getGeometryN(i).getCoordinates()); @@ -146,5 +263,14 @@ private BitSet getFreeRings(List edges) { } return freeRings; } - + + private List getTolerances(List edges) + { + List tolerances = new ArrayList(edges.size()); + for (CoverageEdge edge : edges) { + tolerances.add(edge.getTolerance()); + } + return tolerances; + } + } diff --git a/modules/core/src/main/java/org/locationtech/jts/coverage/TPVWSimplifier.java b/modules/core/src/main/java/org/locationtech/jts/coverage/TPVWSimplifier.java index 64f37913d6..ab2448e60e 100644 --- a/modules/core/src/main/java/org/locationtech/jts/coverage/TPVWSimplifier.java +++ b/modules/core/src/main/java/org/locationtech/jts/coverage/TPVWSimplifier.java @@ -15,6 +15,7 @@ import java.util.BitSet; import java.util.List; import java.util.PriorityQueue; +import java.util.stream.Collectors; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; @@ -58,6 +59,19 @@ public static MultiLineString simplify(MultiLineString lines, double distanceTol MultiLineString result = (MultiLineString) simp.simplify(); return result; } + + /** + * Simplifies a set of lines, preserving the topology of the lines. + * + * @param lines the lines to simplify + * @param distanceTolerances the simplification tolerances for each line string + * @return the simplified lines + */ + public static MultiLineString simplify(MultiLineString lines, List distanceTolerances) { + TPVWSimplifier simp = new TPVWSimplifier(lines, distanceTolerances); + MultiLineString result = (MultiLineString) simp.simplify(); + return result; + } /** * Simplifies a set of lines, preserving the topology of the lines between @@ -80,16 +94,45 @@ public static MultiLineString simplify(MultiLineString lines, BitSet freeRings, MultiLineString result = (MultiLineString) simp.simplify(); return result; } - + + /** + * Simplifies a set of lines, preserving the topology of the lines between + * themselves and a set of linear constraints. + * The endpoints of lines are preserved. + * The endpoint of rings are preserved as well, unless + * the ring is indicated as "free" via a bit flag with the same index. + * + * @param lines the lines to simplify + * @param freeRings flags indicating which ring edges do not have node endpoints + * @param constraintLines the linear constraints (may be null) + * @param distanceTolerances the simplification tolerances for each line string + * @return the simplified lines + */ + public static MultiLineString simplify(MultiLineString lines, BitSet freeRings, + MultiLineString constraintLines, List distanceTolerances) { + TPVWSimplifier simp = new TPVWSimplifier(lines, distanceTolerances); + simp.setFreeRingIndices(freeRings); + simp.setConstraints(constraintLines); + MultiLineString result = (MultiLineString) simp.simplify(); + return result; + } + private MultiLineString inputLines; private BitSet isFreeRing; - private double areaTolerance; + private List areaTolerances; private GeometryFactory geomFactory; private MultiLineString constraintLines = null; private TPVWSimplifier(MultiLineString lines, double distanceTolerance) { this.inputLines = lines; - this.areaTolerance = distanceTolerance * distanceTolerance; + this.areaTolerances = new ArrayList(1); + this.areaTolerances.add(distanceTolerance * distanceTolerance); + geomFactory = inputLines.getFactory(); + } + + private TPVWSimplifier(MultiLineString lines, List distanceTolerances) { + this.inputLines = lines; + this.areaTolerances = distanceTolerances.stream().map(x -> x * x).collect(Collectors.toList()); geomFactory = inputLines.getFactory(); } @@ -123,9 +166,11 @@ private List createEdges(MultiLineString lines, BitSet isFreeRing) { List edges = new ArrayList(); if (lines == null) return edges; + for (int i = 0 ; i < lines.getNumGeometries(); i++) { LineString line = (LineString) lines.getGeometryN(i); boolean isFree = isFreeRing == null ? false : isFreeRing.get(i); + double areaTolerance = (areaTolerances.isEmpty()) ? -1 : ((areaTolerances.size() == 1) ? areaTolerances.get(0) : areaTolerances.get(i)); edges.add(new Edge(line, isFree, areaTolerance)); } return edges; diff --git a/modules/core/src/test/java/org/locationtech/jts/coverage/CoverageSimplifierTest.java b/modules/core/src/test/java/org/locationtech/jts/coverage/CoverageSimplifierTest.java index a5822e956d..d75ce41ebb 100644 --- a/modules/core/src/test/java/org/locationtech/jts/coverage/CoverageSimplifierTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/coverage/CoverageSimplifierTest.java @@ -16,6 +16,11 @@ import junit.textui.TestRunner; import test.jts.GeometryTestCase; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; + public class CoverageSimplifierTest extends GeometryTestCase { public static void main(String args[]) { TestRunner.run(CoverageSimplifierTest.class); @@ -32,6 +37,13 @@ public void testNoopSimple2() { ); } + public void testNoopSimple2Dynamic() { + checkNoopDynamic(readArray( + "POLYGON ((100 100, 200 200, 300 100, 200 101, 100 100))", + "POLYGON ((150 0, 100 100, 200 101, 300 100, 250 0, 150 0))" ) + ); + } + public void testNoopSimple3() { checkNoop(readArray( "POLYGON ((100 300, 200 200, 100 200, 100 300))", @@ -40,6 +52,14 @@ public void testNoopSimple3() { ); } + public void testNoopSimple3Dynamic() { + checkNoopDynamic(readArray( + "POLYGON ((100 300, 200 200, 100 200, 100 300))", + "POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200))", + "POLYGON ((100 100, 200 100, 150 50, 100 100))" ) + ); + } + public void testNoopHole() { checkNoop(readArray( "POLYGON ((10 90, 90 90, 90 10, 10 10, 10 90), (20 80, 80 80, 80 20, 20 20, 20 80))", @@ -47,6 +67,13 @@ public void testNoopHole() { ); } + public void testNoopHoleDynamic() { + checkNoopDynamic(readArray( + "POLYGON ((10 90, 90 90, 90 10, 10 10, 10 90), (20 80, 80 80, 80 20, 20 20, 20 80))", + "POLYGON ((80 20, 20 20, 20 80, 80 80, 80 20))" ) + ); + } + public void testNoopMulti() { checkNoop(readArray( "MULTIPOLYGON (((10 10, 10 50, 50 50, 50 10, 10 10)), ((90 90, 90 50, 50 50, 50 90, 90 90)))", @@ -54,6 +81,12 @@ public void testNoopMulti() { ); } + public void testNoopMultiDynamic() { + checkNoopDynamic(readArray( + "MULTIPOLYGON (((10 10, 10 50, 50 50, 50 10, 10 10)), ((90 90, 90 50, 50 50, 50 90, 90 90)))", + "MULTIPOLYGON (((10 90, 50 90, 50 50, 10 50, 10 90)), ((90 10, 50 10, 50 50, 90 50, 90 10)))" ) + ); + } //--------------------------------------------- public void testRepeatedPointRemoved() { @@ -64,7 +97,18 @@ public void testRepeatedPointRemoved() { "POLYGON ((5 5, 5 9, 9 5, 5 5))" ) ); } - + + public void testRepeatedPointRemovedDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(2.0); + checkResultDynamic(readArray( + "POLYGON ((5 9, 6.5 6.5, 9 5, 5 5, 5 5, 5 9))" ), + tolerance, + readArray( + "POLYGON ((5 5, 5 9, 9 5, 5 5))" ) + ); + } + public void testRepeatedPointCollapseToLine() { checkResult(readArray( "MULTIPOLYGON (((10 10, 10 20, 20 19, 30 20, 30 10, 10 10)), ((10 30, 20 29, 30 30, 30 20, 20 19, 10 20, 10 30)), ((10 20, 20 19, 20 19, 10 20)))" ), @@ -73,7 +117,18 @@ public void testRepeatedPointCollapseToLine() { "MULTIPOLYGON (((10 20, 20 19, 30 20, 30 10, 10 10, 10 20)), ((30 20, 20 19, 10 20, 10 30, 30 30, 30 20)), ((10 20, 20 19, 10 20)))" ) ); } - + + public void testRepeatedPointCollapseToLineDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(5.0); + checkResultDynamic(readArray( + "MULTIPOLYGON (((10 10, 10 20, 20 19, 30 20, 30 10, 10 10)), ((10 30, 20 29, 30 30, 30 20, 20 19, 10 20, 10 30)), ((10 20, 20 19, 20 19, 10 20)))" ), + tolerance, + readArray( + "MULTIPOLYGON (((10 20, 20 19, 30 20, 30 10, 10 10, 10 20)), ((30 20, 20 19, 10 20, 10 30, 30 30, 30 20)), ((10 20, 20 19, 10 20)))" ) + ); + } + public void testRepeatedPointCollapseToPoint() { checkResult(readArray( "MULTIPOLYGON (((10 10, 10 20, 20 19, 30 20, 30 10, 10 10)), ((10 30, 20 29, 30 30, 30 20, 20 19, 10 20, 10 30)), ((20 19, 20 19, 20 19)))" ), @@ -82,6 +137,17 @@ public void testRepeatedPointCollapseToPoint() { "MULTIPOLYGON (((10 10, 10 20, 20 19, 30 20, 30 10, 10 10)), ((10 20, 10 30, 30 30, 30 20, 20 19, 10 20)), ((20 19, 20 19, 20 19)))" ) ); } + + public void testRepeatedPointCollapseToPointDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(5.0); + checkResultDynamic(readArray( + "MULTIPOLYGON (((10 10, 10 20, 20 19, 30 20, 30 10, 10 10)), ((10 30, 20 29, 30 30, 30 20, 20 19, 10 20, 10 30)), ((20 19, 20 19, 20 19)))" ), + tolerance, + readArray( + "MULTIPOLYGON (((10 10, 10 20, 20 19, 30 20, 30 10, 10 10)), ((10 20, 10 30, 30 30, 30 20, 20 19, 10 20)), ((20 19, 20 19, 20 19)))" ) + ); + } public void testRepeatedPointCollapseToPoint2() { checkResult(readArray( @@ -91,7 +157,18 @@ public void testRepeatedPointCollapseToPoint2() { "MULTIPOLYGON (((150 195, 200 200, 200 100, 100 100, 100 200, 150 195)), ((150 195, 150 195, 150 195, 150 195)))" ) ); } - + + public void testRepeatedPointCollapseToPoint2Dynamic() { + List tolerance = new ArrayList(1); + tolerance.add(40.0); + checkResultDynamic(readArray( + "MULTIPOLYGON (((100 200, 150 195, 200 200, 200 100, 100 100, 100 200)), ((150 195, 150 195, 150 195, 150 195)))" ), + tolerance, + readArray( + "MULTIPOLYGON (((150 195, 200 200, 200 100, 100 100, 100 200, 150 195)), ((150 195, 150 195, 150 195, 150 195)))" ) + ); + } + //--------------------------------------------- public void testSimple2() { @@ -105,6 +182,18 @@ public void testSimple2() { ); } + public void testSimple2Dynamic() { + List tolerances = Arrays.asList(1.0, 10.0); + checkResultDynamic(readArray( + "POLYGON ((100 100, 200 200, 300 100, 200 101, 100 100))", + "POLYGON ((150 0, 100 100, 200 101, 300 100, 250 0, 150 0))" ), + tolerances, + readArray( + "POLYGON ((100 100, 200 200, 300 100, 200 101, 100 100)),", + "POLYGON ((300 100, 200 101, 100 100, 150 0, 250 0, 300 100))") + ); + } + public void testMultiPolygons() { checkResult(readArray( "MULTIPOLYGON (((5 9, 2.5 7.5, 1 5, 5 5, 5 9)), ((5 5, 9 5, 7.5 2.5, 5 1, 5 5)))", @@ -115,6 +204,18 @@ public void testMultiPolygons() { "MULTIPOLYGON (((1 5, 5 5, 5 1, 1 5)), ((5 5, 5 9, 9 5, 5 5)))" ) ); } + + public void testMultiPolygonsDynamic() { + List tolerances = Arrays.asList(1.0, 3.0); + checkResultDynamic(readArray( + "MULTIPOLYGON (((5 9, 2.5 7.5, 1 5, 5 5, 5 9)), ((5 5, 9 5, 7.5 2.5, 5 1, 5 5)))", + "MULTIPOLYGON (((5 9, 6.5 6.5, 9 5, 5 5, 5 9)), ((1 5, 5 5, 5 1, 3.5 3.5, 1 5)))" ), + tolerances, + readArray( + "MULTIPOLYGON (((5 9, 2.5 7.5, 1 5, 5 5, 5 9)), ((5 5, 9 5, 7.5 2.5, 5 1, 5 5)))", + "MULTIPOLYGON (((5 9, 9 5, 5 5, 5 9)), ((1 5, 5 5, 5 1, 1 5)))") + ); + } public void testSingleRingNoCollapse() { checkResult(readArray( @@ -125,6 +226,17 @@ public void testSingleRingNoCollapse() { ); } + public void testSingleRingNoCollapseDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(100000.0); + checkResultDynamic(readArray( + "POLYGON ((10 50, 60 90, 70 50, 60 10, 10 50))" ), + tolerance, + readArray( + "POLYGON ((10 50, 60 90, 60 10, 10 50))" ) + ); + } + /** * Checks that a polygon on the edge of the coverage does not collapse * under maximal simplification @@ -140,6 +252,18 @@ public void testMultiEdgeRingNoCollapse() { ); } + public void testMultiEdgeRingNoCollapseDynamic() { + List tolerances = Arrays.asList(40.0, 40.0); + checkResultDynamic(readArray( + "POLYGON ((50 250, 200 200, 180 170, 200 150, 50 50, 50 250))", + "POLYGON ((200 200, 180 170, 200 150, 200 200))"), + tolerances, + readArray( + "POLYGON ((50 250, 200 200, 180 170, 200 150, 50 50, 50 250))", + "POLYGON ((200 200, 180 170, 200 150, 200 200))") + ); + } + public void testFilledHole() { checkResult(readArray( "POLYGON ((20 30, 20 80, 60 50, 80 20, 50 20, 20 30))", @@ -151,6 +275,18 @@ public void testFilledHole() { ); } + public void testFilledHoleDynamic() { + List tolerances = Arrays.asList(17.0, 28.0); + checkResultDynamic(readArray( + "POLYGON ((20 30, 20 80, 60 50, 80 20, 50 20, 20 30))", + "POLYGON ((10 90, 90 90, 90 10, 10 10, 10 90), (50 20, 20 30, 20 80, 60 50, 80 20, 50 20))" ), + tolerances, + readArray( + "POLYGON ((20 30, 20 80, 60 50, 80 20, 20 30))", + "POLYGON ((10 90, 90 90, 90 10, 10 10, 10 90), (20 30, 20 80, 60 50, 80 20, 20 30))") + ); + } + public void testTouchingHoles() { checkResult(readArray( "POLYGON (( 0 0, 0 11, 19 11, 19 0, 0 0 ), ( 4 5, 12 5, 12 6, 10 6, 10 8, 9 8, 9 9, 7 9, 7 8, 6 8, 6 6, 4 6, 4 5 ), ( 12 6, 14 6, 14 9, 13 9, 13 7, 12 7, 12 6 ))", @@ -164,6 +300,20 @@ public void testTouchingHoles() { ); } + public void testTouchingHolesDynamic() { + List tolerances = Arrays.asList(1.0 ,1.0 ,0.5); + checkResultDynamic(readArray( + "POLYGON (( 0 0, 0 11, 19 11, 19 0, 0 0 ), ( 4 5, 12 5, 12 6, 10 6, 10 8, 9 8, 9 9, 7 9, 7 8, 6 8, 6 6, 4 6, 4 5 ), ( 12 6, 14 6, 14 9, 13 9, 13 7, 12 7, 12 6 ))", + "POLYGON (( 12 6, 12 5, 4 5, 4 6, 6 6, 6 8, 7 8, 7 9, 9 9, 9 8, 10 8, 10 6, 12 6 ))", + "POLYGON (( 12 6, 12 7, 13 7, 13 9, 14 9, 14 6, 12 6 ))"), + tolerances, + readArray( + "POLYGON ((0 0, 0 11, 19 11, 19 0, 0 0), (12 6, 10 6, 9 9, 6 8, 6 6, 4 5, 12 5, 12 6), (12 6, 14 6, 14 9, 13 9, 13 7, 12 7, 12 6))", + "POLYGON ((12 6, 10 6, 9 9, 6 8, 6 6, 4 5, 12 5, 12 6))", + "POLYGON ((12 6, 14 6, 14 9, 13 9, 13 7, 12 7, 12 6))" ) + ); + } + public void testHoleTouchingShell() { checkResultInner(readArray( "POLYGON ((200 300, 300 300, 300 100, 100 100, 100 300, 200 300), (170 220, 170 160, 200 140, 200 250, 170 220), (170 250, 200 250, 200 300, 170 250))", @@ -177,6 +327,20 @@ public void testHoleTouchingShell() { ); } + public void testHoleTouchingShellDynamic() { + List tolerances = Arrays.asList(100.0, 100.0, 100.0); + checkResultInnerDynamic(readArray( + "POLYGON ((200 300, 300 300, 300 100, 100 100, 100 300, 200 300), (170 220, 170 160, 200 140, 200 250, 170 220), (170 250, 200 250, 200 300, 170 250))", + "POLYGON ((170 220, 200 250, 200 140, 170 160, 170 220))", + "POLYGON ((170 250, 200 300, 200 250, 170 250))"), + tolerances, + readArray( + "POLYGON ((100 100, 100 300, 200 300, 300 300, 300 100, 100 100), (170 160, 200 140, 200 250, 170 160), (170 250, 200 250, 200 300, 170 250))", + "POLYGON ((170 160, 200 250, 200 140, 170 160))", + "POLYGON ((200 250, 200 300, 170 250, 200 250))" ) + ); + } + public void testHolesTouchingHolesAndShellInner() { checkResultInner(readArray( "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))"), @@ -186,6 +350,17 @@ public void testHolesTouchingHolesAndShellInner() { ); } + public void testHolesTouchingHolesAndShellInnerDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(4.0); + checkResultInnerDynamic(readArray( + "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))"), + tolerance, + readArray( + "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))") + ); + } + public void testHolesTouchingHolesAndShell() { checkResult(readArray( "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))"), @@ -195,6 +370,17 @@ public void testHolesTouchingHolesAndShell() { ); } + public void testHolesTouchingHolesAndShellDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(4.0); + checkResultDynamic(readArray( + "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))"), + tolerance, + readArray( + "POLYGON (( 1 2, 1 8, 9 8, 8 5, 9 2, 1 2 ), ( 5 4, 6 3, 6 4, 5 4 ), ( 5 6, 6 6, 6 7, 5 6 ), ( 6 4, 7 4, 8 5, 7 6, 6 6, 6 4 ), ( 7 3, 8 4, 7 4, 7 3 ), ( 7 6, 8 6, 7 7, 7 6 ))") + ); + } + public void testMultiPolygonWithTouchingShellsInner() { checkResultInner( readArray( @@ -205,6 +391,18 @@ public void testMultiPolygonWithTouchingShellsInner() { ); } + public void testMultiPolygonWithTouchingShellsInnerDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(4.0); + checkResultInnerDynamic( + readArray( + "MULTIPOLYGON ((( 2 7, 2 8, 3 8, 3 7, 2 7 )), (( 1 6, 1 7, 2 7, 2 6, 1 6 )), (( 0 7, 0 8, 1 8, 1 7, 0 7 )), (( 0 5, 0 6, 1 6, 1 5, 0 5 )), (( 2 5, 2 6, 3 6, 3 5, 2 5 )))"), + tolerance, + readArray( + "MULTIPOLYGON ((( 2 7, 2 8, 3 8, 3 7, 2 7 )), (( 1 6, 1 7, 2 7, 2 6, 1 6 )), (( 0 7, 0 8, 1 8, 1 7, 0 7 )), (( 0 5, 0 6, 1 6, 1 5, 0 5 )), (( 2 5, 2 6, 3 6, 3 5, 2 5 )))") + ); + } + public void testMultiPolygonWithTouchingShells() { checkResult( readArray( @@ -215,6 +413,18 @@ public void testMultiPolygonWithTouchingShells() { ); } + public void testMultiPolygonWithTouchingShellsDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(4.0); + checkResultDynamic( + readArray( + "MULTIPOLYGON ((( 2 7, 2 8, 3 8, 3 7, 2 7 )), (( 1 6, 1 7, 2 7, 2 6, 1 6 )), (( 0 7, 0 8, 1 8, 1 7, 0 7 )), (( 0 5, 0 6, 1 6, 1 5, 0 5 )), (( 2 5, 2 6, 3 6, 3 5, 2 5 )))"), + tolerance, + readArray( + "MULTIPOLYGON (((0 5, 0 6, 1 6, 0 5)), ((0 8, 1 8, 1 7, 0 8)), ((1 6, 1 7, 2 7, 2 6, 1 6)), ((2 5, 2 6, 3 5, 2 5)), ((2 7, 3 8, 3 7, 2 7)))") + ); + } + public void testTouchingShellsInner() { checkResultInner(readArray( "POLYGON ((0 0, 0 5, 5 6, 10 5, 10 0, 0 0))", @@ -226,6 +436,18 @@ public void testTouchingShellsInner() { ); } + public void testTouchingShellsInnerDynamic() { + List tolerances = Arrays.asList(4.0, 4.0); + checkResultInnerDynamic(readArray( + "POLYGON ((0 0, 0 5, 5 6, 10 5, 10 0, 0 0))", + "POLYGON ((0 10, 5 6, 10 10, 0 10))"), + tolerances, + readArray( + "POLYGON ((0 0, 0 5, 5 6, 10 5, 10 0, 0 0))", + "POLYGON ((0 10, 5 6, 10 10, 0 10))") + ); + } + public void testShellSimplificationAtStartingNode() { checkResult(readArray( "POLYGON (( 1 5, 1 7, 5 7, 5 3, 2 3, 1 5 ))"), @@ -235,6 +457,17 @@ public void testShellSimplificationAtStartingNode() { ); } + public void testShellSimplificationAtStartingNodeDynamic() { + List tolerance = new ArrayList(1); + tolerance.add(1.5); + checkResultDynamic(readArray( + "POLYGON (( 1 5, 1 7, 5 7, 5 3, 2 3, 1 5 ))"), + tolerance, + readArray( + "POLYGON ((1 7, 5 7, 5 3, 2 3, 1 7))") + ); + } + public void testSimplifyInnerAtStartingNode() { checkResultInner(readArray( "POLYGON (( 0 5, 0 9, 6 9, 6 2, 1 2, 0 5 ), ( 1 5, 2 3, 5 3, 5 7, 1 7, 1 5 ))", @@ -246,6 +479,18 @@ public void testSimplifyInnerAtStartingNode() { ); } + public void testSimplifyInnerAtStartingNodeDynamic() { + List tolerances = Arrays.asList(3.0, 2.0); + checkResultInnerDynamic(readArray( + "POLYGON (( 0 5, 0 9, 6 9, 6 2, 1 2, 0 5 ), ( 1 5, 2 3, 5 3, 5 7, 1 7, 1 5 ))", + "POLYGON (( 1 5, 1 7, 5 7, 5 3, 2 3, 1 5 ))"), + tolerances, + readArray( + "POLYGON ((0 5, 0 9, 6 9, 6 2, 1 2, 0 5), (1 7, 2 3, 5 3, 5 7, 1 7))", + "POLYGON ((1 7, 5 7, 5 3, 2 3, 1 7))") + ); + } + public void testSimplifyAllAtStartingNode() { checkResult(readArray( "POLYGON (( 0 5, 0 9, 6 9, 6 2, 1 2, 0 5 ), ( 1 5, 2 3, 5 3, 5 7, 1 7, 1 5 ))", @@ -257,6 +502,17 @@ public void testSimplifyAllAtStartingNode() { ); } + public void testSimplifyAllAtStartingNodeDynamic() { + List tolerances = Arrays.asList(1.5, 3.0); + checkResultDynamic(readArray( + "POLYGON (( 0 5, 0 9, 6 9, 6 2, 1 2, 0 5 ), ( 1 5, 2 3, 5 3, 5 7, 1 7, 1 5 ))", + "POLYGON (( 1 5, 1 7, 5 7, 5 3, 2 3, 1 5 ))"), + tolerances, + readArray( + "POLYGON ((0 9, 6 9, 6 2, 1 2, 0 9), (1 7, 2 3, 5 3, 5 7, 1 7))", + "POLYGON ((1 7, 5 7, 5 3, 2 3, 1 7))") + ); + } //--------------------------------- public void testInnerSimple() { @@ -268,7 +524,18 @@ public void testInnerSimple() { "POLYGON ((50 50, 50 150, 100 190, 100 200, 200 200, 50 50))", "POLYGON ((200 200, 50 50, 100 0, 170 50, 250 100, 200 200))" ) ); - + } + + public void testInnerSimpleDynamic() { + List tolerances = Arrays.asList(50.0, 100.0); + checkResultInnerDynamic(readArray( + "POLYGON ((50 50, 50 150, 100 190, 100 200, 200 200, 160 150, 120 120, 90 80, 50 50))", + "POLYGON ((100 0, 50 50, 90 80, 120 120, 160 150, 200 200, 250 100, 170 50, 100 0))" ), + tolerances, + readArray( + "POLYGON ((50 50, 50 150, 100 190, 100 200, 200 200, 50 50))", + "POLYGON ((200 200, 50 50, 100 0, 170 50, 250 100, 200 200)))") + ); } //--------------------------------- @@ -283,6 +550,18 @@ public void testAllEmpty() { "POLYGON EMPTY" ) ); } + + public void testAllEmptyDynamic() { + List tolerances = Arrays.asList(1.0, 1.0); + checkResultDynamic(readArray( + "POLYGON EMPTY", + "POLYGON EMPTY" ), + tolerances, + readArray( + "POLYGON EMPTY", + "POLYGON EMPTY" ) + ); + } public void testOneEmpty() { checkResult(readArray( @@ -294,6 +573,18 @@ public void testOneEmpty() { "POLYGON EMPTY" ) ); } + + public void testOneEmptyDynamic() { + List tolerances = Arrays.asList(1.0, 1.0); + checkResultDynamic(readArray( + "POLYGON ((1 9, 5 9.1, 9 9, 9 1, 1 1, 1 9))", + "POLYGON EMPTY" ), + tolerances, + readArray( + "POLYGON ((1 9, 9 9, 9 1, 1 1, 1 9))", + "POLYGON EMPTY" ) + ); + } public void testEmptyHole() { checkResult(readArray( @@ -305,22 +596,49 @@ public void testEmptyHole() { "POLYGON EMPTY" ) ); } + + public void testEmptyHoleDynamic() { + List tolerances = Arrays.asList(1.0, 1.0); + checkResultDynamic(readArray( + "POLYGON ((1 9, 5 9.1, 9 9, 9 1, 1 1, 1 9), EMPTY)", + "POLYGON EMPTY" ), + tolerances, + readArray( + "POLYGON ((1 9, 9 9, 9 1, 1 1, 1 9), EMPTY)", + "POLYGON EMPTY" ) + ); + } //================================= - private void checkNoop(Geometry[] input) { Geometry[] actual = CoverageSimplifier.simplify(input, 0); checkEqual(input, actual); } + + private void checkNoopDynamic(Geometry[] input) { + List tolerance = Collections.nCopies(input.length, 0.0); + Geometry[] actual = CoverageSimplifier.simplify(input, tolerance); + checkEqual(input, actual); + } private void checkResult(Geometry[] input, double tolerance, Geometry[] expected) { Geometry[] actual = CoverageSimplifier.simplify(input, tolerance); checkEqual(expected, actual); } + + private void checkResultDynamic(Geometry[] input, List tolerances, Geometry[] expected) { + Geometry[] actual = CoverageSimplifier.simplify(input, tolerances); + checkEqual(expected, actual); + } private void checkResultInner(Geometry[] input, double tolerance, Geometry[] expected) { Geometry[] actual = CoverageSimplifier.simplifyInner(input, tolerance); checkEqual(expected, actual); } + + private void checkResultInnerDynamic(Geometry[] input, List tolerances, Geometry[] expected) { + Geometry[] actual = CoverageSimplifier.simplifyInner(input, tolerances); + checkEqual(expected, actual); + } }