Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance coverage simplification to handle one tolerance per geometry #1037

Closed
wants to merge 7 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
*/
package org.locationtech.jtstest.function;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.locationtech.jts.coverage.CoverageGapFinder;
import org.locationtech.jts.coverage.CoveragePolygonValidator;
Expand Down Expand Up @@ -64,13 +66,29 @@ 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);
Double[] toleranceList = toDoubleArray(tolerances);
Geometry[] result = CoverageSimplifier.simplify(cov, toleranceList);
return FunctionsUtil.buildGeometry(result);
}

@Metadata(description="Simplify inner edges of a coverage")
public static Geometry simplifyinner(Geometry coverage, double tolerance) {
Geometry[] cov = toGeometryArray(coverage);
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);
Double[] toleranceList = toDoubleArray(tolerances);
Geometry[] result = CoverageSimplifier.simplifyInner(cov, toleranceList);
return FunctionsUtil.buildGeometry(result);
}

static Geometry extractPolygons(Geometry geom) {
List components = PolygonExtracter.getPolygons(geom);
Expand All @@ -85,5 +103,8 @@ private static Geometry[] toGeometryArray(Geometry geom) {
}
return geoms;
}


private static Double[] toDoubleArray(String csvList) {
return Arrays.stream(csvList.split(",")).map(Double::parseDouble).toArray(Double[]::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, Double[] 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, null);
return edges;
}

private Geometry[] coverage;
private Map<LinearRing, List<CoverageEdge>> ringEdgesMap;
private List<CoverageEdge> edges;

public CoverageRingEdges(Geometry[] coverage) {

private Double[] coverageTolerances;

public CoverageRingEdges(Geometry[] coverage, Double[] tolerances) {
this.coverage = coverage;
ringEdgesMap = new HashMap<LinearRing, List<CoverageEdge>>();
edges = new ArrayList<CoverageEdge>();
coverageTolerances = tolerances;
build();
}

Expand Down Expand Up @@ -94,7 +103,8 @@ private void build() {
Set<LineSegment> boundarySegs = CoverageBoundarySegmentFinder.findBoundarySegments(coverage);
nodes.addAll(findBoundaryNodes(boundarySegs));
HashMap<LineSegment, CoverageEdge> uniqueEdgeMap = new HashMap<LineSegment, CoverageEdge>();
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);

Expand All @@ -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<Coordinate> nodes, Set<LineSegment> boundarySegs,
private void addRingEdges(int coverageId, LinearRing ring, Set<Coordinate> nodes, Set<LineSegment> boundarySegs,
HashMap<LineSegment, CoverageEdge> uniqueEdgeMap) {
addBoundaryInnerNodes(ring, boundarySegs, nodes);
List<CoverageEdge> ringEdges = extractRingEdges(ring, uniqueEdgeMap, nodes);
List<CoverageEdge> ringEdges = extractRingEdges(coverageId, ring, uniqueEdgeMap, nodes);
if (ringEdges != null)
ringEdgesMap.put(ring, ringEdges);
}
Expand Down Expand Up @@ -149,8 +159,8 @@ private void addBoundaryInnerNodes(LinearRing ring, Set<LineSegment> boundarySeg
}
}

private List<CoverageEdge> extractRingEdges(LinearRing ring,
HashMap<LineSegment, CoverageEdge> uniqueEdgeMap,
private List<CoverageEdge> extractRingEdges(int coverageId, LinearRing ring,
HashMap<LineSegment, CoverageEdge> uniqueEdgeMap,
Set<Coordinate> nodes) {
// System.out.println(ring);
List<CoverageEdge> ringEdges = new ArrayList<CoverageEdge>();
Expand All @@ -164,15 +174,15 @@ private List<CoverageEdge> 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 {
int start = first;
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;
Expand All @@ -181,36 +191,49 @@ private List<CoverageEdge> extractRingEdges(LinearRing ring,
return ringEdges;
}

private CoverageEdge createEdge(Coordinate[] ring, HashMap<LineSegment, CoverageEdge> uniqueEdgeMap) {
private CoverageEdge createEdge(int coverageId, Coordinate[] ring, HashMap<LineSegment, CoverageEdge> uniqueEdgeMap) {
boolean hasTolerance = coverageTolerances != null;
CoverageEdge edge;
LineSegment edgeKey = CoverageEdge.key(ring);
if (uniqueEdgeMap.containsKey(edgeKey)) {
edge = uniqueEdgeMap.get(edgeKey);
if (hasTolerance){
edge.setTolerance((edge.getTolerance() < coverageTolerances[coverageId]) ? edge.getTolerance() : coverageTolerances[coverageId]);
}
}
else {
edge = CoverageEdge.createEdge(ring);
double tolerance = hasTolerance ? coverageTolerances[coverageId] : -1;
edge = CoverageEdge.createEdge(ring, tolerance);
uniqueEdgeMap.put(edgeKey, edge);
edges.add(edge);
}
edge.incRingCount();
return edge;
}

private CoverageEdge createEdge(Coordinate[] ring, int start, int end, HashMap<LineSegment, CoverageEdge> uniqueEdgeMap) {
private CoverageEdge createEdge(int coverageId, Coordinate[] ring, int start, int end, HashMap<LineSegment, CoverageEdge> uniqueEdgeMap) {
CoverageEdge edge;
LineSegment edgeKey = (end == start) ? CoverageEdge.key(ring) : CoverageEdge.key(ring, start, end);
if (uniqueEdgeMap.containsKey(edgeKey)) {
edge = uniqueEdgeMap.get(edgeKey);
assignEdgeTolerance(coverageId, edge);
}
else {
edge = CoverageEdge.createEdge(ring, start, end);
double tolerance = coverageTolerances == null ? -1 : coverageTolerances[coverageId];
edge = CoverageEdge.createEdge(ring, start, end, tolerance);
uniqueEdgeMap.put(edgeKey, edge);
edges.add(edge);
}
edge.incRingCount();
return edge;
}

private void assignEdgeTolerance(int coverageId, CoverageEdge edge){
if (coverageTolerances != null){
edge.setTolerance((edge.getTolerance() < coverageTolerances[coverageId]) ? edge.getTolerance() : coverageTolerances[coverageId]);
}
}

private int findNextNodeIndex(Coordinate[] ring, int start, Set<Coordinate> nodes) {
int index = start;
boolean isScanned0 = false;
Expand Down Expand Up @@ -278,7 +301,7 @@ private Set<Coordinate> findBoundaryNodes(Set<LineSegment> boundarySegments) {

/**
* Recreates the polygon coverage from the current edge values.
*
*
* @return an array of polygonal geometries representing the coverage
*/
public Geometry[] buildCoverage() {
Expand Down
Loading
Loading