/* * 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.topograph2D.Coordinate; import org.geotools.geometry.iso.topograph2D.Envelope; import org.geotools.geometry.iso.util.Assert; /** * A robust version of {@link LineIntersector}. * * @see RobustDeterminant * * @source $URL$ */ public class RobustLineIntersector extends LineIntersector { public RobustLineIntersector() { } public void computeIntersection(Coordinate p, Coordinate p1, Coordinate p2) { isProper = false; // do between check first, since it is faster than the orientation test if (Envelope.intersects(p1, p2, p)) { if ((CGAlgorithms.orientationIndex(p1, p2, p) == 0) && (CGAlgorithms.orientationIndex(p2, p1, p) == 0)) { isProper = true; if (p.equals(p1) || p.equals(p2)) { isProper = false; } result = DO_INTERSECT; return; } } result = DONT_INTERSECT; } protected int computeIntersect(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2) { isProper = false; // first try a fast test to see if the envelopes of the lines intersect if (!Envelope.intersects(p1, p2, q1, q2)) return DONT_INTERSECT; // for each endpoint, compute which side of the other segment it lies // if both endpoints lie on the same side of the other segment, // the segments do not intersect int Pq1 = CGAlgorithms.orientationIndex(p1, p2, q1); int Pq2 = CGAlgorithms.orientationIndex(p1, p2, q2); if ((Pq1 > 0 && Pq2 > 0) || (Pq1 < 0 && Pq2 < 0)) { return DONT_INTERSECT; } int Qp1 = CGAlgorithms.orientationIndex(q1, q2, p1); int Qp2 = CGAlgorithms.orientationIndex(q1, q2, p2); if ((Qp1 > 0 && Qp2 > 0) || (Qp1 < 0 && Qp2 < 0)) { return DONT_INTERSECT; } boolean collinear = Pq1 == 0 && Pq2 == 0 && Qp1 == 0 && Qp2 == 0; if (collinear) { return computeCollinearIntersection(p1, p2, q1, q2); } /** * Check if the intersection is an endpoint. If it is, copy the endpoint * as the intersection point. Copying the point rather than computing it * ensures the point has the exact value, which is important for * robustness. It is sufficient to simply check for an endpoint which is * on the other line, since at this point we know that the inputLines * must intersect. */ if (Pq1 == 0 || Pq2 == 0 || Qp1 == 0 || Qp2 == 0) { isProper = false; if (Pq1 == 0) { intPt[0] = new Coordinate(q1); } if (Pq2 == 0) { intPt[0] = new Coordinate(q2); } if (Qp1 == 0) { intPt[0] = new Coordinate(p1); } if (Qp2 == 0) { intPt[0] = new Coordinate(p2); } } else { isProper = true; intPt[0] = intersection(p1, p2, q1, q2); } return DO_INTERSECT; } /* * private boolean intersectsEnvelope(Coordinate p1, Coordinate p2, * Coordinate q) { if (((q.x >= Math.min(p1.x, p2.x)) && (q.x <= * Math.max(p1.x, p2.x))) && ((q.y >= Math.min(p1.y, p2.y)) && (q.y <= * Math.max(p1.y, p2.y)))) { return true; } return false; } */ private int computeCollinearIntersection(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2) { boolean p1q1p2 = Envelope.intersects(p1, p2, q1); boolean p1q2p2 = Envelope.intersects(p1, p2, q2); boolean q1p1q2 = Envelope.intersects(q1, q2, p1); boolean q1p2q2 = Envelope.intersects(q1, q2, p2); if (p1q1p2 && p1q2p2) { intPt[0] = q1; intPt[1] = q2; return COLLINEAR; } if (q1p1q2 && q1p2q2) { intPt[0] = p1; intPt[1] = p2; return COLLINEAR; } if (p1q1p2 && q1p1q2) { intPt[0] = q1; intPt[1] = p1; return q1.equals(p1) && !p1q2p2 && !q1p2q2 ? DO_INTERSECT : COLLINEAR; } if (p1q1p2 && q1p2q2) { intPt[0] = q1; intPt[1] = p2; return q1.equals(p2) && !p1q2p2 && !q1p1q2 ? DO_INTERSECT : COLLINEAR; } if (p1q2p2 && q1p1q2) { intPt[0] = q2; intPt[1] = p1; return q2.equals(p1) && !p1q1p2 && !q1p2q2 ? DO_INTERSECT : COLLINEAR; } if (p1q2p2 && q1p2q2) { intPt[0] = q2; intPt[1] = p2; return q2.equals(p2) && !p1q1p2 && !q1p1q2 ? DO_INTERSECT : COLLINEAR; } return DONT_INTERSECT; } /** * This method computes the actual value of the intersection point. To * obtain the maximum precision from the intersection calculation, the * coordinates are normalized by subtracting the minimum ordinate values (in * absolute value). This has the effect of removing common significant * digits from the calculation to maintain more bits of precision. */ private Coordinate intersection(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2) { Coordinate n1 = new Coordinate(p1); Coordinate n2 = new Coordinate(p2); Coordinate n3 = new Coordinate(q1); Coordinate n4 = new Coordinate(q2); Coordinate normPt = new Coordinate(); normalizeToEnvCentre(n1, n2, n3, n4, normPt); Coordinate intPt = null; try { intPt = HCoordinate.intersection(n1, n2, n3, n4); } catch (NotRepresentableException e) { Assert .shouldNeverReachHere("Coordinate for intersection is not calculable"); // EnvelopeMidpointIntersector emInt = new // EnvelopeMidpointIntersector(n1, n2, n3, n4); // intPt = emInt.getIntersection(); } intPt.x += normPt.x; intPt.y += normPt.y; /** * * MD - May 4 2005 - This is still a problem. Here is a failure case: * * LINESTRING (2089426.5233462777 1180182.3877339689, 2085646.6891757075 * 1195618.7333999649) LINESTRING (1889281.8148903656 * 1997547.0560044837, 2259977.3672235999 483675.17050843034) int point = * (2097408.2633752143,1144595.8008114607) */ if (!isInSegmentEnvelopes(intPt)) { System.out.println("Intersection outside segment envelopes: " + intPt); } /* * // disabled until a better solution is found if (! * isInSegmentEnvelopes(intPt)) { System.out.println("first value * outside segment envelopes: " + intPt); * * IteratedBisectionIntersector ibi = new * IteratedBisectionIntersector(p1, p2, q1, q2); intPt = * ibi.getIntersection(); } if (! isInSegmentEnvelopes(intPt)) { * System.out.println("ERROR - outside segment envelopes: " + intPt); * * IteratedBisectionIntersector ibi = new * IteratedBisectionIntersector(p1, p2, q1, q2); Coordinate testPt = * ibi.getIntersection(); } */ if (precisionModel != null) { precisionModel.makePrecise(intPt); } return intPt; } /** * Normalize the supplied coordinates so that their minimum ordinate values * lie at the origin. NOTE: this normalization technique appears to cause * large errors in the position of the intersection point for some cases. * * @param n1 * @param n2 * @param n3 * @param n4 * @param normPt */ private void normalizeToMinimum(Coordinate n1, Coordinate n2, Coordinate n3, Coordinate n4, Coordinate normPt) { normPt.x = smallestInAbsValue(n1.x, n2.x, n3.x, n4.x); normPt.y = smallestInAbsValue(n1.y, n2.y, n3.y, n4.y); n1.x -= normPt.x; n1.y -= normPt.y; n2.x -= normPt.x; n2.y -= normPt.y; n3.x -= normPt.x; n3.y -= normPt.y; n4.x -= normPt.x; n4.y -= normPt.y; } /** * Normalize the supplied coordinates to so that the midpoint of their * intersection envelope lies at the origin. * * @param n00 * @param n01 * @param n10 * @param n11 * @param normPt */ private void normalizeToEnvCentre(Coordinate n00, Coordinate n01, Coordinate n10, Coordinate n11, Coordinate normPt) { double minX0 = n00.x < n01.x ? n00.x : n01.x; double minY0 = n00.y < n01.y ? n00.y : n01.y; double maxX0 = n00.x > n01.x ? n00.x : n01.x; double maxY0 = n00.y > n01.y ? n00.y : n01.y; double minX1 = n10.x < n11.x ? n10.x : n11.x; double minY1 = n10.y < n11.y ? n10.y : n11.y; double maxX1 = n10.x > n11.x ? n10.x : n11.x; double maxY1 = n10.y > n11.y ? n10.y : n11.y; double intMinX = minX0 > minX1 ? minX0 : minX1; double intMaxX = maxX0 < maxX1 ? maxX0 : maxX1; double intMinY = minY0 > minY1 ? minY0 : minY1; double intMaxY = maxY0 < maxY1 ? maxY0 : maxY1; double intMidX = (intMinX + intMaxX) / 2.0; double intMidY = (intMinY + intMaxY) / 2.0; normPt.x = intMidX; normPt.y = intMidY; /* * // equilavalent code using more modular but slower method Envelope * env0 = new Envelope(n00, n01); Envelope env1 = new Envelope(n10, * n11); Envelope intEnv = env0.intersection(env1); Coordinate intMidPt = * intEnv.centre(); * * normPt.x = intMidPt.x; normPt.y = intMidPt.y; */ n00.x -= normPt.x; n00.y -= normPt.y; n01.x -= normPt.x; n01.y -= normPt.y; n10.x -= normPt.x; n10.y -= normPt.y; n11.x -= normPt.x; n11.y -= normPt.y; } private double smallestInAbsValue(double x1, double x2, double x3, double x4) { double x = x1; double xabs = Math.abs(x); if (Math.abs(x2) < xabs) { x = x2; xabs = Math.abs(x2); } if (Math.abs(x3) < xabs) { x = x3; xabs = Math.abs(x3); } if (Math.abs(x4) < xabs) { x = x4; } return x; } /** * Test whether a point lies in the envelopes of both input segments. A * correctly computed intersection point should return <code>true</code> * for this test. Since this test is for debugging purposes only, no attempt * is made to optimize the envelope test. * * @return <code>true</code> if the input point lies within both input * segment envelopes */ private boolean isInSegmentEnvelopes(Coordinate intPt) { Envelope env0 = new Envelope(inputLines[0][0], inputLines[0][1]); Envelope env1 = new Envelope(inputLines[1][0], inputLines[1][1]); return env0.contains(intPt) && env1.contains(intPt); } }