/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2006 Vivid Solutions
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.geometry.iso.util.algorithm2D;
import org.geotools.geometry.iso.PrecisionModel;
import org.geotools.geometry.iso.topograph2D.Coordinate;
import org.geotools.geometry.iso.util.Assert;
/**
* A LineIntersector is an algorithm that can both test whether two line
* segments intersect and compute the intersection point if they do. The
* intersection point may be computed in a precise or non-precise manner.
* Computing it precisely involves rounding it to an integer. (This assumes that
* the input coordinates have been made precise by scaling them to an integer
* grid.)
*
*
* @source $URL$
*/
public abstract class LineIntersector {
public final static int DONT_INTERSECT = 0;
public final static int DO_INTERSECT = 1;
public final static int COLLINEAR = 2;
/**
* Computes the "edge distance" of an intersection point p along a segment.
* The edge distance is a metric of the point along the edge. The metric
* used is a robust and easy to compute metric function. It is <b>not</b>
* equivalent to the usual Euclidean metric. It relies on the fact that
* either the x or the y ordinates of the points in the edge are unique,
* depending on whether the edge is longer in the horizontal or vertical
* direction.
* <p>
* NOTE: This function may produce incorrect distances for inputs where p is
* not precisely on p1-p2 (E.g. p = (139,9) p1 = (139,10), p2 = (280,1)
* produces distanct 0.0, which is incorrect.
* <p>
* My hypothesis is that the function is safe to use for points which are
* the result of <b>rounding</b> points which lie on the line, but not safe
* to use for <b>truncated</b> points.
*/
public static double computeEdgeDistance(Coordinate p, Coordinate p0,
Coordinate p1) {
double dx = Math.abs(p1.x - p0.x);
double dy = Math.abs(p1.y - p0.y);
double dist = -1.0; // sentinel value
if (p.equals(p0)) {
dist = 0.0;
} else if (p.equals(p1)) {
if (dx > dy)
dist = dx;
else
dist = dy;
} else {
double pdx = Math.abs(p.x - p0.x);
double pdy = Math.abs(p.y - p0.y);
if (dx > dy)
dist = pdx;
else
dist = pdy;
// <FIX>
// hack to ensure that non-endpoints always have a non-zero distance
if (dist == 0.0 && !p.equals(p0)) {
dist = Math.max(pdx, pdy);
}
}
Assert.isTrue(!(dist == 0.0 && !p.equals(p0)),
"Bad distance calculation");
return dist;
}
/**
* This function is non-robust, since it may compute the square of large
* numbers. Currently not sure how to improve this.
*/
public static double nonRobustComputeEdgeDistance(Coordinate p,
Coordinate p1, Coordinate p2) {
double dx = p.x - p1.x;
double dy = p.y - p1.y;
double dist = Math.sqrt(dx * dx + dy * dy); // dummy value
Assert.isTrue(!(dist == 0.0 && !p.equals(p1)),
"Invalid distance calculation");
return dist;
}
protected int result;
protected Coordinate[][] inputLines = new Coordinate[2][2];
protected Coordinate[] intPt = new Coordinate[2];
/**
* The indexes of the endpoints of the intersection lines, in order along
* the corresponding line
*/
protected int[][] intLineIndex;
protected boolean isProper;
protected Coordinate pa;
protected Coordinate pb;
/**
* If makePrecise is true, computed intersection coordinates will be made
* precise using Coordinate#makePrecise
*/
protected PrecisionModel precisionModel = null;
// public int numIntersects = 0;
public LineIntersector() {
intPt[0] = new Coordinate();
intPt[1] = new Coordinate();
// alias the intersection points for ease of reference
pa = intPt[0];
pb = intPt[1];
result = 0;
}
/**
* Force computed intersection to be rounded to a given precision model
*
* @param precisionModel
* @deprecated use <code>setPrecisionModel</code> instead
*/
public void setMakePrecise(PrecisionModel precisionModel) {
this.precisionModel = precisionModel;
}
/**
* Force computed intersection to be rounded to a given precision model. No
* getter is provided, because the precision model is not required to be
* specified.
*
* @param precisionModel
*/
public void setPrecisionModel(PrecisionModel precisionModel) {
this.precisionModel = precisionModel;
}
/**
* Compute the intersection of a point p and the line p1-p2. This function
* computes the boolean value of the hasIntersection test. The actual value
* of the intersection (if there is one) is equal to the value of
* <code>p</code>.
*/
public abstract void computeIntersection(Coordinate p, Coordinate p1,
Coordinate p2);
protected boolean isCollinear() {
return result == COLLINEAR;
}
/**
* Computes the intersection of the lines p1-p2 and p3-p4. This function
* computes both the boolean value of the hasIntersection test and the
* (approximate) value of the intersection point itself (if there is one).
*/
public void computeIntersection(Coordinate p1, Coordinate p2,
Coordinate p3, Coordinate p4) {
inputLines[0][0] = p1;
inputLines[0][1] = p2;
inputLines[1][0] = p3;
inputLines[1][1] = p4;
result = computeIntersect(p1, p2, p3, p4);
// numIntersects++;
}
protected abstract int computeIntersect(Coordinate p1, Coordinate p2,
Coordinate q1, Coordinate q2);
/*
* public String toString() { String str = inputLines[0][0] + "-" +
* inputLines[0][1] + " " + inputLines[1][0] + "-" + inputLines[1][1] + " : " +
* getTopologySummary(); return str; }
*/
// public String toString() {
// return WKTWriter.toLineString(inputLines[0][0], inputLines[0][1]) + " - "
// + WKTWriter.toLineString(inputLines[1][0], inputLines[1][1])
// + getTopologySummary();
// }
private String getTopologySummary() {
StringBuffer catBuf = new StringBuffer();
if (isEndPoint())
catBuf.append(" endpoint");
if (isProper)
catBuf.append(" proper");
if (isCollinear())
catBuf.append(" collinear");
return catBuf.toString();
}
protected boolean isEndPoint() {
return hasIntersection() && !isProper;
}
/**
* Tests whether the input geometries intersect.
*
* @return true if the input geometries intersect
*/
public boolean hasIntersection() {
return result != DONT_INTERSECT;
}
/**
* Returns the number of intersection points found. This will be either 0, 1
* or 2.
*/
public int getIntersectionNum() {
return result;
}
/**
* Returns the intIndex'th intersection point
*
* @param intIndex
* is 0 or 1
*
* @return the intIndex'th intersection point
*/
public Coordinate getIntersection(int intIndex) {
return intPt[intIndex];
}
protected void computeIntLineIndex() {
if (intLineIndex == null) {
intLineIndex = new int[2][2];
computeIntLineIndex(0);
computeIntLineIndex(1);
}
}
/**
* Test whether a point is a intersection point of two line segments. Note
* that if the intersection is a line segment, this method only tests for
* equality with the endpoints of the intersection segment. It does <b>not</b>
* return true if the input point is internal to the intersection segment.
*
* @return true if the input point is one of the intersection points.
*/
public boolean isIntersection(Coordinate pt) {
for (int i = 0; i < result; i++) {
if (intPt[i].equals2D(pt)) {
return true;
}
}
return false;
}
/**
* Tests whether either intersection point is an interior point of one of
* the input segments.
*
* @return <code>true</code> if either intersection point is in the
* interior of one of the input segments
*/
public boolean isInteriorIntersection() {
if (isInteriorIntersection(0))
return true;
if (isInteriorIntersection(1))
return true;
return false;
}
/**
* Tests whether either intersection point is an interior point of the
* specified input segment.
*
* @return <code>true</code> if either intersection point is in the
* interior of the input segment
*/
public boolean isInteriorIntersection(int inputLineIndex) {
for (int i = 0; i < result; i++) {
if (!(intPt[i].equals2D(inputLines[inputLineIndex][0]) || intPt[i]
.equals2D(inputLines[inputLineIndex][1]))) {
return true;
}
}
return false;
}
/**
* Tests whether an intersection is proper. <br>
* The intersection between two line segments is considered proper if they
* intersect in a single point in the interior of both segments (e.g. the
* intersection is a single point and is not equal to any of the endpoints).
* <p>
* The intersection between a point and a line segment is considered proper
* if the point lies in the interior of the segment (e.g. is not equal to
* either of the endpoints).
*
* @return true if the intersection is proper
*/
public boolean isProper() {
return hasIntersection() && isProper;
}
/**
* Computes the intIndex'th intersection point in the direction of a
* specified input line segment
*
* @param segmentIndex
* is 0 or 1
* @param intIndex
* is 0 or 1
*
* @return the intIndex'th intersection point in the direction of the
* specified input line segment
*/
public Coordinate getIntersectionAlongSegment(int segmentIndex, int intIndex) {
// lazily compute int line array
computeIntLineIndex();
return intPt[intLineIndex[segmentIndex][intIndex]];
}
/**
* Computes the index of the intIndex'th intersection point in the direction
* of a specified input line segment
*
* @param segmentIndex
* is 0 or 1
* @param intIndex
* is 0 or 1
*
* @return the index of the intersection point along the segment (0 or 1)
*/
public int getIndexAlongSegment(int segmentIndex, int intIndex) {
computeIntLineIndex();
return intLineIndex[segmentIndex][intIndex];
}
protected void computeIntLineIndex(int segmentIndex) {
double dist0 = getEdgeDistance(segmentIndex, 0);
double dist1 = getEdgeDistance(segmentIndex, 1);
if (dist0 > dist1) {
intLineIndex[segmentIndex][0] = 0;
intLineIndex[segmentIndex][1] = 1;
} else {
intLineIndex[segmentIndex][0] = 1;
intLineIndex[segmentIndex][1] = 0;
}
}
/**
* Computes the "edge distance" of an intersection point along the specified
* input line segment.
*
* @param segmentIndex
* is 0 or 1
* @param intIndex
* is 0 or 1
*
* @return the edge distance of the intersection point
*/
public double getEdgeDistance(int segmentIndex, int intIndex) {
double dist = computeEdgeDistance(intPt[intIndex],
inputLines[segmentIndex][0], inputLines[segmentIndex][1]);
return dist;
}
}