/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.algorithm; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.geometry.util.Assert; /** * A <code>LineIntersector</code> is an algorithm that can both test whether * two line segments intersect and compute the intersection point(s) * if they do. * <p> * There are three possible outcomes when determining whether two line segments intersect: * <ul> * <li>{@link #NO_INTERSECTION} - the segments do not intersect * <li>{@link #POINT_INTERSECTION - the segments intersect in a single point * <li>{@link #COLLINEAR_INTERSECTION - the segments are collinear and they intersect in a line segment * </ul> * For segments which intersect in a single point, the point may be either an endpoint * or in the interior of each segment. * If the point lies in the interior of both segments, * this is termed a <i>proper intersection</i>. * The method {@link #isProper()} test for this situation. * <p> * The intersection point(s) may be computed in a precise or non-precise manner. * Computing an intersection point precisely involves rounding it * via a supplied scale. * <p> * LineIntersectors do not perform an initial envelope intersection test * to determine if the segments are disjoint. * This is because this class is likely to be used in a context where * envelope overlap is already known to occur (or be likely). * * @version 1.7 */ public abstract class LineIntersector { public final static int COLLINEAR = 2; /** * Indicates that line segments intersect in a line segment */ public final static int COLLINEAR_INTERSECTION = 2; public final static int DO_INTERSECT = 1; /** * These are deprecated, due to ambiguous naming */ public final static int DONT_INTERSECT = 0; /** * Indicates that line segments do not intersect */ public final static int NO_INTERSECTION = 0; /** * Indicates that line segments intersect in a single point */ public final static int POINT_INTERSECTION = 1; /** * 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(final Point p, final Point p1, final Point p2) { final double dx = p.getX() - p1.getX(); final double dy = p.getY() - p1.getY(); final double dist = Math.sqrt(dx * dx + dy * dy); // dummy value Assert.isTrue(!(dist == 0.0 && !p.equals(p1)), "Invalid distance calculation"); return dist; } protected double intersectionX1 = Double.NaN; protected double intersectionY1 = Double.NaN; protected double intersectionX2 = Double.NaN; protected double intersectionY2 = Double.NaN; protected double pointAX = Double.NaN; protected double pointAY = Double.NaN; protected double pointBX = Double.NaN; protected double pointBY = Double.NaN; protected boolean isProper; protected int intersectionCount; protected final double scale; protected double line1x1; protected double line1y1; protected double line1x2; protected double line1y2; protected double line2x1; protected double line2y1; protected double line2x2; protected double line2y2; public LineIntersector() { this.intersectionCount = 0; this.scale = 0; } public LineIntersector(final double scale) { this.scale = scale; } protected abstract int computeIntersect(final double line1x1, final double line1y1, final double line1x2, final double line1y2, final double line2x1, final double line2y1, final double line2x2, final double line2y2); /** * 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 boolean computeIntersection(double x, double y, double x1, double y1, double x2, double y2); public final boolean computeIntersection(final double line1x1, final double line1y1, final double line1x2, final double line1y2, final double line2x1, final double line2y1, final double line2x2, final double line2y2) { this.line1x1 = line1x1; this.line1y1 = line1y1; this.line1x2 = line1x2; this.line1y2 = line1y2; this.line2x1 = line2x1; this.line2y1 = line2y1; this.line2x2 = line2x2; this.line2y2 = line2y2; this.intersectionCount = computeIntersect(line1x1, line1y1, line1x2, line1y2, line2x1, line2y1, line2x2, line2y2); return hasIntersection(); } /** * 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 final boolean computeIntersectionPoints(final Point p1, final Point p2, final Point p3, final Point p4) { final double x1 = p1.getX(); final double y1 = p1.getY(); final double x2 = p2.getX(); final double y2 = p2.getY(); final double x3 = p3.getX(); final double y3 = p3.getY(); final double x4 = p4.getX(); final double y4 = p4.getY(); return computeIntersection(x1, y1, x2, y2, x3, y3, x4, y4); } public boolean equalsIntersection(final int intersectionIndex, final double x, final double y) { if (intersectionIndex == 0) { return x == this.intersectionX1 && y == this.intersectionY1; } else if (intersectionIndex == 1) { return x == this.intersectionX2 && y == this.intersectionY2; } else { throw new ArrayIndexOutOfBoundsException(intersectionIndex); } } /** * Computes the "edge distance" of an intersection point along the specified input line segment. * * @param segmentIndex is 0 or 1 * @param index is 0 or 1 * * @return the edge distance of the intersection point */ public double getEdgeDistance(final int segmentIndex, final int index) { double x; double y; if (index == 0) { x = this.intersectionX1; y = this.intersectionY1; } else if (index == 1) { x = this.intersectionX2; y = this.intersectionY2; } else { throw new ArrayIndexOutOfBoundsException(index); } final double x1; final double y1; final double x2; final double y2; if (segmentIndex == 0) { x1 = this.line1x1; y1 = this.line1y1; x2 = this.line1x2; y2 = this.line1y2; } else { x1 = this.line2x1; y1 = this.line2y1; x2 = this.line2x2; y2 = this.line2y2; } if (x == x1 && y == y1) { return 0.0; } else if (x == x2 && y == y2) { final double dx = Math.abs(x2 - x1); final double dy = Math.abs(y2 - y1); if (dx > dy) { return dx; } else { return dy; } } else { final double dx = Math.abs(x2 - x1); final double dy = Math.abs(y2 - y1); final double pdx = Math.abs(x - x1); final double pdy = Math.abs(y - y1); double dist; if (dx > dy) { dist = pdx; } else { dist = pdy; } // hack to ensure that non-endpoints always have a non-zero distance if (dist == 0.0) { dist = Math.max(pdx, pdy); } return dist; } } /** * Gets an endpoint of an input segment. * * @param segmentIndex the index of the input segment (0 or 1) * @param ptIndex the index of the endpoint (0 or 1) * @return the specified endpoint */ public Point getEndpoint(final int segmentIndex, final int ptIndex) { double x; double y; if (segmentIndex == 0) { if (ptIndex == 0) { x = this.line1x1; y = this.line1y1; } else { x = this.line1x2; y = this.line1y2; } } else { if (ptIndex == 0) { x = this.line2x1; y = this.line2y1; } else { x = this.line2x2; y = this.line2y2; } } return new PointDoubleXY(x, y); } /* * public String toString() { String str = inputLines[0][0] + "-" + inputLines[0][1] + " " + * inputLines[1][0] + "-" + inputLines[1][1] + " : " + getTopologySummary(); return str; } */ /** * Returns the intIndex'th intersection point * * @param intIndex is 0 or 1 * * @return the intIndex'th intersection point */ public Point getIntersection(final int intersectionIndex) { if (intersectionIndex == 0) { return new PointDoubleXY(this.intersectionX1, this.intersectionY1); } else if (intersectionIndex == 1) { return new PointDoubleXY(this.intersectionX2, this.intersectionY2); } else { throw new ArrayIndexOutOfBoundsException(intersectionIndex); } } /** * Returns the number of intersection points found. This will be either 0, 1 or 2. * * @return the number of intersection points found (0, 1, or 2) */ public int getIntersectionCount() { return this.intersectionCount; } private String getTopologySummary() { final StringBuilder catBuf = new StringBuilder(); if (isEndPoint()) { catBuf.append(" endpoint"); } if (this.isProper) { catBuf.append(" proper"); } if (isCollinear()) { catBuf.append(" collinear"); } return catBuf.toString(); } /** * Tests whether the input geometries intersect. * * @return true if the input geometries intersect */ public boolean hasIntersection() { return this.intersectionCount != NO_INTERSECTION; } protected boolean isCollinear() { return this.intersectionCount == COLLINEAR_INTERSECTION; } protected boolean isEndPoint() { return hasIntersection() && !this.isProper; } /** * 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; } else if (isInteriorIntersection(1)) { return true; } else { 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(final int inputLineIndex) { if (this.intersectionCount > 0) { final double x1; final double y1; final double x2; final double y2; if (inputLineIndex == 0) { x1 = this.line1x1; y1 = this.line1y1; x2 = this.line1x2; y2 = this.line1y2; } else { x1 = this.line2x1; y1 = this.line2y1; x2 = this.line2x2; y2 = this.line2y2; } for (int i = 0; i < this.intersectionCount; i++) { if (!(equalsIntersection(i, x1, y1) || equalsIntersection(i, x2, y2))) { return true; } } } return false; } /** * 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(final Point point) { final double x = point.getX(); final double y = point.getY(); if (this.intersectionCount == NO_INTERSECTION) { return false; } else { if (this.intersectionX1 == x && this.intersectionY1 == y) { return true; } if (this.intersectionCount == COLLINEAR_INTERSECTION) { if (this.intersectionX2 == x && this.intersectionY2 == y) { 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() && this.isProper; } @Override public String toString() { return "LINESTRING(" + this.line1x1 + ' ' + this.line1y1 + ',' + this.line1x2 + ' ' + this.line1y2 + ") - LINESTRING(" + this.line2x1 + ' ' + this.line2y1 + ',' + this.line2x2 + ' ' + this.line2y2 + ")" + getTopologySummary(); } }