NodingIntersectionFinder.java
/*
* Copyright (c) 2016 Vivid Solutions.
*
* 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.noding;
import java.util.ArrayList;
import java.util.List;
import org.locationtech.jts.algorithm.LineIntersector;
import org.locationtech.jts.geom.Coordinate;
/**
* Finds non-noded intersections in a set of {@link SegmentString}s,
* if any exist.
* <p>
* Non-noded intersections include:
* <ul>
* <li><b>Interior intersections</b> which lie in the interior of a segment
* (with another segment interior or with a vertex or endpoint)
* <li><b>Vertex intersections</b> which occur at vertices in the interior of {@link SegmentString}s
* (with a segment string endpoint or with another interior vertex)
* </ul>
* The finder can be limited to finding only interior intersections
* by setting {@link #setInteriorIntersectionsOnly(boolean)}.
* <p>
* By default only the first intersection is found,
* but all can be found by setting {@link #setFindAllIntersections(boolean)}
*
* @version 1.7
*/
public class NodingIntersectionFinder
implements SegmentIntersector
{
/**
* Creates a finder which tests if there is at least one intersection.
* Uses short-circuiting for efficient performance.
* The intersection found is recorded.
*
* @param li a line intersector
* @return a finder which tests if there is at least one intersection.
*/
public static NodingIntersectionFinder createAnyIntersectionFinder(LineIntersector li)
{
return new NodingIntersectionFinder(li);
}
/**
* Creates a finder which finds all intersections.
* The intersections are recorded for later inspection.
*
* @param li a line intersector
* @return a finder which finds all intersections.
*/
public static NodingIntersectionFinder createAllIntersectionsFinder(LineIntersector li)
{
NodingIntersectionFinder finder = new NodingIntersectionFinder(li);
finder.setFindAllIntersections(true);
return finder;
}
/**
* Creates a finder which finds all interior intersections.
* The intersections are recorded for later inspection.
*
* @param li a line intersector
* @return a finder which finds all interior intersections.
*/
public static NodingIntersectionFinder createInteriorIntersectionsFinder(LineIntersector li)
{
NodingIntersectionFinder finder = new NodingIntersectionFinder(li);
finder.setFindAllIntersections(true);
finder.setInteriorIntersectionsOnly(true);
return finder;
}
/**
* Creates an finder which counts all intersections.
* The intersections are note recorded to reduce memory usage.
*
* @param li a line intersector
* @return a finder which counts all intersections.
*/
public static NodingIntersectionFinder createIntersectionCounter(LineIntersector li)
{
NodingIntersectionFinder finder = new NodingIntersectionFinder(li);
finder.setFindAllIntersections(true);
finder.setKeepIntersections(false);
return finder;
}
/**
* Creates an finder which counts all interior intersections.
* The intersections are note recorded to reduce memory usage.
*
* @param li a line intersector
* @return a finder which counts all interior intersections.
*/
public static NodingIntersectionFinder createInteriorIntersectionCounter(LineIntersector li)
{
NodingIntersectionFinder finder = new NodingIntersectionFinder(li);
finder.setInteriorIntersectionsOnly(true);
finder.setFindAllIntersections(true);
finder.setKeepIntersections(false);
return finder;
}
private boolean findAllIntersections = false;
private boolean isCheckEndSegmentsOnly = false;
private boolean keepIntersections = true;
private boolean isInteriorIntersectionsOnly = false;
private LineIntersector li;
private Coordinate interiorIntersection = null;
private Coordinate[] intSegments = null;
private List intersections = new ArrayList();
private int intersectionCount = 0;
/**
* Creates an intersection finder which finds an intersection
* if one exists
*
* @param li the LineIntersector to use
*/
public NodingIntersectionFinder(LineIntersector li)
{
this.li = li;
interiorIntersection = null;
}
/**
* Sets whether all intersections should be computed.
* When this is <code>false</code> (the default value)
* the value of {@link #isDone()} is <code>true</code> after the first intersection is found.
* <p>
* Default is <code>false</code>.
*
* @param findAllIntersections whether all intersections should be computed
*/
public void setFindAllIntersections(boolean findAllIntersections)
{
this.findAllIntersections = findAllIntersections;
}
/**
* Sets whether only interior (proper) intersections will be found.
* @param isInteriorIntersectionsOnly whether to find only interior intersections
*/
public void setInteriorIntersectionsOnly(boolean isInteriorIntersectionsOnly)
{
this.isInteriorIntersectionsOnly = isInteriorIntersectionsOnly;
}
/**
* Sets whether only end segments should be tested for intersection.
* This is a performance optimization that may be used if
* the segments have been previously noded by an appropriate algorithm.
* It may be known that any potential noding failures will occur only in
* end segments.
*
* @param isCheckEndSegmentsOnly whether to test only end segments
*/
public void setCheckEndSegmentsOnly(boolean isCheckEndSegmentsOnly)
{
this.isCheckEndSegmentsOnly = isCheckEndSegmentsOnly;
}
/**
* Sets whether intersection points are recorded.
* If the only need is to count intersection points, this can be set to <code>false</code>.
* <p>
* Default is <code>true</code>.
*
* @param keepIntersections indicates whether intersections should be recorded
*/
public void setKeepIntersections(boolean keepIntersections)
{
this.keepIntersections = keepIntersections;
}
/**
* Gets the intersections found.
*
* @return a List of {@link Coordinate}
*/
public List getIntersections()
{
return intersections;
}
/**
* Gets the count of intersections found.
*
* @return the intersection count
*/
public int count()
{
return intersectionCount;
}
/**
* Tests whether an intersection was found.
*
* @return true if an intersection was found
*/
public boolean hasIntersection()
{
return interiorIntersection != null;
}
/**
* Gets the computed location of the intersection.
* Due to round-off, the location may not be exact.
*
* @return the coordinate for the intersection location
*/
public Coordinate getIntersection()
{
return interiorIntersection;
}
/**
* Gets the endpoints of the intersecting segments.
*
* @return an array of the segment endpoints (p00, p01, p10, p11)
*/
public Coordinate[] getIntersectionSegments()
{
return intSegments;
}
/**
* This method is called by clients
* of the {@link SegmentIntersector} class to process
* intersections for two segments of the {@link SegmentString}s being intersected.
* Note that some clients (such as <code>MonotoneChain</code>s) may optimize away
* this call for segment pairs which they have determined do not intersect
* (e.g. by an disjoint envelope test).
*/
public void processIntersections(
SegmentString e0, int segIndex0,
SegmentString e1, int segIndex1
)
{
// short-circuit if intersection already found
if (! findAllIntersections && hasIntersection())
return;
// don't bother intersecting a segment with itself
boolean isSameSegString = e0 == e1;
boolean isSameSegment = isSameSegString && segIndex0 == segIndex1;
if (isSameSegment) return;
/**
* If enabled, only test end segments (on either segString).
*
*/
if (isCheckEndSegmentsOnly) {
boolean isEndSegPresent = isEndSegment(e0, segIndex0) || isEndSegment(e1, segIndex1);
if (! isEndSegPresent)
return;
}
Coordinate p00 = e0.getCoordinate(segIndex0);
Coordinate p01 = e0.getCoordinate(segIndex0 + 1);
Coordinate p10 = e1.getCoordinate(segIndex1);
Coordinate p11 = e1.getCoordinate(segIndex1 + 1);
boolean isEnd00 = segIndex0 == 0;
boolean isEnd01 = segIndex0 + 2 == e0.size();
boolean isEnd10 = segIndex1 == 0;
boolean isEnd11 = segIndex1 + 2 == e1.size();
li.computeIntersection(p00, p01, p10, p11);
//if (li.hasIntersection() && li.isProper()) Debug.println(li);
/**
* Check for an intersection in the interior of a segment
*/
boolean isInteriorInt = li.hasIntersection() && li.isInteriorIntersection();
/**
* Check for an intersection between two vertices which are not both endpoints.
*/
boolean isInteriorVertexInt = false;
if (! isInteriorIntersectionsOnly) {
boolean isAdjacentSegment = isSameSegString && Math.abs(segIndex1 - segIndex0) <= 1;
isInteriorVertexInt = (! isAdjacentSegment) && isInteriorVertexIntersection(p00, p01, p10, p11,
isEnd00, isEnd01, isEnd10, isEnd11);
}
if (isInteriorInt || isInteriorVertexInt) {
// found an intersection!
intSegments = new Coordinate[4];
intSegments[0] = p00;
intSegments[1] = p01;
intSegments[2] = p10;
intSegments[3] = p11;
//TODO: record endpoint intersection(s)
interiorIntersection = li.getIntersection(0);
if (keepIntersections) intersections.add(interiorIntersection);
intersectionCount++;
}
}
/**
* Tests if an intersection occurs between a segmentString interior vertex and another vertex.
* Note that intersections between two endpoint vertices are valid noding,
* and are not flagged.
*
* @param p00 a segment vertex
* @param p01 a segment vertex
* @param p10 a segment vertex
* @param p11 a segment vertex
* @param isEnd00 true if vertex is a segmentString endpoint
* @param isEnd01 true if vertex is a segmentString endpoint
* @param isEnd10 true if vertex is a segmentString endpoint
* @param isEnd11 true if vertex is a segmentString endpoint
* @return true if an intersection is found
*/
private static boolean isInteriorVertexIntersection(
Coordinate p00, Coordinate p01,
Coordinate p10, Coordinate p11,
boolean isEnd00, boolean isEnd01,
boolean isEnd10, boolean isEnd11) {
if (isInteriorVertexIntersection(p00, p10, isEnd00, isEnd10)) return true;
if (isInteriorVertexIntersection(p00, p11, isEnd00, isEnd11)) return true;
if (isInteriorVertexIntersection(p01, p10, isEnd01, isEnd10)) return true;
if (isInteriorVertexIntersection(p01, p11, isEnd01, isEnd11)) return true;
return false;
}
/**
* Tests if two vertices with at least one in a segmentString interior
* are equal.
*
* @param p0 a segment vertex
* @param p1 a segment vertex
* @param isEnd0 true if vertex is a segmentString endpoint
* @param isEnd1 true if vertex is a segmentString endpoint
* @return true if an intersection is found
*/
private static boolean isInteriorVertexIntersection(
Coordinate p0, Coordinate p1,
boolean isEnd0, boolean isEnd1) {
// Intersections between endpoints are valid nodes, so not reported
if (isEnd0 && isEnd1) return false;
if (p0.equals2D(p1)) {
return true;
}
return false;
}
/**
* Tests whether a segment in a {@link SegmentString} is an end segment.
* (either the first or last).
*
* @param segStr a segment string
* @param index the index of a segment in the segment string
* @return true if the segment is an end segment
*/
private static boolean isEndSegment(SegmentString segStr, int index)
{
if (index == 0) return true;
if (index >= segStr.size() - 2) return true;
return false;
}
/**
*
*/
public boolean isDone()
{
if (findAllIntersections) return false;
return interiorIntersection != null;
}
}