/** * Return a latitude-longitude rectangle that contains the edge from "a" to * "b". Both points must be unit-length. Note that the bounding rectangle of * an edge can be larger than the bounding rectangle of its endpoints. */ public static S2LatLngRect FromEdge(S2Point a, S2Point b) { // assert (S2.isUnitLength(a) && S2.isUnitLength(b)); var r = FromPointPair(new S2LatLng(a), new S2LatLng(b)); // 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. var ab = S2.RobustCrossProd(a, b); var dir = S2Point.CrossProd(ab, new S2Point(0, 0, 1)); var da = dir.DotProd(a); var db = dir.DotProd(b); if (da * db >= 0) { // Minimum and maximum latitude are attained at the vertices. return(r); } // Minimum/maximum latitude occurs in the edge interior. This affects the // latitude bounds but not the longitude bounds. var absLat = Math.Acos(Math.Abs(ab.Z / ab.Norm)); if (da < 0) { return(new S2LatLngRect(new R1Interval(r.Lat.Lo, absLat), r.Lng)); } else { return(new S2LatLngRect(new R1Interval(-absLat, r.Lat.Hi), r.Lng)); } }
/* * 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. var aNorm = S2Point.Normalize(S2.RobustCrossProd(a0, a1)); var bNorm = S2Point.Normalize(S2.RobustCrossProd(b0, b1)); var 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(a0 + a1 + b0 + b1) < 0) { x = -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. var 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.Vmin); }
public void AddPoint(S2Point b) { // assert (S2.isUnitLength(b)); var 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. var aCrossB = S2.RobustCrossProd(a, b); var dir = S2Point.CrossProd(aCrossB, new S2Point(0, 0, 1)); var da = dir.DotProd(a); var 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. var absLat = Math.Acos(Math.Abs(aCrossB[2] / aCrossB.Norm)); var 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; }
/** * 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)); var crossProd = S2.RobustCrossProd(a, b); // Find the closest point to X along the great circle through AB. var p = x - (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((x - a).Norm2 <= (x - b).Norm2 ? a : b); }
/** * 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))); }
/** * Return true if the edge AB intersects the given edge of constant latitude. */ private static bool IntersectsLatEdge(S2Point a, S2Point b, double lat, S1Interval lng) { // Return true if the segment AB intersects the given edge of constant // latitude. Unfortunately, lines of constant latitude are curves on // the sphere. They can intersect a straight edge in 0, 1, or 2 points. // assert (S2.isUnitLength(a) && S2.isUnitLength(b)); // First, compute the normal to the plane AB that points vaguely north. var z = S2Point.Normalize(S2.RobustCrossProd(a, b)); if (z.Z < 0) { z = -z; } // Extend this to an orthonormal frame (x,y,z) where x is the direction // where the great circle through AB achieves its maximium latitude. var y = S2Point.Normalize(S2.RobustCrossProd(z, new S2Point(0, 0, 1))); var x = S2Point.CrossProd(y, z); // assert (S2.isUnitLength(x) && x.z >= 0); // Compute the angle "theta" from the x-axis (in the x-y plane defined // above) where the great circle intersects the given line of latitude. var sinLat = Math.Sin(lat); if (Math.Abs(sinLat) >= x.Z) { return(false); // The great circle does not reach the given latitude. } // assert (x.z > 0); var cosTheta = sinLat / x.Z; var sinTheta = Math.Sqrt(1 - cosTheta * cosTheta); var theta = Math.Atan2(sinTheta, cosTheta); // The candidate intersection points are located +/- theta in the x-y // plane. For an intersection to be valid, we need to check that the // intersection point is contained in the interior of the edge AB and // also that it is contained within the given longitude interval "lng". // Compute the range of theta values spanned by the edge AB. var abTheta = S1Interval.FromPointPair(Math.Atan2( a.DotProd(y), a.DotProd(x)), Math.Atan2(b.DotProd(y), b.DotProd(x))); if (abTheta.Contains(theta)) { // Check if the intersection point is also in the given "lng" interval. var isect = (x * cosTheta) + (y * sinTheta); if (lng.Contains(Math.Atan2(isect.Y, isect.X))) { return(true); } } if (abTheta.Contains(-theta)) { // Check if the intersection point is also in the given "lng" interval. var intersection = (x * cosTheta) - (y * sinTheta); if (lng.Contains(Math.Atan2(intersection.Y, intersection.X))) { return(true); } } return(false); }