public void Test_ExpandedByDistance_NegativeDistanceLatResultEmpty() { S2LatLngRect rect = RectFromDegrees(0.0, 0.0, 9.9, 90.0) .ExpandedByDistance(-S1Angle.FromDegrees(5.0)); Assert.True(rect.IsEmpty()); }
public void Test_ExpandedByDistance_NegativeDistanceLngResultEmpty() { S2LatLngRect rect = RectFromDegrees(0.0, 0.0, 30.0, 11.0) .ExpandedByDistance(-S1Angle.FromDegrees(5.0)); // The cap center is at latitude 30 - 5 = 25 degrees. The length of the // latitude 25 degree line is 0.906 times the length of the equator. Thus the // cap whose radius is 5 degrees covers the rectangle whose latitude interval // is 11 degrees. Assert.True(rect.IsEmpty()); }
public void Test_S2LatLngRect_EmptyAndFull() { // Test basic properties of empty and full rectangles. S2LatLngRect empty = S2LatLngRect.Empty; S2LatLngRect full = S2LatLngRect.Full; Assert.True(empty.IsValid()); Assert.True(empty.IsEmpty()); Assert.False(empty.IsPoint()); Assert.True(full.IsValid()); Assert.True(full.IsFull()); Assert.False(full.IsPoint()); // Check that the default S2LatLngRect is identical to Empty(). S2LatLngRect default_empty = S2LatLngRect.Empty; Assert.True(default_empty.IsValid()); Assert.True(default_empty.IsEmpty()); Assert.Equal(empty.Lat, default_empty.Lat); Assert.Equal(empty.Lng, default_empty.Lng); }
private static void TestCellOps(S2LatLngRect r, S2Cell cell, int level) { // Test the relationship between the given rectangle and cell: // 0 == no intersection, 1 == MayIntersect, 2 == Intersects, // 3 == Vertex Containment, 4 == Contains bool vertex_contained = false; for (int i = 0; i < 4; ++i) { if (r.Contains(cell.VertexRaw(i)) || (!r.IsEmpty() && cell.Contains(r.Vertex(i).ToPoint()))) { vertex_contained = true; } } Assert.Equal(r.MayIntersect(cell), level >= 1); Assert.Equal(r.Intersects(cell), level >= 2); Assert.Equal(vertex_contained, level >= 3); Assert.Equal(r.Contains(cell), level >= 4); }
// Expands a bound returned by GetBound() so that it is guaranteed to // contain the bounds of any subregion whose bounds are computed using // this class. For example, consider a loop L that defines a square. // GetBound() ensures that if a point P is contained by this square, then // S2LatLng(P) is contained by the bound. But now consider a diamond // shaped loop S contained by L. It is possible that GetBound() returns a // *larger* bound for S than it does for L, due to rounding errors. This // method expands the bound for L so that it is guaranteed to contain the // bounds of any subregion S. // // More precisely, if L is a loop that does not contain either pole, and S // is a loop such that L.Contains(S), then // // ExpandForSubregions(RectBound(L)).Contains(RectBound(S)). public static S2LatLngRect ExpandForSubregions(S2LatLngRect bound) { // Empty bounds don't need expansion. if (bound.IsEmpty()) { return(bound); } // First we need to check whether the bound B contains any nearly-antipodal // points (to within 4.309 * S2Constants.DoubleEpsilon). If so then we need to return // S2LatLngRect.Full(), since the subregion might have an edge between two // such points, and AddPoint() returns Full() for such edges. Note that // this can happen even if B is not Full(); for example, consider a loop // that defines a 10km strip straddling the equator extending from // longitudes -100 to +100 degrees. // // It is easy to check whether B contains any antipodal points, but checking // for nearly-antipodal points is trickier. Essentially we consider the // original bound B and its reflection through the origin B', and then test // whether the minimum distance between B and B' is less than 4.309 * // S2Constants.DoubleEpsilon. // "lng_gap" is a lower bound on the longitudinal distance between B and its // reflection B'. (2.5 * S2Constants.DoubleEpsilon is the maximum combined error of the // endpoint longitude calculations and the GetLength() call.) double lng_gap = Math.Max(0.0, Math.PI - bound.Lng.GetLength() - 2.5 * S2.DoubleEpsilon); // "min_abs_lat" is the minimum distance from B to the equator (if zero or // negative, then B straddles the equator). double min_abs_lat = Math.Max(bound.Lat.Lo, -bound.Lat.Hi); // "lat_gap1" and "lat_gap2" measure the minimum distance from B to the // south and north poles respectively. double lat_gap1 = S2.M_PI_2 + bound.Lat.Lo; double lat_gap2 = S2.M_PI_2 - bound.Lat.Hi; if (min_abs_lat >= 0) { // The bound B does not straddle the equator. In this case the minimum // distance is between one endpoint of the latitude edge in B closest to // the equator and the other endpoint of that edge in B'. The latitude // distance between these two points is 2*min_abs_lat, and the longitude // distance is lng_gap. We could compute the distance exactly using the // Haversine formula, but then we would need to bound the errors in that // calculation. Since we only need accuracy when the distance is very // small (close to 4.309 * S2Constants.DoubleEpsilon), we substitute the Euclidean // distance instead. This gives us a right triangle XYZ with two edges of // length x = 2*min_abs_lat and y ~= lng_gap. The desired distance is the // length of the third edge "z", and we have // // z ~= Math.Sqrt(x^2 + y^2) >= (x + y) / Math.Sqrt(2) // // Therefore the region may contain nearly antipodal points only if // // 2*min_abs_lat + lng_gap < Math.Sqrt(2) * 4.309 * S2Constants.DoubleEpsilon // ~= 1.354e-15 // // Note that because the given bound B is conservative, "min_abs_lat" and // "lng_gap" are both lower bounds on their true values so we do not need // to make any adjustments for their errors. if (2 * min_abs_lat + lng_gap < 1.354e-15) { return(S2LatLngRect.Full); } } else if (lng_gap >= S2.M_PI_2) { // B spans at most Pi/2 in longitude. The minimum distance is always // between one corner of B and the diagonally opposite corner of B'. We // use the same distance approximation that we used above; in this case // we have an obtuse triangle XYZ with two edges of length x = lat_gap1 // and y = lat_gap2, and angle Z >= Pi/2 between them. We then have // // z >= Math.Sqrt(x^2 + y^2) >= (x + y) / Math.Sqrt(2) // // Unlike the case above, "lat_gap1" and "lat_gap2" are not lower bounds // (because of the extra addition operation, and because S2Constants.M_PI_2 is not // exactly equal to Pi/2); they can exceed their true values by up to // 0.75 * S2Constants.DoubleEpsilon. Putting this all together, the region may // contain nearly antipodal points only if // // lat_gap1 + lat_gap2 < (Math.Sqrt(2) * 4.309 + 1.5) * S2Constants.DoubleEpsilon // ~= 1.687e-15 if (lat_gap1 + lat_gap2 < 1.687e-15) { return(S2LatLngRect.Full); } } else { // Otherwise we know that (1) the bound straddles the equator and (2) its // width in longitude is at least Pi/2. In this case the minimum // distance can occur either between a corner of B and the diagonally // opposite corner of B' (as in the case above), or between a corner of B // and the opposite longitudinal edge reflected in B'. It is sufficient // to only consider the corner-edge case, since this distance is also a // lower bound on the corner-corner distance when that case applies. // Consider the spherical triangle XYZ where X is a corner of B with // minimum absolute latitude, Y is the closest pole to X, and Z is the // point closest to X on the opposite longitudinal edge of B'. This is a // right triangle (Z = Pi/2), and from the spherical law of sines we have // // sin(z) / sin(Z) = sin(y) / sin(Y) // sin(max_lat_gap) / 1 = sin(d_min) / sin(lng_gap) // sin(d_min) = sin(max_lat_gap) * sin(lng_gap) // // where "max_lat_gap" = Math.Max(lat_gap1, lat_gap2) and "d_min" is the // desired minimum distance. Now using the facts that sin(t) >= (2/Pi)*t // for 0 <= t <= Pi/2, that we only need an accurate approximation when // at least one of "max_lat_gap" or "lng_gap" is extremely small (in // which case sin(t) ~= t), and recalling that "max_lat_gap" has an error // of up to 0.75 * S2Constants.DoubleEpsilon, we want to test whether // // max_lat_gap * lng_gap < (4.309 + 0.75) * (Pi/2) * S2Constants.DoubleEpsilon // ~= 1.765e-15 if (Math.Max(lat_gap1, lat_gap2) * lng_gap < 1.765e-15) { return(S2LatLngRect.Full); } } // Next we need to check whether the subregion might contain any edges that // span (Math.PI - 2 * S2Constants.DoubleEpsilon) radians or more in longitude, since AddPoint // sets the longitude bound to Full() in that case. This corresponds to // testing whether (lng_gap <= 0) in "lng_expansion" below. // Otherwise, the maximum latitude error in AddPoint is 4.8 * S2Constants.DoubleEpsilon. // In the worst case, the errors when computing the latitude bound for a // subregion could go in the opposite direction as the errors when computing // the bound for the original region, so we need to double this value. // (More analysis shows that it's okay to round down to a multiple of // S2Constants.DoubleEpsilon.) // // For longitude, we rely on the fact that atan2 is correctly rounded and // therefore no additional bounds expansion is necessary. double lat_expansion = 9 * S2.DoubleEpsilon; double lng_expansion = (lng_gap <= 0) ? Math.PI : 0; return(bound.Expanded(S2LatLng.FromRadians(lat_expansion, lng_expansion)).PolarClosure()); }