/* * 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 v1.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-v10.html * and the Eclipse Distribution License is available at * * http://www.eclipse.org/org/documents/edl-v10.php. */ package org.locationtech.jts.algorithm; /** * @version 1.7 */ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.PrecisionModel; import org.locationtech.jts.io.WKTWriter; import org.locationtech.jts.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 {@link PrecisionModel}. * <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 { /** * These are deprecated, due to ambiguous naming */ public final static int DONT_INTERSECT = 0; public final static int DO_INTERSECT = 1; public final static int COLLINEAR = 2; /** * 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; /** * Indicates that line segments intersect in a line segment */ public final static int COLLINEAR_INTERSECTION = 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 distance 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; } /** * 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 Coordinate getEndpoint(int segmentIndex, int ptIndex) { return inputLines[segmentIndex][ptIndex]; } /** * 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_INTERSECTION; } /** * 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() { StringBuilder catBuilder = new StringBuilder(); if (isEndPoint()) catBuilder.append(" endpoint"); if (isProper) catBuilder.append(" proper"); if (isCollinear()) catBuilder.append(" collinear"); return catBuilder.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 != NO_INTERSECTION; } /** * 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 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 (order) 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 input 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; } }