blob: f5879351aad84ab5961b862a71d40ac282108964 [file] [log] [blame]
/*
* 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() {
}
}