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

Fix TopologyPreservingSimplifier to prevent jumping components causing incorrect topology #1024

Merged
merged 15 commits into from
Dec 4, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,24 @@ public void countSegment(Coordinate p1, Coordinate p2) {
}
}

/**
* Reports whether the point lies exactly on one of the supplied segments.
* This method may be called at any time as segments are processed.
* If the result of this method is <tt>true</tt>,
* no further segments need be supplied, since the result
* will never change again.
*
* @return true if the point lies exactly on a segment
*/
/**
* Gets the count of crossings.
*
* @return the crossing count
*/
public int getCount() {
return crossingCount;
}

/**
* Reports whether the point lies exactly on one of the supplied segments.
* This method may be called at any time as segments are processed.
* If the result of this method is <tt>true</tt>,
* no further segments need be supplied, since the result
* will never change again.
*
* @return true if the point lies exactly on a segment
*/
public boolean isOnSegment() { return isPointOnSegment; }

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright (c) 2023 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.simplify;

import java.util.Collection;

import org.locationtech.jts.algorithm.RayCrossingCounter;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.LineSegment;

/**
* Checks if simplifying (flattening) line sections or segments
* would cause them to "jump" over other components in the geometry.
*
* @author mdavis
*
*/
class ComponentJumpChecker {

//TODO: use a spatial index?
private Collection<TaggedLineString> components;

public ComponentJumpChecker(Collection<TaggedLineString> taggedLines) {
components = taggedLines;
}

/**
* Checks if a line section jumps a component if flattened.
*
* Assumes start <= end.
*
* @param line the line containing the section being flattened
* @param start start index of the section
* @param end end index of the section
* @param seg the flattening segment
* @return true if the flattened section jumps a component
*/
public boolean hasJump(TaggedLineString line, int start, int end, LineSegment seg) {
Envelope sectionEnv = computeEnvelope(line, start, end);
for (TaggedLineString comp : components) {
//-- don't test component against itself
if (comp == line)
continue;

Coordinate compPt = comp.getComponentPoint();
if (sectionEnv.intersects(compPt)) {
if (hasJumpAtComponent(compPt, line, start, end, seg)) {
return true;
}
}
}
return false;
}

/**
* Checks if two consecutive segments jumps a component if flattened.
* The segments are assumed to be consecutive.
* (so the seg1.p1 = seg2.p0).
* The flattening segment must be the segment between seg1.p0 and seg2.p1.
*
* @param line the line containing the section being flattened
* @param seg1 the first replaced segment
* @param seg2 the next replaced segment
* @param seg the flattening segment
* @return true if the flattened segment jumps a component
*/
public boolean hasJump(TaggedLineString line, LineSegment seg1, LineSegment seg2, LineSegment seg) {
Envelope sectionEnv = computeEnvelope(seg1, seg2);
for (TaggedLineString comp : components) {
//-- don't test component against itself
if (comp == line)
continue;

Coordinate compPt = comp.getComponentPoint();
if (sectionEnv.intersects(compPt)) {
if (hasJumpAtComponent(compPt, seg1, seg2, seg)) {
return true;
}
}
}
return false;
}

private static boolean hasJumpAtComponent(Coordinate compPt, TaggedLineString line, int start, int end, LineSegment seg) {
int sectionCount = crossingCount(compPt, line, start, end);
int segCount = crossingCount(compPt, seg);
boolean hasJump = sectionCount % 2 != segCount % 2;
return hasJump;
}

private static boolean hasJumpAtComponent(Coordinate compPt, LineSegment seg1, LineSegment seg2, LineSegment seg) {
int sectionCount = crossingCount(compPt, seg1, seg2);
int segCount = crossingCount(compPt, seg);
boolean hasJump = sectionCount % 2 != segCount % 2;
return hasJump;
}

private static int crossingCount(Coordinate compPt, LineSegment seg) {
RayCrossingCounter rcc = new RayCrossingCounter(compPt);
rcc.countSegment(seg.p0, seg.p1);
return rcc.getCount();
}

private static int crossingCount(Coordinate compPt, LineSegment seg1, LineSegment seg2) {
RayCrossingCounter rcc = new RayCrossingCounter(compPt);
rcc.countSegment(seg1.p0, seg1.p1);
rcc.countSegment(seg2.p0, seg2.p1);
return rcc.getCount();
}

private static int crossingCount(Coordinate compPt, TaggedLineString line, int start, int end) {
RayCrossingCounter rcc = new RayCrossingCounter(compPt);
for (int i = start; i < end; i++) {
rcc.countSegment(line.getCoordinate(i), line.getCoordinate(i + 1));
}
return rcc.getCount();
}

private static Envelope computeEnvelope(LineSegment seg1, LineSegment seg2) {
Envelope env = new Envelope();
env.expandToInclude(seg1.p0);
env.expandToInclude(seg1.p1);
env.expandToInclude(seg2.p0);
env.expandToInclude(seg2.p1);
return env;
}

private static Envelope computeEnvelope(TaggedLineString line, int start, int end) {
Envelope env = new Envelope();
for (int i = start; i <= end; i++) {
env.expandToInclude(line.getCoordinate(i));
}
return env;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ public void remove(LineSegment seg)
index.remove(new Envelope(seg.p0, seg.p1), seg);
}

public List query(LineSegment querySeg)
public List<Object> query(LineSegment querySeg)
{
Envelope env = new Envelope(querySeg.p0, querySeg.p1);

LineSegmentVisitor visitor = new LineSegmentVisitor(querySeg);
index.query(env, visitor);
List itemsFound = visitor.getItems();
List<Object> itemsFound = visitor.getItems();

// List listQueryItems = index.query(env);
// System.out.println("visitor size = " + itemsFound.size()
Expand All @@ -78,7 +78,7 @@ class LineSegmentVisitor
// MD - only seems to make about a 10% difference in overall time.

private LineSegment querySeg;
private ArrayList items = new ArrayList();
private ArrayList<Object> items = new ArrayList<Object>();

public LineSegmentVisitor(LineSegment querySeg) {
this.querySeg = querySeg;
Expand All @@ -91,5 +91,5 @@ public void visitItem(Object item)
items.add(item);
}

public ArrayList getItems() { return items; }
public ArrayList<Object> getItems() { return items; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,38 @@ class TaggedLineString

private LineString parentLine;
private TaggedLineSegment[] segs;
private List resultSegs = new ArrayList();
private List<LineSegment> resultSegs = new ArrayList<LineSegment>();
private int minimumSize;
private boolean isPreserveEndpoint = true;
private boolean isRing = true;

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

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

public boolean isPreserveEndpoint() {
return isPreserveEndpoint;
public boolean isRing() {
return isRing;
}

public int getMinimumSize() { return minimumSize; }
public LineString getParent() { return parentLine; }
public Coordinate[] getParentCoordinates() { return parentLine.getCoordinates(); }
public Coordinate[] getResultCoordinates() { return extractCoordinates(resultSegs); }

public Coordinate getCoordinate(int i) {
return parentLine.getCoordinateN(i);
}

public int size() {
return parentLine.getNumPoints();
}

public Coordinate getComponentPoint() {
return getParentCoordinates()[1];
}

public int getResultSize()
{
int resultSegsSize = resultSegs.size();
Expand Down Expand Up @@ -91,6 +99,13 @@ private void init()

public TaggedLineSegment[] getSegments() { return segs; }

/**
* Add a simplified segment to the result.
* This assumes simplified segments are computed in the order
* they occur in the line.
*
* @param seg the result segment to add
*/
public void addToResult(LineSegment seg)
{
resultSegs.add(seg);
Expand All @@ -105,7 +120,7 @@ public LinearRing asLinearRing() {
return parentLine.getFactory().createLinearRing(extractCoordinates(resultSegs));
}

private static Coordinate[] extractCoordinates(List segs)
private static Coordinate[] extractCoordinates(List<LineSegment> segs)
{
Coordinate[] pts = new Coordinate[segs.size() + 1];
LineSegment seg = null;
Expand All @@ -126,4 +141,6 @@ void removeRingEndpoint()
firstSeg.p0 = lastSeg.p0;
resultSegs.remove(resultSegs.size() - 1);
}


}
Loading
Loading