/* * 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 java.util.List; import org.geotools.geometry.iso.coordinate.DirectPositionImpl; import org.geotools.geometry.iso.topograph2D.Coordinate; import org.geotools.geometry.iso.topograph2D.util.CoordinateArrays; import org.opengis.geometry.DirectPosition; /** * Specifies and implements various fundamental Computational Geometric * algorithms. The algorithms supplied in this class are robust for * double-precision floating point. * * * * @source $URL$ */ public class CGAlgorithms { /** * A value that indicates an orientation of clockwise, or a right turn. */ public static final int CLOCKWISE = -1; public static final int RIGHT = CLOCKWISE; /** * A value that indicates an orientation of counterclockwise, or a left * turn. */ public static final int COUNTERCLOCKWISE = 1; public static final int LEFT = COUNTERCLOCKWISE; /** * A value that indicates an orientation of collinear, or no turn * (straight). */ public static final int COLLINEAR = 0; public static final int STRAIGHT = COLLINEAR; /** * Returns the index of the direction of the point <code>q</code> relative * to a vector specified by <code>p1-p2</code>. * * @param p1 * the origin point of the vector * @param p2 * the final point of the vector * @param q * the point to compute the direction to * * @return 1 if q is counter-clockwise (left) from p1-p2 * @return -1 if q is clockwise (right) from p1-p2 * @return 0 if q is collinear with p1-p2 */ public static int orientationIndex(Coordinate p1, Coordinate p2, Coordinate q) { // travelling along p1->p2, turn counter clockwise to get to q return 1, // travelling along p1->p2, turn clockwise to get to q return -1, // p1, p2 and q are colinear return 0. double dx1 = p2.x - p1.x; double dy1 = p2.y - p1.y; double dx2 = q.x - p2.x; double dy2 = q.y - p2.y; return RobustDeterminant.signOfDet2x2(dx1, dy1, dx2, dy2); } public CGAlgorithms() { } /** * Test whether a point lies inside a ring. The ring may be oriented in * either direction. If the point lies on the ring boundary the result of * this method is unspecified. * <p> * This algorithm does not attempt to first check the point against the * envelope of the ring. * * @param p * point to check for ring inclusion * @param ring * assumed to have first point identical to last point * @return <code>true</code> if p is inside ring */ public static boolean isPointInRing(Coordinate p, Coordinate[] ring) { /* * For each segment l = (i-1, i), see if it crosses ray from test point * in positive x direction. */ int crossings = 0; // number of segment/ray crossings for (int i = 1; i < ring.length; i++) { int i1 = i - 1; Coordinate p1 = ring[i]; Coordinate p2 = ring[i1]; if (((p1.y > p.y) && (p2.y <= p.y)) || ((p2.y > p.y) && (p1.y <= p.y))) { double x1 = p1.x - p.x; double y1 = p1.y - p.y; double x2 = p2.x - p.x; double y2 = p2.y - p.y; /* * segment straddles x axis, so compute intersection with * x-axis. */ double xInt = RobustDeterminant.signOfDet2x2(x1, y1, x2, y2) / (y2 - y1); // xsave = xInt; /* * crosses ray if strictly positive intersection. */ if (xInt > 0.0) { crossings++; } } } /* * p is inside if number of crossings is odd. */ if ((crossings % 2) == 1) { return true; } else { return false; } } /** * Test whether a point lies on the line segments defined by a list of * coordinates. * * @return true true if the point is a vertex of the line or lies in the * interior of a line segment in the linestring */ public static boolean isOnLine(Coordinate p, Coordinate[] pt) { LineIntersector lineIntersector = new RobustLineIntersector(); for (int i = 1; i < pt.length; i++) { Coordinate p0 = pt[i - 1]; Coordinate p1 = pt[i]; lineIntersector.computeIntersection(p, p0, p1); if (lineIntersector.hasIntersection()) { return true; } } return false; } /** * Same method as * public static boolean isCCW(Coordinate[] ring) * based on a DirectPosition list. * ===================================================== * Computes whether a ring defined by an array of {@link Coordinate} is * oriented counter-clockwise. * <ul> * <li>The list of points is assumed to have the first and last points * equal. * <li>This will handle coordinate lists which contain repeated points. * </ul> * This algorithm is <b>only</b> guaranteed to work with valid rings. If * the ring is invalid (e.g. self-crosses or touches), the computed result * <b>may</b> not be correct. * * @param ring * a list of direct positions forming a ring * @return <code>true</code> if the ring is oriented counter-clockwise. */ public static boolean isCCW(List<DirectPosition> ring) { Coordinate[] coords = CoordinateArrays.toCoordinateArray(ring); return isCCW(coords); } /** * Computes whether a ring defined by an array of {@link Coordinate} is * oriented counter-clockwise. * <ul> * <li>The list of points is assumed to have the first and last points * equal. * <li>This will handle coordinate lists which contain repeated points. * </ul> * This algorithm is <b>only</b> guaranteed to work with valid rings. If * the ring is invalid (e.g. self-crosses or touches), the computed result * <b>may</b> not be correct. * * @param ring * an array of coordinates forming a ring * @return <code>true</code> if the ring is oriented counter-clockwise. */ public static boolean isCCW(Coordinate[] ring) { // # of points without closing endpoint int nPts = ring.length - 1; // find highest point Coordinate hiPt = ring[0]; int hiIndex = 0; for (int i = 1; i <= nPts; i++) { Coordinate p = ring[i]; if (p.y > hiPt.y) { hiPt = p; hiIndex = i; } } // find distinct point before highest point int iPrev = hiIndex; do { iPrev = iPrev - 1; if (iPrev < 0) iPrev = nPts; } while (ring[iPrev].equals2D(hiPt) && iPrev != hiIndex); // find distinct point after highest point int iNext = hiIndex; do { iNext = (iNext + 1) % nPts; } while (ring[iNext].equals2D(hiPt) && iNext != hiIndex); Coordinate prev = ring[iPrev]; Coordinate next = ring[iNext]; /** * This check catches cases where the ring contains an A-B-A * configuration of points. This can happen if the ring does not contain * 3 distinct points (including the case where the input array has fewer * than 4 elements), or it contains coincident line segments. */ if (prev.equals2D(hiPt) || next.equals2D(hiPt) || prev.equals2D(next)) return false; int disc = computeOrientation(prev, hiPt, next); /** * If disc is exactly 0, lines are collinear. There are two possible * cases: (1) the lines lie along the x axis in opposite directions (2) * the lines lie on top of one another * * (1) is handled by checking if next is left of prev ==> CCW (2) will * never happen if the ring is valid, so don't check for it (Might want * to assert this) */ boolean isCCW = false; if (disc == 0) { // poly is CCW if prev x is right of next x isCCW = (prev.x > next.x); } else { // if area is positive, points are ordered CCW isCCW = (disc > 0); } return isCCW; } /** * Computes the orientation of a point q to the directed line segment p1-p2. * The orientation of a point relative to a directed line segment indicates * which way you turn to get to q after travelling from p1 to p2. * * @return 1 if q is counter-clockwise from p1-p2 * @return -1 if q is clockwise from p1-p2 * @return 0 if q is collinear with p1-p2 */ public static int computeOrientation(Coordinate p1, Coordinate p2, Coordinate q) { return orientationIndex(p1, p2, q); } /** * Computes the distance from a point p to a line segment AB * * Note: NON-ROBUST! * * @param p * the point to compute the distance for * @param A * one point of the line * @param B * another point of the line (must be different to A) * @return the distance from p to line segment AB */ public static double distancePointLine(Coordinate p, Coordinate A, Coordinate B) { // if start==end, then use pt distance if (A.equals(B)) return p.distance(A); // otherwise use comp.graphics.algorithms Frequently Asked Questions // method /* * (1) AC dot AB r = --------- ||AB||^2 r has the following meaning: r=0 * P = A r=1 P = B r<0 P is on the backward extension of AB r>1 P is on * the forward extension of AB 0<r<1 P is interior to AB */ double r = ((p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); if (r <= 0.0) return p.distance(A); if (r >= 1.0) return p.distance(B); /* * (2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- * L^2 * * Then the distance from C to P = |s|*L. */ double s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); return Math.abs(s) * Math.sqrt(((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))); } /** * Computes the perpendicular distance from a point p to the (infinite) line * containing the points AB * * @param p * the point to compute the distance for * @param A * one point of the line * @param B * another point of the line (must be different to A) * @return the distance from p to line AB */ public static double distancePointLinePerpendicular(Coordinate p, Coordinate A, Coordinate B) { // use comp.graphics.algorithms Frequently Asked Questions method /* * (2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- * L^2 * * Then the distance from C to P = |s|*L. */ double s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); return Math.abs(s) * Math.sqrt(((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))); } /** * Computes the distance from a line segment AB to a line segment CD * * Note: NON-ROBUST! * * @param A * a point of one line * @param B * the second point of (must be different to A) * @param C * one point of the line * @param D * another point of the line (must be different to A) */ public static double distanceLineLine(Coordinate A, Coordinate B, Coordinate C, Coordinate D) { // check for zero-length segments if (A.equals(B)) return distancePointLine(A, C, D); if (C.equals(D)) return distancePointLine(D, A, B); // AB and CD are line segments /* * from comp.graphics.algo * * Solving the above for r and s yields (Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy) r = * ----------------------------- (eqn 1) (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) * * (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- (eqn * 2) (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) Let P be the position vector of the * intersection point, then P=A+r(B-A) or Px=Ax+r(Bx-Ax) Py=Ay+r(By-Ay) * By examining the values of r & s, you can also determine some other * limiting conditions: If 0<=r<=1 & 0<=s<=1, intersection exists r<0 * or r>1 or s<0 or s>1 line segments do not intersect If the * denominator in eqn 1 is zero, AB & CD are parallel If the numerator * in eqn 1 is also zero, AB & CD are collinear. * */ double r_top = (A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y); double r_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x); double s_top = (A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y); double s_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x); if ((r_bot == 0) || (s_bot == 0)) { return Math.min(distancePointLine(A, C, D), Math.min( distancePointLine(B, C, D), Math.min(distancePointLine(C, A, B), distancePointLine(D, A, B)))); } double s = s_top / s_bot; double r = r_top / r_bot; if ((r < 0) || (r > 1) || (s < 0) || (s > 1)) { // no intersection return Math.min(distancePointLine(A, C, D), Math.min( distancePointLine(B, C, D), Math.min(distancePointLine(C, A, B), distancePointLine(D, A, B)))); } return 0.0; // intersection exists } /** * Returns the signed area for a ring. The area is positive if the ring is * oriented CW. */ public static double signedArea(Coordinate[] ring) { if (ring.length < 3) return 0.0; double sum = 0.0; for (int i = 0; i < ring.length - 1; i++) { double bx = ring[i].x; double by = ring[i].y; double cx = ring[i + 1].x; double cy = ring[i + 1].y; sum += (bx + cx) * (cy - by); } return -sum / 2.0; } /** * Computes the length of a linestring specified by a sequence of points. * * @param pts * the points specifying the linestring * @return the length of the linestring */ // TODO evtl wieder einkommentieren wenn von operationen benoetigt // public static double length(CoordinateSequence pts) { // if (pts.size() < 1) return 0.0; // double sum = 0.0; // for (int i = 1; i < pts.size(); i++) { // sum += pts.getCoordinate(i).distance(pts.getCoordinate(i - 1)); // } // return sum; // } }