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

Improve CoverageSimplifier #1060

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/
package org.locationtech.jtstest.function;

import java.util.Arrays;
import java.util.List;

import org.locationtech.jts.coverage.CoverageGapFinder;
Expand Down Expand Up @@ -62,20 +63,82 @@ public static Geometry union(Geometry coverage) {
public static Geometry simplify(Geometry coverage, double tolerance) {
Geometry[] cov = toGeometryArray(coverage);
Geometry[] result = CoverageSimplifier.simplify(cov, tolerance);
return FunctionsUtil.buildGeometry(result);
return coverage.getFactory().createGeometryCollection(result);
}

@Metadata(description="Simplify a coverage with a smoothness weight")
public static Geometry simplifySharp(Geometry coverage,
@Metadata(title="Distance tol")
double tolerance,
@Metadata(title="Weight")
double weight) {
Geometry[] cov = toGeometryArray(coverage);
CoverageSimplifier simplifier = new CoverageSimplifier(cov);
simplifier.setSmoothWeight(weight);
Geometry[] result = simplifier.simplify(tolerance);
return coverage.getFactory().createGeometryCollection(result);
}

@Metadata(description="Simplify a coverage with a ring removal size factor")
public static Geometry simplifyRemoveRings(Geometry coverage,
@Metadata(title="Distance tol")
double tolerance,
@Metadata(title="Removal Size Factor")
double factor) {
Geometry[] cov = toGeometryArray(coverage);
CoverageSimplifier simplifier = new CoverageSimplifier(cov);
simplifier.setRemovableRingSizeFactor(factor);
Geometry[] result = simplifier.simplify(tolerance);
return coverage.getFactory().createGeometryCollection(result);
}

@Metadata(description="Simplify inner edges of a coverage")
public static Geometry simplifyinner(Geometry coverage, double tolerance) {
public static Geometry simplifyInner(Geometry coverage, double tolerance) {
Geometry[] cov = toGeometryArray(coverage);
Geometry[] result = CoverageSimplifier.simplifyInner(cov, tolerance);
return coverage.getFactory().createGeometryCollection(result);
}

@Metadata(description="Simplify outer edges of a coverage")
public static Geometry simplifyOuter(Geometry coverage, double tolerance) {
Geometry[] cov = toGeometryArray(coverage);
Geometry[] result = CoverageSimplifier.simplifyOuter(cov, tolerance);
return coverage.getFactory().createGeometryCollection(result);
}

@Metadata(description="Simplify inner and outer edges of a coverage differently")
public static Geometry simplifyInOut(Geometry coverage,
@Metadata(title="Inner Distance tol")
double toleranceInner,
@Metadata(title="Outer Distance tol")
double toleranceOuter) {
Geometry[] cov = toGeometryArray(coverage);
CoverageSimplifier simplifier = new CoverageSimplifier(cov);
Geometry[] result = simplifier.simplify(toleranceInner, toleranceOuter);
return coverage.getFactory().createGeometryCollection(result);
}

@Metadata(description="Simplify a coverage with per-geometry tolerances")
public static Geometry simplifyTolerances(Geometry coverage,
@Metadata(title="Tolerances (comma-sep)")
String tolerancesCSV) {
Geometry[] cov = toGeometryArray(coverage);
double[] tolerances = tolerances(tolerancesCSV, cov.length);
Geometry[] result = CoverageSimplifier.simplify(cov, tolerances);
return FunctionsUtil.buildGeometry(result);
}

static Geometry extractPolygons(Geometry geom) {
List components = PolygonExtracter.getPolygons(geom);
Geometry result = geom.getFactory().buildGeometry(components);
return result;
private static double[] tolerances(String csvList, int len) {
Double[] tolsDouble = toDoubleArray(csvList);
double[] tols = new double[len];
for (int i = 0; i < tolsDouble.length; i++) {
tols[i] = tolsDouble[i];
}
return tols;
}

private static Double[] toDoubleArray(String csvList) {
return Arrays.stream(csvList.split(",")).map(Double::parseDouble).toArray(Double[]::new);
}

private static Geometry[] toGeometryArray(Geometry geom) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ class Corner implements Comparable<Corner> {
private int next;
private double area;

public Corner(LinkedLine edge, int i) {
public Corner(LinkedLine edge, int i, double area) {
this.edge = edge;
this.index = i;
this.prev = edge.prev(i);
this.next = edge.next(i);
this.area = area(edge, i);
this.area = area;
}

public boolean isVertex(int index) {
Expand Down Expand Up @@ -58,13 +58,6 @@ public Coordinate prev() {
public Coordinate next() {
return edge.getCoordinate(next);
}

private static double area(LinkedLine edge, int index) {
Coordinate pp = edge.prevCoordinate(index);
Coordinate p = edge.getCoordinate(index);
Coordinate pn = edge.nextCoordinate(index);
return Triangle.area(pp, p, pn);
}

/**
* Orders corners by increasing area.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 Martin Davis.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.coverage;

import org.locationtech.jts.algorithm.Angle;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Triangle;
import org.locationtech.jts.math.MathUtil;

/**
* Computes the effective area of corners,
* taking into account the smoothing weight.
*
* <h3>FUTURE WORK</h3>
*
* Support computing geodetic area
*
* @author Martin Davis
*
*/
class CornerArea {
public static final double DEFAULT_SMOOTH_WEIGHT = 0.0;

private double smoothWeight = DEFAULT_SMOOTH_WEIGHT;

public CornerArea() {
}

/**
* Creates a new corner area computer.
*
* @param smoothWeight the weight for smoothing corners. In range [0..1].
*/
public CornerArea(double smoothWeight) {
this.smoothWeight = smoothWeight;
}

public double area(Coordinate pp, Coordinate p, Coordinate pn) {

double area = Triangle.area(pp, p, pn);
double ang = angleNorm(pp, p, pn);
//-- rescale to [-1 .. 1], with 1 being narrow and -1 being flat
double angBias = 1.0 - 2.0 * ang;
//-- reduce area for narrower corners, to make them more likely to be removed
double areaWeighted = (1 - smoothWeight * angBias) * area;
return areaWeighted;
}

private static double angleNorm(Coordinate pp, Coordinate p, Coordinate pn) {
double angNorm = Angle.angleBetween(pp, p, pn) / 2 / Math.PI;
return MathUtil.clamp(angNorm, 0, 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,38 @@
*/
package org.locationtech.jts.coverage;

import java.util.List;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateArrays;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineSegment;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.io.WKTWriter;

/**
* An edge of a polygonal coverage formed from all or a section of a polygon ring.
* An edge may be a free ring, which is a ring which has not node points
* (i.e. does not touch any other rings in the parent coverage).
* An edge may be a free ring, which is a ring which has no node points
* (i.e. does not share a vertex with any other rings in the parent coverage).
*
* @author mdavis
*
*/
class CoverageEdge {

public static CoverageEdge createEdge(Coordinate[] ring) {
public static final int RING_COUNT_INNER = 2;
public static final int RING_COUNT_OUTER = 1;

public static CoverageEdge createEdge(Coordinate[] ring, boolean isPrimary) {
Coordinate[] pts = extractEdgePoints(ring, 0, ring.length - 1);
CoverageEdge edge = new CoverageEdge(pts, true);
CoverageEdge edge = new CoverageEdge(pts, isPrimary, true);
return edge;
}

public static CoverageEdge createEdge(Coordinate[] ring, int start, int end) {
public static CoverageEdge createEdge(Coordinate[] ring, int start, int end, boolean isPrimary) {
Coordinate[] pts = extractEdgePoints(ring, start, end);
CoverageEdge edge = new CoverageEdge(pts, false);
CoverageEdge edge = new CoverageEdge(pts, isPrimary, false);
return edge;
}

static MultiLineString createLines(List<CoverageEdge> edges, GeometryFactory geomFactory) {
LineString lines[] = new LineString[edges.size()];
for (int i = 0; i < edges.size(); i++) {
CoverageEdge edge = edges.get(i);
lines[i] = edge.toLineString(geomFactory);
}
MultiLineString mls = geomFactory.createMultiLineString(lines);
return mls;
}

private static Coordinate[] extractEdgePoints(Coordinate[] ring, int start, int end) {
int size = start < end
? end - start + 1
Expand Down Expand Up @@ -136,12 +127,16 @@ else if (i > pts.length - 1) {
private Coordinate[] pts;
private int ringCount = 0;
private boolean isFreeRing = true;
private boolean isPrimary = true;
private int adjacentIndex0 = -1;
private int adjacentIndex1 = -1;

public CoverageEdge(Coordinate[] pts, boolean isFreeRing) {
public CoverageEdge(Coordinate[] pts, boolean isPrimary, boolean isFreeRing) {
this.pts = pts;
this.isPrimary = isPrimary;
this.isFreeRing = isFreeRing;
}

public void incRingCount() {
ringCount++;
}
Expand All @@ -150,9 +145,30 @@ public int getRingCount() {
return ringCount;
}

public boolean isInner() {
return ringCount == RING_COUNT_INNER;
}

public boolean isOuter() {
return ringCount == RING_COUNT_OUTER;
}

public void setPrimary(boolean isPrimary) {
//-- preserve primary status if set
if (this.isPrimary)
return;
this.isPrimary = isPrimary;
}

public boolean isRemovableRing() {
boolean isRing = CoordinateArrays.isRing(pts);
return isRing && ! isPrimary;
}

/**
* Returns whether this edge is a free ring;
* i.e. one with no constrained nodes.
* i.e. one that does not have nodes
* which are anchored because they occur in another ring.
*
* @return true if this is a free ring
*/
Expand Down Expand Up @@ -184,5 +200,28 @@ public String toString() {
return WKTWriter.toLineString(pts);
}

public void addIndex(int index) {
//TODO: keep information about which element is L and R?

// assert: at least one elementIndex is unset (< 0)
if (adjacentIndex0 < 0) {
adjacentIndex0 = index;
}
else {
adjacentIndex1 = index;
}
}

public int getAdjacentIndex(int index) {
if (index == 0)
return adjacentIndex0;
return adjacentIndex1;
}

public boolean hasAdjacentIndex(int index) {
if (index == 0)
return adjacentIndex0 >= 0;
return adjacentIndex1 >= 0;
}

}
Loading
Loading