Skip to content

Commit

Permalink
Merge master
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Barry <msb5014@gmail.com>
  • Loading branch information
msbarry committed Nov 8, 2023
1 parent 220bbdd commit 9cc4e94
Show file tree
Hide file tree
Showing 11 changed files with 593 additions and 299 deletions.
1 change: 1 addition & 0 deletions doc/JTS_Version_History.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Distributions for older JTS versions can be obtained at the
* Improve `OffsetCurve` to return more linework for some input situations (#956)
* Reduce buffer curve short fillet segments (#960)
* Added ability to specify boundary for `LargestEmptyCircle` (#973)
* Improve `DouglaPeuckerSimplifier` and `TopologyPreservingSimplifier` to handle ring endpoints (#1013)

### Bug Fixes
* Fix `PreparedGeometry` handling of EMPTY elements (#904)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.locationtech.jts.simplify;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateArrays;
import org.locationtech.jts.geom.CoordinateList;
import org.locationtech.jts.geom.LineSegment;

Expand All @@ -24,16 +25,18 @@
*/
class DouglasPeuckerLineSimplifier
{
public static Coordinate[] simplify(Coordinate[] pts, double distanceTolerance)
public static Coordinate[] simplify(Coordinate[] pts, double distanceTolerance, boolean isPreserveEndpoint)
{
DouglasPeuckerLineSimplifier simp = new DouglasPeuckerLineSimplifier(pts);
simp.setDistanceTolerance(distanceTolerance);
simp.setPreserveEndpoint(isPreserveEndpoint);
return simp.simplify();
}

private Coordinate[] pts;
private boolean[] usePt;
private double distanceTolerance;
private boolean isPreserveEndpoint = false;

public DouglasPeuckerLineSimplifier(Coordinate[] pts)
{
Expand All @@ -50,19 +53,44 @@ public void setDistanceTolerance(double distanceTolerance) {
this.distanceTolerance = distanceTolerance;
}

private void setPreserveEndpoint(boolean isPreserveEndpoint) {
this.isPreserveEndpoint = isPreserveEndpoint;
}

public Coordinate[] simplify()
{
usePt = new boolean[pts.length];
for (int i = 0; i < pts.length; i++) {
usePt[i] = true;
}
simplifySection(0, pts.length - 1);

CoordinateList coordList = new CoordinateList();
for (int i = 0; i < pts.length; i++) {
if (usePt[i])
coordList.add(new Coordinate(pts[i]));
}
return coordList.toCoordinateArray();

if (! isPreserveEndpoint && CoordinateArrays.isRing(pts)) {
simplifyRingEndpoint(coordList);
}

return coordList.toCoordinateArray();
}

private void simplifyRingEndpoint(CoordinateList pts) {
//-- avoid collapsing triangles
if (pts.size() < 4)
return;
//-- base segment for endpoint
seg.p0 = pts.get(1);
seg.p1 = pts.get(pts.size() - 2);
double distance = seg.distance(pts.get(0));
if (distance <= distanceTolerance) {
pts.remove(0);
pts.remove(pts.size() - 1);
pts.closeRing();
}
}

private LineSegment seg = new LineSegment();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,14 @@ public DPTransformer(boolean isEnsureValidTopology, double distanceTolerance)
@Override
protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent)
{
boolean isPreserveEndpoint = ! (parent instanceof LinearRing);
Coordinate[] inputPts = coords.toCoordinateArray();
Coordinate[] newPts = null;
if (inputPts.length == 0) {
newPts = new Coordinate[0];
}
else {
newPts = DouglasPeuckerLineSimplifier.simplify(inputPts, distanceTolerance);
newPts = DouglasPeuckerLineSimplifier.simplify(inputPts, distanceTolerance, isPreserveEndpoint);
}
return factory.getCoordinateSequenceFactory().create(newPts);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,23 @@ class TaggedLineString
private TaggedLineSegment[] segs;
private List resultSegs = new ArrayList();
private int minimumSize;
private boolean isPreserveEndpoint = true;

public TaggedLineString(LineString parentLine) {
this(parentLine, 2);
this(parentLine, 2, true);
}

public TaggedLineString(LineString parentLine, int minimumSize) {
public TaggedLineString(LineString parentLine, int minimumSize, boolean isPreserveEndpoint) {
this.parentLine = parentLine;
this.minimumSize = minimumSize;
this.isPreserveEndpoint = isPreserveEndpoint;
init();
}

public boolean isPreserveEndpoint() {
return isPreserveEndpoint;
}

public int getMinimumSize() { return minimumSize; }
public LineString getParent() { return parentLine; }
public Coordinate[] getParentCoordinates() { return parentLine.getCoordinates(); }
Expand All @@ -58,6 +64,20 @@ public int getResultSize()

public TaggedLineSegment getSegment(int i) { return segs[i]; }

/**
* Gets a segment of the result list.
* Negative indexes can be used to retrieve from the end of the list.
* @param i the segment index to retrieve
* @return the result segment
*/
public LineSegment getResultSegment(int i) {
int index = i;
if (i < 0) {
index = resultSegs.size() + i;
}
return (LineSegment) resultSegs.get(index);
}

private void init()
{
Coordinate[] pts = parentLine.getCoordinates();
Expand Down Expand Up @@ -98,5 +118,12 @@ private static Coordinate[] extractCoordinates(List segs)
return pts;
}

void removeRingEndpoint()
{
LineSegment firstSeg = (LineSegment) resultSegs.get(0);
LineSegment lastSeg = (LineSegment) resultSegs.get(resultSegs.size() - 1);

firstSeg.p0 = lastSeg.p0;
resultSegs.remove(resultSegs.size() - 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.locationtech.jts.algorithm.LineIntersector;
import org.locationtech.jts.algorithm.RobustLineIntersector;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateArrays;
import org.locationtech.jts.geom.LineSegment;

/**
Expand Down Expand Up @@ -66,13 +67,16 @@ void simplify(TaggedLineString line)
this.line = line;
linePts = line.getParentCoordinates();
simplifySection(0, linePts.length - 1, 0);

if (! line.isPreserveEndpoint() && CoordinateArrays.isRing(linePts)) {
simplifyRingEndpoint();
}
}

private void simplifySection(int i, int j, int depth)
{
depth += 1;
int[] sectionIndex = new int[2];
if((i+1) == j) {
if ((i+1) == j) {
LineSegment newSeg = line.getSegment(i);
line.addToResult(newSeg);
// leave this segment in the input index, for efficiency
Expand Down Expand Up @@ -101,9 +105,7 @@ private void simplifySection(int i, int j, int depth)
LineSegment candidateSeg = new LineSegment();
candidateSeg.p0 = linePts[i];
candidateSeg.p1 = linePts[j];
sectionIndex[0] = i;
sectionIndex[1] = j;
if (hasBadIntersection(line, sectionIndex, candidateSeg)) {
if (hasBadIntersection(line, i, j, candidateSeg)) {
isValidToSimplify = false;
}

Expand All @@ -116,6 +118,21 @@ private void simplifySection(int i, int j, int depth)
simplifySection(furthestPtIndex, j, depth);
}

private void simplifyRingEndpoint()
{
if (line.getResultSize() > line.getMinimumSize()) {
LineSegment firstSeg = line.getResultSegment(0);
LineSegment lastSeg = line.getResultSegment(-1);

LineSegment simpSeg = new LineSegment(lastSeg.p0, firstSeg.p1);
//-- the excluded segments are the ones containing the endpoint
if (simpSeg.distance(firstSeg.p0) <= distanceTolerance
&& ! hasBadIntersection(line, line.getSegments().length - 2, 0, simpSeg)) {
line.removeRingEndpoint();
}
}
}

private int findFurthestPoint(Coordinate[] pts, int i, int j, double[] maxDistance)
{
LineSegment seg = new LineSegment();
Expand Down Expand Up @@ -158,12 +175,25 @@ private LineSegment flatten(int start, int end)
return newSeg;
}

private boolean hasBadIntersection(TaggedLineString parentLine,
int[] sectionIndex,
/**
* Tests if a flattening segment intersects a line
* (excluding a given section of segments).
* The excluded section is being replaced by the flattening segment,
* so there is no need to test it
* (and it may well intersect the segment).
*
* @param line
* @param excludeStart
* @param excludeEnd
* @param candidateSeg
* @return
*/
private boolean hasBadIntersection(TaggedLineString line,
int excludeStart, int excludeEnd,
LineSegment candidateSeg)
{
if (hasBadOutputIntersection(candidateSeg)) return true;
if (hasBadInputIntersection(parentLine, sectionIndex, candidateSeg)) return true;
if (hasBadInputIntersection(line, excludeStart, excludeEnd, candidateSeg)) return true;
return false;
}

Expand All @@ -179,16 +209,19 @@ private boolean hasBadOutputIntersection(LineSegment candidateSeg)
return false;
}

private boolean hasBadInputIntersection(TaggedLineString parentLine,
int[] sectionIndex,
private boolean hasBadInputIntersection(TaggedLineString line,
int excludeStart, int excludeEnd,
LineSegment candidateSeg)
{
List querySegs = inputIndex.query(candidateSeg);
for (Iterator i = querySegs.iterator(); i.hasNext(); ) {
TaggedLineSegment querySeg = (TaggedLineSegment) i.next();
if (hasInvalidIntersection(querySeg, candidateSeg)) {
//-- don't fail if the segment is part of parent line
if (isInLineSection(parentLine, sectionIndex, querySeg))
/**
* Ignore the intersection if the intersecting segment is part of the section being collapsed
* to the candidate segment
*/
if (isInLineSection(line, excludeStart, excludeEnd, querySeg))
continue;
return true;
}
Expand All @@ -197,23 +230,36 @@ private boolean hasBadInputIntersection(TaggedLineString parentLine,
}

/**
* Tests whether a segment is in a section of a TaggedLineString
* @param line
* @param sectionIndex
* @param seg
* @return
* Tests whether a segment is in a section of a TaggedLineString.
* Sections may wrap around the endpoint of the line,
* to support ring endpoint simplification.
* This is indicated by excludedStart > excludedEnd
*
* @param line the TaggedLineString containing the section segments
* @param excludeStart the index of the first segment in the excluded section
* @param excludeEnd the index of the last segment in the excluded section
* @param seg the segment to test
* @return true if the test segment intersects some segment in the line not in the excluded section
*/
private static boolean isInLineSection(
TaggedLineString line,
int[] sectionIndex,
int excludeStart, int excludeEnd,
TaggedLineSegment seg)
{
// not in this line
//-- test segment is not in this line
if (seg.getParent() != line.getParent())
return false;
int segIndex = seg.getIndex();
if (segIndex >= sectionIndex[0] && segIndex < sectionIndex[1])
if (excludeStart <= excludeEnd) {
//-- section is contiguous
if (segIndex >= excludeStart && segIndex < excludeEnd)
return true;
}
else {
//-- section wraps around the end of a ring
if (segIndex >= excludeStart || segIndex <= excludeEnd)
return true;
}
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
* any intersecting line segments, this property
* will be preserved in the output.
* <p>
* For polygonal geometries and LinearRings the endpoint will participate
* in simplification. For LineStrings the endpoints will not be unchanged.
* <p>
* For all geometry types, the result will contain
* enough vertices to ensure validity. For polygons
* and closed linear geometries, the result will have at
Expand Down Expand Up @@ -177,7 +180,8 @@ public void filter(Geometry geom)
if (line.isEmpty()) return;

int minSize = ((LineString) line).isClosed() ? 4 : 2;
TaggedLineString taggedLine = new TaggedLineString((LineString) line, minSize);
boolean isPreserveEndpoint = (line instanceof LinearRing) ? false : true;
TaggedLineString taggedLine = new TaggedLineString((LineString) line, minSize, isPreserveEndpoint);
tps.linestringMap.put(line, taggedLine);
}
}
Expand Down
Loading

0 comments on commit 9cc4e94

Please sign in to comment.