/* * Copyright 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.geometry; import com.google.common.base.Preconditions; /** * This class contains various utility functions related to edges. It collects * together common code that is needed to implement polygonal geometry such as * polylines, loops, and general polygons. * */ public strictfp class S2EdgeUtil { /** * IEEE floating-point operations have a maximum error of 0.5 ULPS (units in * the last place). For double-precision numbers, this works out to 2**-53 * (about 1.11e-16) times the magnitude of the result. It is possible to * analyze the calculation done by getIntersection() and work out the * worst-case rounding error. I have done a rough version of this, and my * estimate is that the worst case distance from the intersection point X to * the great circle through (a0, a1) is about 12 ULPS, or about 1.3e-15. This * needs to be increased by a factor of (1/0.866) to account for the * edgeSpliceFraction() in S2PolygonBuilder. Note that the maximum error * measured by the unittest in 1,000,000 trials is less than 3e-16. */ public static final S1Angle DEFAULT_INTERSECTION_TOLERANCE = S1Angle.radians(1.5e-15); /** * This class allows a vertex chain v0, v1, v2, ... to be efficiently tested * for intersection with a given fixed edge AB. */ public static class EdgeCrosser { // The fields below are all constant. private final S2Point a; private final S2Point b; private final S2Point aCrossB; // The fields below are updated for each vertex in the chain. // Previous vertex in the vertex chain. private S2Point c; // The orientation of the triangle ACB. private int acb; /** * AB is the given fixed edge, and C is the first vertex of the vertex * chain. All parameters must point to fixed storage that persists for the * lifetime of the EdgeCrosser object. */ public EdgeCrosser(S2Point a, S2Point b, S2Point c) { this.a = a; this.b = b; this.aCrossB = S2Point.crossProd(a, b); restartAt(c); } /** * Call this function when your chain 'jumps' to a new place. */ public void restartAt(S2Point c) { this.c = c; acb = -S2.robustCCW(a, b, c, aCrossB); } /** * This method is equivalent to calling the S2EdgeUtil.robustCrossing() * function (defined below) on the edges AB and CD. It returns +1 if there * is a crossing, -1 if there is no crossing, and 0 if two points from * different edges are the same. Returns 0 or -1 if either edge is * degenerate. As a side effect, it saves vertex D to be used as the next * vertex C. */ public int robustCrossing(S2Point d) { // For there to be an edge crossing, the triangles ACB, CBD, BDA, DAC must // all be oriented the same way (CW or CCW). We keep the orientation // of ACB as part of our state. When each new point D arrives, we // compute the orientation of BDA and check whether it matches ACB. // This checks whether the points C and D are on opposite sides of the // great circle through AB. // Recall that robustCCW is invariant with respect to rotating its // arguments, i.e. ABC has the same orientation as BDA. int bda = S2.robustCCW(a, b, d, aCrossB); int result; if (bda == -acb && bda != 0) { // Most common case -- triangles have opposite orientations. result = -1; } else if ((bda & acb) == 0) { // At least one value is zero -- two vertices are identical. result = 0; } else { // assert (bda == acb && bda != 0); result = robustCrossingInternal(d); // Slow path. } // Now save the current vertex D as the next vertex C, and also save the // orientation of the new triangle ACB (which is opposite to the current // triangle BDA). c = d; acb = -bda; return result; } /** * This method is equivalent to the S2EdgeUtil.edgeOrVertexCrossing() method * defined below. It is similar to robustCrossing, but handles cases where * two vertices are identical in a way that makes it easy to implement * point-in-polygon containment tests. */ public boolean edgeOrVertexCrossing(S2Point d) { // We need to copy c since it is clobbered by robustCrossing(). S2Point c2 = new S2Point(c.get(0), c.get(1), c.get(2)); int crossing = robustCrossing(d); if (crossing < 0) { return false; } if (crossing > 0) { return true; } return vertexCrossing(a, b, c2, d); } /** * This function handles the "slow path" of robustCrossing(). */ private int robustCrossingInternal(S2Point d) { // ACB and BDA have the appropriate orientations, so now we check the // triangles CBD and DAC. S2Point cCrossD = S2Point.crossProd(c, d); int cbd = -S2.robustCCW(c, d, b, cCrossD); if (cbd != acb) { return -1; } int dac = S2.robustCCW(c, d, a, cCrossD); return (dac == acb) ? 1 : -1; } } /** * This class computes a bounding rectangle that contains all edges defined by * a vertex chain v0, v1, v2, ... All vertices must be unit length. Note that * the bounding rectangle of an edge can be larger than the bounding rectangle * of its endpoints, e.g. consider an edge that passes through the north pole. */ public static class RectBounder { // The previous vertex in the chain. private S2Point a; // The corresponding latitude-longitude. private S2LatLng aLatLng; // The current bounding rectangle. private S2LatLngRect bound; public RectBounder() { this.bound = S2LatLngRect.empty(); } /** * This method is called to add each vertex to the chain. 'b' must point to * fixed storage that persists for the lifetime of the RectBounder. */ public void addPoint(S2Point b) { // assert (S2.isUnitLength(b)); S2LatLng bLatLng = new S2LatLng(b); if (bound.isEmpty()) { bound = bound.addPoint(bLatLng); } else { // We can't just call bound.addPoint(bLatLng) here, since we need to // ensure that all the longitudes between "a" and "b" are included. bound = bound.union(S2LatLngRect.fromPointPair(aLatLng, bLatLng)); // Check whether the min/max latitude occurs in the edge interior. // We find the normal to the plane containing AB, and then a vector // "dir" in this plane that also passes through the equator. We use // RobustCrossProd to ensure that the edge normal is accurate even // when the two points are very close together. S2Point aCrossB = S2.robustCrossProd(a, b); S2Point dir = S2Point.crossProd(aCrossB, new S2Point(0, 0, 1)); double da = dir.dotProd(a); double db = dir.dotProd(b); if (da * db < 0) { // Minimum/maximum latitude occurs in the edge interior. This affects // the latitude bounds but not the longitude bounds. double absLat = Math.acos(Math.abs(aCrossB.get(2) / aCrossB.norm())); R1Interval lat = bound.lat(); if (da < 0) { // It's possible that absLat < lat.lo() due to numerical errors. lat = new R1Interval(lat.lo(), Math.max(absLat, bound.lat().hi())); } else { lat = new R1Interval(Math.min(-absLat, bound.lat().lo()), lat.hi()); } bound = new S2LatLngRect(lat, bound.lng()); } } a = b; aLatLng = bLatLng; } /** * Return the bounding rectangle of the edge chain that connects the * vertices defined so far. */ public S2LatLngRect getBound() { return bound; } } /** * The purpose of this class is to find edges that intersect a given XYZ * bounding box. It can be used as an efficient rejection test when attempting to * find edges that intersect a given region. It accepts a vertex chain v0, v1, * v2, ... and returns a boolean value indicating whether each edge intersects * the specified bounding box. * * We use XYZ intervals instead of something like longitude intervals because * it is cheap to collect from S2Point lists and any slicing strategy should * give essentially equivalent results. See S2Loop for an example of use. */ public static class XYZPruner { private S2Point lastVertex; // The region to be tested against. private boolean boundSet; private double xmin; private double ymin; private double zmin; private double xmax; private double ymax; private double zmax; private double maxDeformation; public XYZPruner() { boundSet = false; } /** * Accumulate a bounding rectangle from provided edges. * * @param from start of edge * @param to end of edge. */ public void addEdgeToBounds(S2Point from, S2Point to) { if (!boundSet) { boundSet = true; xmin = xmax = from.x; ymin = ymax = from.y; zmin = zmax = from.z; } xmin = Math.min(xmin, Math.min(to.x, from.x)); ymin = Math.min(ymin, Math.min(to.y, from.y)); zmin = Math.min(zmin, Math.min(to.z, from.z)); xmax = Math.max(xmax, Math.max(to.x, from.x)); ymax = Math.max(ymax, Math.max(to.y, from.y)); zmax = Math.max(zmax, Math.max(to.z, from.z)); // Because our arcs are really geodesics on the surface of the earth // an edge can have intermediate points outside the xyz bounds implicit // in the end points. Based on the length of the arc we compute a // generous bound for the maximum amount of deformation. For small edges // it will be very small but for some large arcs (ie. from (1N,90W) to // (1N,90E) the path can be wildly deformed. I did a bunch of // experiments with geodesics to get safe bounds for the deformation. double approxArcLen = Math.abs(from.x - to.x) + Math.abs(from.y - to.y) + Math.abs(from.z - to.z); if (approxArcLen < 0.025) { // less than 2 degrees maxDeformation = Math.max(maxDeformation, approxArcLen * 0.0025); } else if (approxArcLen < 1.0) { // less than 90 degrees maxDeformation = Math.max(maxDeformation, approxArcLen * 0.11); } else { maxDeformation = approxArcLen * 0.5; } } public void setFirstIntersectPoint(S2Point v0) { xmin = xmin - maxDeformation; ymin = ymin - maxDeformation; zmin = zmin - maxDeformation; xmax = xmax + maxDeformation; ymax = ymax + maxDeformation; zmax = zmax + maxDeformation; this.lastVertex = v0; } /** * Returns true if the edge going from the last point to this point passes * through the pruner bounding box, otherwise returns false. So the * method returns false if we are certain there is no intersection, but it * may return true when there turns out to be no intersection. */ public boolean intersects(S2Point v1) { boolean result = true; if ((v1.x < xmin && lastVertex.x < xmin) || (v1.x > xmax && lastVertex.x > xmax)) { result = false; } else if ((v1.y < ymin && lastVertex.y < ymin) || (v1.y > ymax && lastVertex.y > ymax)) { result = false; } else if ((v1.z < zmin && lastVertex.z < zmin) || (v1.z > zmax && lastVertex.z > zmax)) { result = false; } lastVertex = v1; return result; } } /** * The purpose of this class is to find edges that intersect a given longitude * interval. It can be used as an efficient rejection test when attempting to * find edges that intersect a given region. It accepts a vertex chain v0, v1, * v2, ... and returns a boolean value indicating whether each edge intersects * the specified longitude interval. * * This class is not currently used as the XYZPruner is preferred for * S2Loop, but this should be usable in similar circumstances. Be wary * of the cost of atan2() in conversions from S2Point to longitude! */ public static class LongitudePruner { // The interval to be tested against. private S1Interval interval; // The longitude of the next v0. private double lng0; /** *'interval' is the longitude interval to be tested against, and 'v0' is * the first vertex of edge chain. */ public LongitudePruner(S1Interval interval, S2Point v0) { this.interval = interval; this.lng0 = S2LatLng.longitude(v0).radians(); } /** * Returns true if the edge (v0, v1) intersects the given longitude * interval, and then saves 'v1' to be used as the next 'v0'. */ public boolean intersects(S2Point v1) { double lng1 = S2LatLng.longitude(v1).radians(); boolean result = interval.intersects(S1Interval.fromPointPair(lng0, lng1)); lng0 = lng1; return result; } } /** * A wedge relation's test method accepts two edge chains A=(a0,a1,a2) and * B=(b0,b1,b2) where a1==b1, and returns either -1, 0, or 1 to indicate the * relationship between the region to the left of A and the region to the left * of B. Wedge relations are used to determine the local relationship between * two polygons that share a common vertex. * * All wedge relations require that a0 != a2 and b0 != b2. Other degenerate * cases (such as a0 == b2) are handled as expected. The parameter "ab1" * denotes the common vertex a1 == b1. */ public interface WedgeRelation { int test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2); } public static class WedgeContains implements WedgeRelation { /** * Given two edge chains (see WedgeRelation above), this function returns +1 * if the region to the left of A contains the region to the left of B, and * 0 otherwise. */ @Override public int test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2) { // For A to contain B (where each loop interior is defined to be its left // side), the CCW edge order around ab1 must be a2 b2 b0 a0. We split // this test into two parts that test three vertices each. return S2.orderedCCW(a2, b2, b0, ab1) && S2.orderedCCW(b0, a0, a2, ab1) ? 1 : 0; } } public static class WedgeIntersects implements WedgeRelation { /** * Given two edge chains (see WedgeRelation above), this function returns -1 * if the region to the left of A intersects the region to the left of B, * and 0 otherwise. Note that regions are defined such that points along a * boundary are contained by one side or the other, not both. So for * example, if A,B,C are distinct points ordered CCW around a vertex O, then * the wedges BOA, AOC, and COB do not intersect. */ @Override public int test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2) { // For A not to intersect B (where each loop interior is defined to be // its left side), the CCW edge order around ab1 must be a0 b2 b0 a2. // Note that it's important to write these conditions as negatives // (!OrderedCCW(a,b,c,o) rather than Ordered(c,b,a,o)) to get correct // results when two vertices are the same. return (S2.orderedCCW(a0, b2, b0, ab1) && S2.orderedCCW(b0, a2, a0, ab1) ? 0 : -1); } } public static class WedgeContainsOrIntersects implements WedgeRelation { /** * Given two edge chains (see WedgeRelation above), this function returns +1 * if A contains B, 0 if A and B are disjoint, and -1 if A intersects but * does not contain B. */ @Override public int test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2) { // This is similar to WedgeContainsOrCrosses, except that we want to // distinguish cases (1) [A contains B], (3) [A and B are disjoint], // and (2,4,5,6) [A intersects but does not contain B]. if (S2.orderedCCW(a0, a2, b2, ab1)) { // We are in case 1, 5, or 6, or case 2 if a2 == b2. return S2.orderedCCW(b2, b0, a0, ab1) ? 1 : -1; // Case 1 vs. 2,5,6. } // We are in cases 2, 3, or 4. if (!S2.orderedCCW(a2, b0, b2, ab1)) { return 0; // Case 3. } // We are in case 2 or 4, or case 3 if a2 == b0. return (a2.equals(b0)) ? 0 : -1; // Case 3 vs. 2,4. } } public static class WedgeContainsOrCrosses implements WedgeRelation { /** * Given two edge chains (see WedgeRelation above), this function returns +1 * if A contains B, 0 if B contains A or the two wedges do not intersect, * and -1 if the edge chains A and B cross each other (i.e. if A intersects * both the interior and exterior of the region to the left of B). In * degenerate cases where more than one of these conditions is satisfied, * the maximum possible result is returned. For example, if A == B then the * result is +1. */ @Override public int test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2) { // There are 6 possible edge orderings at a shared vertex (all // of these orderings are circular, i.e. abcd == bcda): // // (1) a2 b2 b0 a0: A contains B // (2) a2 a0 b0 b2: B contains A // (3) a2 a0 b2 b0: A and B are disjoint // (4) a2 b0 a0 b2: A and B intersect in one wedge // (5) a2 b2 a0 b0: A and B intersect in one wedge // (6) a2 b0 b2 a0: A and B intersect in two wedges // // In cases (4-6), the boundaries of A and B cross (i.e. the boundary // of A intersects the interior and exterior of B and vice versa). // Thus we want to distinguish cases (1), (2-3), and (4-6). // // Note that the vertices may satisfy more than one of the edge // orderings above if two or more vertices are the same. The tests // below are written so that we take the most favorable // interpretation, i.e. preferring (1) over (2-3) over (4-6). In // particular note that if orderedCCW(a,b,c,o) returns true, it may be // possible that orderedCCW(c,b,a,o) is also true (if a == b or b == c). if (S2.orderedCCW(a0, a2, b2, ab1)) { // The cases with this vertex ordering are 1, 5, and 6, // although case 2 is also possible if a2 == b2. if (S2.orderedCCW(b2, b0, a0, ab1)) { return 1; // Case 1 (A contains B) } // We are in case 5 or 6, or case 2 if a2 == b2. return (a2.equals(b2)) ? 0 : -1; // Case 2 vs. 5,6. } // We are in case 2, 3, or 4. return S2.orderedCCW(a0, b0, a2, ab1) ? 0 : -1; // Case 2,3 vs. 4. } } /** * Return true if edge AB crosses CD at a point that is interior to both * edges. Properties: * * (1) simpleCrossing(b,a,c,d) == simpleCrossing(a,b,c,d) (2) * simpleCrossing(c,d,a,b) == simpleCrossing(a,b,c,d) */ public static boolean simpleCrossing(S2Point a, S2Point b, S2Point c, S2Point d) { // We compute simpleCCW() for triangles ACB, CBD, BDA, and DAC. All // of these triangles need to have the same orientation (CW or CCW) // for an intersection to exist. Note that this is slightly more // restrictive than the corresponding definition for planar edges, // since we need to exclude pairs of line segments that would // otherwise "intersect" by crossing two antipodal points. S2Point ab = S2Point.crossProd(a, b); double acb = -(ab.dotProd(c)); double bda = ab.dotProd(d); if (acb * bda <= 0) { return false; } S2Point cd = S2Point.crossProd(c, d); double cbd = -(cd.dotProd(b)); double dac = cd.dotProd(a); return (acb * cbd > 0) && (acb * dac > 0); } /** * Like SimpleCrossing, except that points that lie exactly on a line are * arbitrarily classified as being on one side or the other (according to the * rules of S2.robustCCW). It returns +1 if there is a crossing, -1 if there * is no crossing, and 0 if any two vertices from different edges are the * same. Returns 0 or -1 if either edge is degenerate. Properties of * robustCrossing: * * (1) robustCrossing(b,a,c,d) == robustCrossing(a,b,c,d) (2) * robustCrossing(c,d,a,b) == robustCrossing(a,b,c,d) (3) * robustCrossing(a,b,c,d) == 0 if a==c, a==d, b==c, b==d (3) * robustCrossing(a,b,c,d) <= 0 if a==b or c==d * * Note that if you want to check an edge against a *chain* of other edges, * it is much more efficient to use an EdgeCrosser (above). */ public static int robustCrossing(S2Point a, S2Point b, S2Point c, S2Point d) { // For there to be a crossing, the triangles ACB, CBD, BDA, DAC must // all have the same orientation (clockwise or counterclockwise). // // First we compute the orientation of ACB and BDA. We permute the // arguments to robustCCW so that we can reuse the cross-product of A and B. // Recall that when the arguments to robustCCW are permuted, the sign of the // result changes according to the sign of the permutation. Thus ACB and // ABC are oppositely oriented, while BDA and ABD are the same. S2Point aCrossB = S2Point.crossProd(a, b); int acb = -S2.robustCCW(a, b, c, aCrossB); int bda = S2.robustCCW(a, b, d, aCrossB); // If any two vertices are the same, the result is degenerate. if ((bda & acb) == 0) { return 0; } // If ABC and BDA have opposite orientations (the most common case), // there is no crossing. if (bda != acb) { return -1; } // Otherwise we compute the orientations of CBD and DAC, and check whether // their orientations are compatible with the other two triangles. S2Point cCrossD = S2Point.crossProd(c, d); int cbd = -S2.robustCCW(c, d, b, cCrossD); if (cbd != acb) { return -1; } int dac = S2.robustCCW(c, d, a, cCrossD); return (dac == acb) ? 1 : -1; } /** * Given two edges AB and CD where at least two vertices are identical (i.e. * robustCrossing(a,b,c,d) == 0), this function defines whether the two edges * "cross" in a such a way that point-in-polygon containment tests can be * implemented by counting the number of edge crossings. The basic rule is * that a "crossing" occurs if AB is encountered after CD during a CCW sweep * around the shared vertex starting from a fixed reference point. * * Note that according to this rule, if AB crosses CD then in general CD does * not cross AB. However, this leads to the correct result when counting * polygon edge crossings. For example, suppose that A,B,C are three * consecutive vertices of a CCW polygon. If we now consider the edge * crossings of a segment BP as P sweeps around B, the crossing number changes * parity exactly when BP crosses BA or BC. * * Useful properties of VertexCrossing (VC): * * (1) VC(a,a,c,d) == VC(a,b,c,c) == false (2) VC(a,b,a,b) == VC(a,b,b,a) == * true (3) VC(a,b,c,d) == VC(a,b,d,c) == VC(b,a,c,d) == VC(b,a,d,c) (3) If * exactly one of a,b equals one of c,d, then exactly one of VC(a,b,c,d) and * VC(c,d,a,b) is true * * It is an error to call this method with 4 distinct vertices. */ public static boolean vertexCrossing(S2Point a, S2Point b, S2Point c, S2Point d) { // If A == B or C == D there is no intersection. We need to check this // case first in case 3 or more input points are identical. if (a.equals(b) || c.equals(d)) { return false; } // If any other pair of vertices is equal, there is a crossing if and only // if orderedCCW() indicates that the edge AB is further CCW around the // shared vertex than the edge CD. if (a.equals(d)) { return S2.orderedCCW(S2.ortho(a), c, b, a); } if (b.equals(c)) { return S2.orderedCCW(S2.ortho(b), d, a, b); } if (a.equals(c)) { return S2.orderedCCW(S2.ortho(a), d, b, a); } if (b.equals(d)) { return S2.orderedCCW(S2.ortho(b), c, a, b); } // assert (false); return false; } /** * A convenience function that calls robustCrossing() to handle cases where * all four vertices are distinct, and VertexCrossing() to handle cases where * two or more vertices are the same. This defines a crossing function such * that point-in-polygon containment tests can be implemented by simply * counting edge crossings. */ public static boolean edgeOrVertexCrossing(S2Point a, S2Point b, S2Point c, S2Point d) { int crossing = robustCrossing(a, b, c, d); if (crossing < 0) { return false; } if (crossing > 0) { return true; } return vertexCrossing(a, b, c, d); } static class CloserResult { private double dmin2; private S2Point vmin; public double getDmin2() { return dmin2; } public S2Point getVmin() { return vmin; } public CloserResult(double dmin2, S2Point vmin) { this.dmin2 = dmin2; this.vmin = vmin; } public void replaceIfCloser(S2Point x, S2Point y) { // If the squared distance from x to y is less than dmin2, then replace // vmin by y and update dmin2 accordingly. double d2 = S2Point.minus(x, y).norm2(); if (d2 < dmin2 || (d2 == dmin2 && y.lessThan(vmin))) { dmin2 = d2; vmin = y; } } } /* * Given two edges AB and CD such that robustCrossing() is true, return their * intersection point. Useful properties of getIntersection (GI): * * (1) GI(b,a,c,d) == GI(a,b,d,c) == GI(a,b,c,d) (2) GI(c,d,a,b) == * GI(a,b,c,d) * * The returned intersection point X is guaranteed to be close to the edges AB * and CD, but if the edges intersect at a very small angle then X may not be * close to the true mathematical intersection point P. See the description of * "DEFAULT_INTERSECTION_TOLERANCE" below for details. */ public static S2Point getIntersection(S2Point a0, S2Point a1, S2Point b0, S2Point b1) { Preconditions.checkArgument(robustCrossing(a0, a1, b0, b1) > 0, "Input edges a0a1 and b0b1 muct have a true robustCrossing."); // We use robustCrossProd() to get accurate results even when two endpoints // are close together, or when the two line segments are nearly parallel. S2Point aNorm = S2Point.normalize(S2.robustCrossProd(a0, a1)); S2Point bNorm = S2Point.normalize(S2.robustCrossProd(b0, b1)); S2Point x = S2Point.normalize(S2.robustCrossProd(aNorm, bNorm)); // Make sure the intersection point is on the correct side of the sphere. // Since all vertices are unit length, and edges are less than 180 degrees, // (a0 + a1) and (b0 + b1) both have positive dot product with the // intersection point. We use the sum of all vertices to make sure that the // result is unchanged when the edges are reversed or exchanged. if (x.dotProd(S2Point.add(S2Point.add(a0, a1), S2Point.add(b0, b1))) < 0) { x = S2Point.neg(x); } // The calculation above is sufficient to ensure that "x" is within // DEFAULT_INTERSECTION_TOLERANCE of the great circles through (a0,a1) and // (b0,b1). // However, if these two great circles are very close to parallel, it is // possible that "x" does not lie between the endpoints of the given line // segments. In other words, "x" might be on the great circle through // (a0,a1) but outside the range covered by (a0,a1). In this case we do // additional clipping to ensure that it does. if (S2.orderedCCW(a0, x, a1, aNorm) && S2.orderedCCW(b0, x, b1, bNorm)) { return x; } // Find the acceptable endpoint closest to x and return it. An endpoint is // acceptable if it lies between the endpoints of the other line segment. CloserResult r = new CloserResult(10, x); if (S2.orderedCCW(b0, a0, b1, bNorm)) { r.replaceIfCloser(x, a0); } if (S2.orderedCCW(b0, a1, b1, bNorm)) { r.replaceIfCloser(x, a1); } if (S2.orderedCCW(a0, b0, a1, aNorm)) { r.replaceIfCloser(x, b0); } if (S2.orderedCCW(a0, b1, a1, aNorm)) { r.replaceIfCloser(x, b1); } return r.getVmin(); } /** * Given a point X and an edge AB, return the distance ratio AX / (AX + BX). * If X happens to be on the line segment AB, this is the fraction "t" such * that X == Interpolate(A, B, t). Requires that A and B are distinct. */ public static double getDistanceFraction(S2Point x, S2Point a0, S2Point a1) { Preconditions.checkArgument(!a0.equals(a1)); double d0 = x.angle(a0); double d1 = x.angle(a1); return d0 / (d0 + d1); } /** * Return the minimum distance from X to any point on the edge AB. The result * is very accurate for small distances but may have some numerical error if * the distance is large (approximately Pi/2 or greater). The case A == B is * handled correctly. Note: x, a and b must be of unit length. Throws * IllegalArgumentException if this is not the case. */ public static S1Angle getDistance(S2Point x, S2Point a, S2Point b) { return getDistance(x, a, b, S2.robustCrossProd(a, b)); } /** * A slightly more efficient version of getDistance() where the cross product * of the two endpoints has been precomputed. The cross product does not need * to be normalized, but should be computed using S2.robustCrossProd() for the * most accurate results. */ public static S1Angle getDistance(S2Point x, S2Point a, S2Point b, S2Point aCrossB) { Preconditions.checkArgument(S2.isUnitLength(x)); Preconditions.checkArgument(S2.isUnitLength(a)); Preconditions.checkArgument(S2.isUnitLength(b)); // There are three cases. If X is located in the spherical wedge defined by // A, B, and the axis A x B, then the closest point is on the segment AB. // Otherwise the closest point is either A or B; the dividing line between // these two cases is the great circle passing through (A x B) and the // midpoint of AB. if (S2.simpleCCW(aCrossB, a, x) && S2.simpleCCW(x, b, aCrossB)) { // The closest point to X lies on the segment AB. We compute the distance // to the corresponding great circle. The result is accurate for small // distances but not necessarily for large distances (approaching Pi/2). double sinDist = Math.abs(x.dotProd(aCrossB)) / aCrossB.norm(); return S1Angle.radians(Math.asin(Math.min(1.0, sinDist))); } // Otherwise, the closest point is either A or B. The cheapest method is // just to compute the minimum of the two linear (as opposed to spherical) // distances and convert the result to an angle. Again, this method is // accurate for small but not large distances (approaching Pi). double linearDist2 = Math.min(S2Point.minus(x, a).norm2(), S2Point.minus(x, b).norm2()); return S1Angle.radians(2 * Math.asin(Math.min(1.0, 0.5 * Math.sqrt(linearDist2)))); } /** * Returns the point on edge AB closest to X. x, a and b must be of unit * length. Throws IllegalArgumentException if this is not the case. * */ public static S2Point getClosestPoint(S2Point x, S2Point a, S2Point b) { Preconditions.checkArgument(S2.isUnitLength(x)); Preconditions.checkArgument(S2.isUnitLength(a)); Preconditions.checkArgument(S2.isUnitLength(b)); S2Point crossProd = S2.robustCrossProd(a, b); // Find the closest point to X along the great circle through AB. S2Point p = S2Point.minus(x, S2Point.mul(crossProd, x.dotProd(crossProd) / crossProd.norm2())); // If p is on the edge AB, then it's the closest point. if (S2.simpleCCW(crossProd, a, p) && S2.simpleCCW(p, b, crossProd)) { return S2Point.normalize(p); } // Otherwise, the closest point is either A or B. return S2Point.minus(x, a).norm2() <= S2Point.minus(x, b).norm2() ? a : b; } /** Constructor is private so that this class is never instantiated. */ private S2EdgeUtil() { } }