public string ToDegreesString() { var s2LatLng = new S2LatLng(this); return("(" + s2LatLng.LatDegrees + ", " + s2LatLng.LngDegrees + ")"); }
/** * Return the center of the rectangle in latitude-longitude space (in general * this is not the center of the region on the sphere). */ /** * Return the minimum distance (measured along the surface of the sphere) * from a given point to the rectangle (both its boundary and its interior). * The latLng must be valid. */ public S1Angle GetDistance(S2LatLng p) { // The algorithm here is the same as in getDistance(S2LagLngRect), only // with simplified calculations. var a = this; Preconditions.CheckState(!a.IsEmpty); Preconditions.CheckArgument(p.IsValid); if (a.Lng.Contains(p.Lng.Radians)) { return(S1Angle.FromRadians(Math.Max(0.0, Math.Max(p.Lat.Radians - a.Lat.Hi, a.Lat.Lo - p.Lat.Radians)))); } var interval = new S1Interval(a.Lng.Hi, a.Lng.Complement.Center); var aLng = a.Lng.Lo; if (interval.Contains(p.Lng.Radians)) { aLng = a.Lng.Hi; } var lo = S2LatLng.FromRadians(a.Lat.Lo, aLng).ToPoint(); var hi = S2LatLng.FromRadians(a.Lat.Hi, aLng).ToPoint(); var loCrossHi = S2LatLng.FromRadians(0, aLng - S2.PiOver2).Normalized.ToPoint(); return(S2EdgeUtil.GetDistance(p.ToPoint(), lo, hi, loCrossHi)); }
/** * Returns true if the edge (v0, v1) intersects the given longitude * interval, and then saves 'v1' to be used as the next 'v0'. */ public bool Intersects(S2Point v1) { var lng1 = S2LatLng.Longitude(v1).Radians; var result = interval.Intersects(S1Interval.FromPointPair(lng0, lng1)); lng0 = lng1; return(result); }
// Increase the size of the bounding rectangle to include the given point. // The rectangle is expanded by the minimum amount possible. public S2LatLngRect AddPoint(S2LatLng ll) { // assert (ll.isValid()); var newLat = _lat.AddPoint(ll.Lat.Radians); var newLng = _lng.AddPoint(ll.Lng.Radians); return(new S2LatLngRect(newLat, newLng)); }
/** * Return true if the edge AB intersects the given edge of constant longitude. */ private static bool IntersectsLngEdge(S2Point a, S2Point b, R1Interval lat, double lng) { // Return true if the segment AB intersects the given edge of constant // longitude. The nice thing about edges of constant longitude is that // they are straight lines on the sphere (geodesics). return(S2.SimpleCrossing(a, b, S2LatLng.FromRadians(lat.Lo, lng) .ToPoint(), S2LatLng.FromRadians(lat.Hi, lng).ToPoint())); }
/** * Return a rectangle that contains all points whose latitude distance from * this rectangle is at most margin.Lat, and whose longitude distance from * this rectangle is at most margin.Lng. In particular, latitudes are * clamped while longitudes are wrapped. Note that any expansion of an empty * interval remains empty, and both components of the given margin must be * non-negative. * * NOTE: If you are trying to grow a rectangle by a certain *distance* on the * sphere (e.g. 5km), use the ConvolveWithCap() method instead. */ public S2LatLngRect Expanded(S2LatLng margin) { // assert (margin.Lat.radians() >= 0 && margin.Lng.radians() >= 0); if (IsEmpty) { return(this); } return(new S2LatLngRect(_lat.Expanded(margin.Lat.Radians).Intersection( FullLat), _lng.Expanded(margin.Lng.Radians))); }
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; }
/** * Return true if the rectangle is valid, which essentially just means that * the latitude bounds do not exceed Pi/2 in absolute value and the longitude * bounds do not exceed Pi in absolute value. * */ /** Return the k-th vertex of the rectangle (k = 0,1,2,3) in CCW order. */ public S2LatLng GetVertex(int k) { // Return the points in CCW order (SW, SE, NE, NW). switch (k) { case 0: return(S2LatLng.FromRadians(_lat.Lo, _lng.Lo)); case 1: return(S2LatLng.FromRadians(_lat.Lo, _lng.Hi)); case 2: return(S2LatLng.FromRadians(_lat.Hi, _lng.Hi)); case 3: return(S2LatLng.FromRadians(_lat.Hi, _lng.Lo)); default: throw new ArgumentException("Invalid vertex index."); } }
/** * Return the minimum distance (measured along the surface of the sphere) to * the given S2LatLngRect. Both S2LatLngRects must be non-empty. */ public S1Angle GetDistance(S2LatLngRect other) { var a = this; var b = other; Preconditions.CheckState(!a.IsEmpty); Preconditions.CheckArgument(!b.IsEmpty); // First, handle the trivial cases where the longitude intervals overlap. if (a.Lng.Intersects(b.Lng)) { if (a.Lat.Intersects(b.Lat)) { return(S1Angle.FromRadians(0)); // Intersection between a and b. } // We found an overlap in the longitude interval, but not in the latitude // interval. This means the shortest path travels along some line of // longitude connecting the high-latitude of the lower rect with the // low-latitude of the higher rect. S1Angle lo, hi; if (a.Lat.Lo > b.Lat.Hi) { lo = b.LatHi; hi = a.LatLo; } else { lo = a.LatHi; hi = b.LatLo; } return(S1Angle.FromRadians(hi.Radians - lo.Radians)); } // The longitude intervals don't overlap. In this case, the closest points // occur somewhere on the pair of longitudinal edges which are nearest in // longitude-space. S1Angle aLng, bLng; var loHi = S1Interval.FromPointPair(a.Lng.Lo, b.Lng.Hi); var hiLo = S1Interval.FromPointPair(a.Lng.Hi, b.Lng.Lo); if (loHi.Length < hiLo.Length) { aLng = a.LngLo; bLng = b.LngHi; } else { aLng = a.LngHi; bLng = b.LngLo; } // The shortest distance between the two longitudinal segments will include // at least one segment endpoint. We could probably narrow this down further // to a single point-edge distance by comparing the relative latitudes of the // endpoints, but for the sake of clarity, we'll do all four point-edge // distance tests. var aLo = new S2LatLng(a.LatLo, aLng).ToPoint(); var aHi = new S2LatLng(a.LatHi, aLng).ToPoint(); var aLoCrossHi = S2LatLng.FromRadians(0, aLng.Radians - S2.PiOver2).Normalized.ToPoint(); var bLo = new S2LatLng(b.LatLo, bLng).ToPoint(); var bHi = new S2LatLng(b.LatHi, bLng).ToPoint(); var bLoCrossHi = S2LatLng.FromRadians(0, bLng.Radians - S2.PiOver2).Normalized.ToPoint(); return(S1Angle.Min(S2EdgeUtil.GetDistance(aLo, bLo, bHi, bLoCrossHi), S1Angle.Min(S2EdgeUtil.GetDistance(aHi, bLo, bHi, bLoCrossHi), S1Angle.Min(S2EdgeUtil.GetDistance(bLo, aLo, aHi, aLoCrossHi), S2EdgeUtil.GetDistance(bHi, aLo, aHi, aLoCrossHi))))); }
/** *'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; lng0 = S2LatLng.Longitude(v0).Radians; }
/** * Returns true if this rectangle intersects the given cell. (This is an exact * test and may be fairly expensive, see also MayIntersect below.) */ public bool Intersects(S2Cell cell) { // First we eliminate the cases where one region completely contains the // other. Once these are disposed of, then the regions will intersect // if and only if their boundaries intersect. if (IsEmpty) { return(false); } if (Contains(cell.Center)) { return(true); } if (cell.Contains(Center.ToPoint())) { return(true); } // Quick rejection test (not required for correctness). if (!Intersects(cell.RectBound)) { return(false); } // Now check whether the boundaries intersect. Unfortunately, a // latitude-longitude rectangle does not have straight edges -- two edges // are curved, and at least one of them is concave. // Precompute the cell vertices as points and latitude-longitudes. var cellV = new S2Point[4]; var cellLl = new S2LatLng[4]; for (var i = 0; i < 4; ++i) { cellV[i] = cell.GetVertex(i); // Must be normalized. cellLl[i] = new S2LatLng(cellV[i]); if (Contains(cellLl[i])) { return(true); // Quick acceptance test. } } for (var i = 0; i < 4; ++i) { var edgeLng = S1Interval.FromPointPair( cellLl[i].Lng.Radians, cellLl[(i + 1) & 3].Lng.Radians); if (!_lng.Intersects(edgeLng)) { continue; } var a = cellV[i]; var b = cellV[(i + 1) & 3]; if (edgeLng.Contains(_lng.Lo)) { if (IntersectsLngEdge(a, b, _lat, _lng.Lo)) { return(true); } } if (edgeLng.Contains(_lng.Hi)) { if (IntersectsLngEdge(a, b, _lat, _lng.Hi)) { return(true); } } if (IntersectsLatEdge(a, b, _lat.Lo, _lng)) { return(true); } if (IntersectsLatEdge(a, b, _lat.Hi, _lng)) { return(true); } } return(false); }
/** * More efficient version of InteriorContains() that accepts a S2LatLng rather * than an S2Point. */ public bool InteriorContains(S2LatLng ll) { // assert (ll.isValid()); return(_lat.InteriorContains(ll.Lat.Radians) && _lng .InteriorContains(ll.Lng.Radians)); }
public S2Cell(S2LatLng ll) { Init(S2CellId.FromLatLng(ll)); }
/** * Construct a rectangle from minimum and maximum latitudes and longitudes. If * lo.Lng > hi.Lng, the rectangle spans the 180 degree longitude line. */ public S2LatLngRect(S2LatLng lo, S2LatLng hi) { _lat = new R1Interval(lo.Lat.Radians, hi.Lat.Radians); _lng = new S1Interval(lo.Lng.Radians, hi.Lng.Radians); // assert (isValid()); }
/** * Convenience method to construct the minimal bounding rectangle containing * the two given points. This is equivalent to starting with an empty * rectangle and calling AddPoint() twice. Note that it is different than the * S2LatLngRect(lo, hi) constructor, where the first point is always used as * the lower-left corner of the resulting rectangle. */ public static S2LatLngRect FromPointPair(S2LatLng p1, S2LatLng p2) { // assert (p1.isValid() && p2.isValid()); return(new S2LatLngRect(R1Interval.FromPointPair(p1.Lat.Radians, p2.Lat.Radians), S1Interval.FromPointPair(p1.Lng.Radians, p2.Lng.Radians))); }
/** Convenience method to construct a rectangle containing a single point. */ public static S2LatLngRect FromPoint(S2LatLng p) { // assert (p.isValid()); return(new S2LatLngRect(p, p)); }
/** The canonical empty rectangle */ /** * Construct a rectangle from a center point (in lat-lng space) and size in * each dimension. If size.Lng is greater than 360 degrees it is clamped, * and latitudes greater than +/- 90 degrees are also clamped. So for example, * FromCenterSize((80,170),(20,20)) -> (lo=(60,150),hi=(90,-170)). */ public static S2LatLngRect FromCenterSize(S2LatLng center, S2LatLng size) { return(FromPoint(center).Expanded(size * 0.5)); }
/** Return the leaf cell containing the given S2LatLng. */ public static S2CellId FromLatLng(S2LatLng ll) { return(FromPoint(ll.ToPoint())); }