private bool loopsEqual(S2Loop a, S2Loop b, double maxError) { // Return true if two loops have the same cyclic vertex sequence. if (a.NumVertices != b.NumVertices) { return(false); } for (var offset = 0; offset < a.NumVertices; ++offset) { if (S2.ApproxEquals(a.Vertex(offset), b.Vertex(0), maxError)) { var success = true; for (var i = 0; i < a.NumVertices; ++i) { if (!S2.ApproxEquals(a.Vertex(i + offset), b.Vertex(i), maxError)) { success = false; break; } } if (success) { return(true); } // Otherwise continue looping. There may be more than one candidate // starting offset since vertices are only matched approximately. } } return(false); }
public void Test_GetCentroid_Polyline() { // Checks that points are ignored when computing the centroid. Assert.True(S2.ApproxEquals( new S2Point(1, 1, 0), S2ShapeIndexMeasures.GetCentroid(MakeIndexOrDie("5:5 | 6:6 # 0:0, 0:90 #")))); }
public void Test_GetCentroid_Polygon() { // Checks that points and polylines are ignored when computing the centroid. Assert.True(S2.ApproxEquals( new S2Point(S2.M_PI_4, S2.M_PI_4, S2.M_PI_4), S2ShapeIndexMeasures.GetCentroid(MakeIndexOrDie("5:5 # 6:6, 7:7 # 0:0, 0:90, 90:0")))); }
// Given a point X and an edge AB, check that the distance from X to AB is // "distance_radians" and the closest point on AB is "expected_closest". private static void CheckDistance(S2Point x, S2Point a, S2Point b, double distance_radians, S2Point expected_closest) { x = x.Normalize(); a = a.Normalize(); b = b.Normalize(); expected_closest = expected_closest.Normalize(); Assert2.Near(distance_radians, S2.GetDistance(x, a, b).Radians, S2.DoubleError); S2Point closest = S2.Project(x, a, b); Assert.True(S2Pred.CompareEdgeDistance( closest, a, b, new S1ChordAngle(S2.kProjectPerpendicularErrorS1Angle)) < 0); // If X is perpendicular to AB then there is nothing further we can expect. if (distance_radians != S2.M_PI_2) { if (expected_closest == new S2Point()) { // This special value says that the result should be A or B. Assert.True(closest == a || closest == b); } else { Assert.True(S2.ApproxEquals(closest, expected_closest)); } } S1ChordAngle min_distance = S1ChordAngle.Zero; Assert.False(S2.UpdateMinDistance(x, a, b, ref min_distance)); min_distance = S1ChordAngle.Infinity; Assert.True(S2.UpdateMinDistance(x, a, b, ref min_distance)); Assert2.Near(distance_radians, min_distance.ToAngle().Radians, S2.DoubleError); }
public void testConversion() { // Test special cases: poles, "date line" assertDoubleNear( new S2LatLng(S2LatLng.FromDegrees(90.0, 65.0).ToPoint()).Lat.Degrees, 90.0); assertEquals( new S2LatLng(S2LatLng.FromRadians(-S2.PiOver2, 1).ToPoint()).Lat.Radians, -S2.PiOver2); assertDoubleNear( Math.Abs(new S2LatLng(S2LatLng.FromDegrees(12.2, 180.0).ToPoint()).Lng.Degrees), 180.0); assertEquals( Math.Abs(new S2LatLng(S2LatLng.FromRadians(0.1, -S2.Pi).ToPoint()).Lng.Radians), S2.Pi); // Test a bunch of random points. for (var i = 0; i < 100000; ++i) { var p = randomPoint(); assertTrue(S2.ApproxEquals(p, new S2LatLng(p).ToPoint())); } // Test generation from E5 var test = S2LatLng.FromE5(123456, 98765); assertDoubleNear(test.Lat.Degrees, 1.23456); assertDoubleNear(test.Lng.Degrees, 0.98765); }
public void Test_GetCentroid_Polygon() { // GetCentroid returns the centroid multiplied by the area of the polygon. Assert.True(S2.ApproxEquals( new S2Point(S2.M_PI_4, S2.M_PI_4, S2.M_PI_4), S2.GetCentroid(MakeLaxPolygonOrDie("0:0, 0:90, 90:0")))); }
public void Test_GetCentroid_Polyline() { // GetCentroid returns the centroid multiplied by the length of the polyline. Assert.True(S2.ApproxEquals( new S2Point(1, 1, 0), S2ShapeIndexMeasures.GetCentroid(MakeIndexOrDie("0:0, 0:90")))); }
void TestProjectUnproject(Projection projection, R2Point px, S2Point x) { // The arguments are chosen such that projection is exact, but // unprojection may not be. Assert.Equal(px, projection.Project(x)); Assert.True(S2.ApproxEquals(x, projection.Unproject(px))); }
public void Test_EdgeTrueCentroid_SemiEquator() { // Test the centroid of polyline ABC that follows the equator and consists // of two 90 degree edges (i.e., C = -A). The centroid (multiplied by // length) should point toward B and have a norm of 2.0. (The centroid // itself has a norm of 2/Pi, and the total edge length is Pi.) S2Point a = new(0, -1, 0), b = new(1, 0, 0), c = new(0, 1, 0); S2Point centroid = S2Centroid.TrueCentroid(a, b) + S2Centroid.TrueCentroid(b, c); Assert.True(S2.ApproxEquals(b, centroid.Normalize())); Assert2.DoubleEqual(2.0, centroid.Norm()); }
public void Test_S2LatLng_TestConversion() { // Test special cases: poles, "date line" Assert2.DoubleEqual(90.0, new S2LatLng(S2LatLng.FromDegrees(90.0, 65.0).ToPoint()).Lat().GetDegrees()); Assert.Equal(-S2.M_PI_2, new S2LatLng(S2LatLng.FromRadians(-S2.M_PI_2, 1).ToPoint()).LatRadians); Assert2.DoubleEqual(180.0, Math.Abs(new S2LatLng(S2LatLng.FromDegrees(12.2, 180.0).ToPoint()).Lng().GetDegrees())); Assert.Equal(Math.PI, Math.Abs(new S2LatLng(S2LatLng.FromRadians(0.1, -Math.PI).ToPoint()).LngRadians)); // Test a bunch of random points. for (int i = 0; i < 100000; ++i) { S2Point p = S2Testing.RandomPoint(); Assert.True(S2.ApproxEquals(p, new S2LatLng(p).ToPoint())); } }
public void Test_S2_Frames() { var z = new S2Point(0.2, 0.5, -3.3).Normalize(); var m = S2.GetFrame(z); Assert.True(S2.ApproxEquals(m.Col(2), z)); Assert.True(m.Col(0).IsUnitLength()); Assert.True(m.Col(1).IsUnitLength()); Assert2.DoubleEqual(m.Det(), 1); Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(0)), new(1, 0, 0))); Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(1)), new(0, 1, 0))); Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(2)), new(0, 0, 1))); Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(1, 0, 0)), m.Col(0))); Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(0, 1, 0)), m.Col(1))); Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(0, 0, 1)), m.Col(2))); }
public void testInterpolate() { var vertices = new List <S2Point>(); vertices.Add(new S2Point(1, 0, 0)); vertices.Add(new S2Point(0, 1, 0)); vertices.Add(S2Point.Normalize(new S2Point(0, 1, 1))); vertices.Add(new S2Point(0, 0, 1)); var line = new S2Polyline(vertices); assertEquals(line.Interpolate(-0.1), vertices[0]); assertTrue(S2.ApproxEquals( line.Interpolate(0.1), S2Point.Normalize(new S2Point(1, Math.Tan(0.2 * S2.Pi / 2), 0)))); assertTrue(S2.ApproxEquals(line.Interpolate(0.25), S2Point.Normalize(new S2Point(1, 1, 0)))); assertTrue(S2.ApproxEquals(line.Interpolate(0.5), vertices[1])); assertTrue(S2.ApproxEquals(line.Interpolate(0.75), vertices[2])); assertTrue(S2.ApproxEquals(line.Interpolate(1.1), vertices[3])); }
public void testProject() { var latLngs = new List <S2Point>(); latLngs.Add(S2LatLng.FromDegrees(0, 0).ToPoint()); latLngs.Add(S2LatLng.FromDegrees(0, 1).ToPoint()); latLngs.Add(S2LatLng.FromDegrees(0, 2).ToPoint()); latLngs.Add(S2LatLng.FromDegrees(1, 2).ToPoint()); var line = new S2Polyline(latLngs); var edgeIndex = -1; S2Point testPoint = default(S2Point); testPoint = S2LatLng.FromDegrees(0.5, -0.5).ToPoint(); edgeIndex = line.GetNearestEdgeIndex(testPoint); assertTrue(S2.ApproxEquals( line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 0).ToPoint())); assertEquals(0, edgeIndex); testPoint = S2LatLng.FromDegrees(0.5, 0.5).ToPoint(); edgeIndex = line.GetNearestEdgeIndex(testPoint); assertTrue(S2.ApproxEquals( line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 0.5).ToPoint())); assertEquals(0, edgeIndex); testPoint = S2LatLng.FromDegrees(0.5, 1).ToPoint(); edgeIndex = line.GetNearestEdgeIndex(testPoint); assertTrue(S2.ApproxEquals( line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 1).ToPoint())); assertEquals(0, edgeIndex); testPoint = S2LatLng.FromDegrees(-0.5, 2.5).ToPoint(); edgeIndex = line.GetNearestEdgeIndex(testPoint); assertTrue(S2.ApproxEquals( line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 2).ToPoint())); assertEquals(1, edgeIndex); testPoint = S2LatLng.FromDegrees(2, 2).ToPoint(); edgeIndex = line.GetNearestEdgeIndex(testPoint); assertTrue(S2.ApproxEquals( line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(1, 2).ToPoint())); assertEquals(2, edgeIndex); }
// Converts the planar edge AB in the given projection to a chain of // spherical geodesic edges and appends the vertices to "vertices". // // This method can be called multiple times with the same output vector to // convert an entire polyline or loop. All vertices of the first edge are // appended, but the first vertex of each subsequent edge is omitted (and is // required to match that last vertex of the previous edge). // // Note that to construct an S2Loop, you must call vertices.pop_back() at // the very end to eliminate the duplicate first and last vertex. Note also // that if the given projection involves coordinate "wrapping" (e.g. across // the 180 degree meridian) then the first and last vertices may not be // exactly the same. public void AppendUnprojected(R2Point a, R2Point b, List <S2Point> vertices) { var pointA = proj_.Unproject(a); var pointB = proj_.Unproject(b); if (!vertices.Any()) { vertices.Add(pointA); } else { // Note that coordinate wrapping can create a small amount of error. For // example in the edge chain "0:-175, 0:179, 0:-177", the first edge is // transformed into "0:-175, 0:-181" while the second is transformed into // "0:179, 0:183". The two coordinate pairs for the middle vertex // ("0:-181" and "0:179") may not yield exactly the same S2Point. System.Diagnostics.Debug.Assert(S2.ApproxEquals(vertices.Last(), pointA)); // Appended edges must form a chain } AppendUnprojected(a, pointA, b, pointB, vertices); }
// Given two edges a0a1 and b0b1, check that the minimum distance between them // is "distance_radians", and that GetEdgePairClosestPoints() returns // "expected_a" and "expected_b" as the points that achieve this distance. // S2Point.Empty may be passed for "expected_a" or "expected_b" to indicate // that both endpoints of the corresponding edge are equally distant, and // therefore either one might be returned. // // Parameters are passed by value so that this function can normalize them. private static void CheckEdgePairMinDistance(S2Point a0, S2Point a1, S2Point b0, S2Point b1, double distance_radians, S2Point expected_a, S2Point expected_b) { a0 = a0.Normalize(); a1 = a1.Normalize(); b0 = b0.Normalize(); b1 = b1.Normalize(); expected_a = expected_a.Normalize(); expected_b = expected_b.Normalize(); var closest = S2.GetEdgePairClosestPoints(a0, a1, b0, b1); S2Point actual_a = closest.Item1; S2Point actual_b = closest.Item2; if (expected_a == S2Point.Empty) { // This special value says that the result should be a0 or a1. Assert.True(actual_a == a0 || actual_a == a1); } else { Assert.True(S2.ApproxEquals(expected_a, actual_a)); } if (expected_b == S2Point.Empty) { // This special value says that the result should be b0 or b1. Assert.True(actual_b == b0 || actual_b == b1); } else { Assert.True(S2.ApproxEquals(expected_b, actual_b)); } S1ChordAngle min_distance = S1ChordAngle.Zero; Assert.False(S2.UpdateEdgePairMinDistance(a0, a1, b0, b1, ref min_distance)); min_distance = S1ChordAngle.Infinity; Assert.True(S2.UpdateEdgePairMinDistance(a0, a1, b0, b1, ref min_distance)); Assert2.Near(distance_radians, min_distance.Radians(), S2.DoubleError); }
// Given a point X and an edge AB, check that the distance from X to AB is // "distanceRadians" and the closest point on AB is "expectedClosest". private static void checkDistance( S2Point x, S2Point a, S2Point b, double distanceRadians, S2Point expectedClosest) { var kEpsilon = 1e-10; x = S2Point.Normalize(x); a = S2Point.Normalize(a); b = S2Point.Normalize(b); expectedClosest = S2Point.Normalize(expectedClosest); assertEquals(distanceRadians, S2EdgeUtil.GetDistance(x, a, b).Radians, kEpsilon); var closest = S2EdgeUtil.GetClosestPoint(x, a, b); if (expectedClosest.Equals(new S2Point(0, 0, 0))) { // This special value says that the result should be A or B. assertTrue(closest == a || closest == b); } else { assertTrue(S2.ApproxEquals(closest, expectedClosest)); } }
// Common back end for AddPoint() and AddLatLng(). b and b_latlng // must refer to the same vertex. private void AddInternal(S2Point b, S2LatLng b_latlng) { // Simple consistency check to verify that b and b_latlng are alternate // representations of the same vertex. System.Diagnostics.Debug.Assert(S2.ApproxEquals(b, b_latlng.ToPoint())); if (bound_.IsEmpty()) { bound_ = bound_.AddPoint(b_latlng); } else { // First compute the cross product N = A x B robustly. This is the normal // to the great circle through A and B. We don't use S2.RobustCrossProd() // since that method returns an arbitrary vector orthogonal to A if the two // vectors are proportional, and we want the zero vector in that case. var n = (a_ - b).CrossProd(a_ + b); // N = 2 * (A x B) // The relative error in N gets large as its norm gets very small (i.e., // when the two points are nearly identical or antipodal). We handle this // by choosing a maximum allowable error, and if the error is greater than // this we fall back to a different technique. Since it turns out that // the other sources of error in converting the normal to a maximum // latitude add up to at most 1.16 * S2Constants.DoubleEpsilon (see below), and it is // desirable to have the total error be a multiple of S2Constants.DoubleEpsilon, we have // chosen to limit the maximum error in the normal to 3.84 * S2Constants.DoubleEpsilon. // It is possible to show that the error is less than this when // // n.Norm >= 8 * Math.Sqrt(3) / (3.84 - 0.5 - Math.Sqrt(3)) * S2Constants.DoubleEpsilon // = 1.91346e-15 (about 8.618 * S2Constants.DoubleEpsilon) var n_norm = n.Norm(); if (n_norm < 1.91346e-15) { // A and B are either nearly identical or nearly antipodal (to within // 4.309 * S2Constants.DoubleEpsilon, or about 6 nanometers on the earth's surface). if (a_.DotProd(b) < 0) { // The two points are nearly antipodal. The easiest solution is to // assume that the edge between A and B could go in any direction // around the sphere. bound_ = S2LatLngRect.Full; } else { // The two points are nearly identical (to within 4.309 * S2Constants.DoubleEpsilon). // In this case we can just use the bounding rectangle of the points, // since after the expansion done by GetBound() this rectangle is // guaranteed to include the (lat,lng) values of all points along AB. bound_ = bound_.Union(S2LatLngRect.FromPointPair(a_latlng_, b_latlng)); } } else { // Compute the longitude range spanned by AB. var lng_ab = S1Interval.FromPointPair(a_latlng_.LngRadians, b_latlng.LngRadians); if (lng_ab.GetLength() >= Math.PI - 2 * S2.DoubleEpsilon) { // The points lie on nearly opposite lines of longitude to within the // maximum error of the calculation. (Note that this test relies on // the fact that Math.PI is slightly less than the true value of Pi, and // that representable values near Math.PI are 2 * S2Constants.DoubleEpsilon apart.) // The easiest solution is to assume that AB could go on either side // of the pole. lng_ab = S1Interval.Full; } // Next we compute the latitude range spanned by the edge AB. We start // with the range spanning the two endpoints of the edge: var lat_ab = R1Interval.FromPointPair(a_latlng_.LatRadians, b_latlng.LatRadians); // This is the desired range unless the edge AB crosses the plane // through N and the Z-axis (which is where the great circle through A // and B attains its minimum and maximum latitudes). To test whether AB // crosses this plane, we compute a vector M perpendicular to this // plane and then project A and B onto it. var m = n.CrossProd(new S2Point(0, 0, 1)); var m_a = m.DotProd(a_); var m_b = m.DotProd(b); // We want to test the signs of "m_a" and "m_b", so we need to bound // the error in these calculations. It is possible to show that the // total error is bounded by // // (1 + Math.Sqrt(3)) * S2Constants.DoubleEpsilon * n_norm + 8 * Math.Sqrt(3) * (S2Constants.DoubleEpsilon**2) // = 6.06638e-16 * n_norm + 6.83174e-31 double m_error = 6.06638e-16 * n_norm + 6.83174e-31; if (m_a * m_b < 0 || Math.Abs(m_a) <= m_error || Math.Abs(m_b) <= m_error) { // Minimum/maximum latitude *may* occur in the edge interior. // // The maximum latitude is 90 degrees minus the latitude of N. We // compute this directly using atan2 in order to get maximum accuracy // near the poles. // // Our goal is compute a bound that contains the computed latitudes of // all S2Points P that pass the point-in-polygon containment test. // There are three sources of error we need to consider: // - the directional error in N (at most 3.84 * S2Constants.DoubleEpsilon) // - converting N to a maximum latitude // - computing the latitude of the test point P // The latter two sources of error are at most 0.955 * S2Constants.DoubleEpsilon // individually, but it is possible to show by a more complex analysis // that together they can add up to at most 1.16 * S2Constants.DoubleEpsilon, for a // total error of 5 * S2Constants.DoubleEpsilon. // // We add 3 * S2Constants.DoubleEpsilon to the bound here, and GetBound() will pad // the bound by another 2 * S2Constants.DoubleEpsilon. var max_lat = Math.Min( Math.Atan2(Math.Sqrt(n[0] * n[0] + n[1] * n[1]), Math.Abs(n[2])) + 3 * S2.DoubleEpsilon, S2.M_PI_2); // In order to get tight bounds when the two points are close together, // we also bound the min/max latitude relative to the latitudes of the // endpoints A and B. First we compute the distance between A and B, // and then we compute the maximum change in latitude between any two // points along the great circle that are separated by this distance. // This gives us a latitude change "budget". Some of this budget must // be spent getting from A to B; the remainder bounds the round-trip // distance (in latitude) from A or B to the min or max latitude // attained along the edge AB. // // There is a maximum relative error of 4.5 * DBL_EPSILON in computing // the squared distance (a_ - b), which means a maximum error of (4.5 // / 2 + 0.5) == 2.75 * DBL_EPSILON in computing Norm(). The sin() // and multiply each have a relative error of 0.5 * DBL_EPSILON which // we round up to a total of 4 * DBL_EPSILON. var lat_budget_z = 0.5 * (a_ - b).Norm() * Math.Sin(max_lat); const double folded = (1 + 4 * S2.DoubleEpsilon); var lat_budget = 2 * Math.Asin(Math.Min(folded * lat_budget_z, 1.0)); var max_delta = 0.5 * (lat_budget - lat_ab.GetLength()) + S2.DoubleEpsilon; // Test whether AB passes through the point of maximum latitude or // minimum latitude. If the dot product(s) are small enough then the // result may be ambiguous. if (m_a <= m_error && m_b >= -m_error) { lat_ab = new R1Interval(lat_ab.Lo, Math.Min(max_lat, lat_ab.Hi + max_delta)); } if (m_b <= m_error && m_a >= -m_error) { lat_ab = new R1Interval(Math.Max(-max_lat, lat_ab.Lo - max_delta), lat_ab.Lo); } } bound_ = bound_.Union(new S2LatLngRect(lat_ab, lng_ab)); } } a_ = b; a_latlng_ = b_latlng; }
static void TestSubdivide(S2Cell cell) { GatherStats(cell); if (cell.IsLeaf()) { return; } var children = new S2Cell[4]; Assert.True(cell.Subdivide(children)); S2CellId child_id = cell.Id.ChildBegin(); double exact_area = 0; double approx_area = 0; double average_area = 0; for (int i = 0; i < 4; ++i, child_id = child_id.Next()) { exact_area += children[i].ExactArea(); approx_area += children[i].ApproxArea(); average_area += children[i].AverageArea(); // Check that the child geometry is consistent with its cell ID. Assert.Equal(child_id, children[i].Id); Assert.True(S2.ApproxEquals(children[i].Center(), child_id.ToPoint())); S2Cell direct = new(child_id); Assert.Equal(direct.Face, children[i].Face); Assert.Equal(direct.Level, children[i].Level); Assert.Equal(direct.Orientation, children[i].Orientation); Assert.Equal(direct.CenterRaw(), children[i].CenterRaw()); for (int k = 0; k < 4; ++k) { Assert.Equal(direct.VertexRaw(k), children[i].VertexRaw(k)); Assert.Equal(direct.EdgeRaw(k), children[i].EdgeRaw(k)); } // Test Contains() and MayIntersect(). Assert.True(cell.Contains(children[i])); Assert.True(cell.MayIntersect(children[i])); Assert.False(children[i].Contains(cell)); Assert.True(cell.Contains(children[i].CenterRaw())); for (int j = 0; j < 4; ++j) { Assert.True(cell.Contains(children[i].VertexRaw(j))); if (j != i) { Assert.False(children[i].Contains(children[j].CenterRaw())); Assert.False(children[i].MayIntersect(children[j])); } } // Test GetCapBound and GetRectBound. S2Cap parent_cap = cell.GetCapBound(); S2LatLngRect parent_rect = cell.GetRectBound(); if (cell.Contains(new S2Point(0, 0, 1)) || cell.Contains(new S2Point(0, 0, -1))) { Assert.True(parent_rect.Lng.IsFull()); } S2Cap child_cap = children[i].GetCapBound(); S2LatLngRect child_rect = children[i].GetRectBound(); Assert.True(child_cap.Contains(children[i].Center())); Assert.True(child_rect.Contains(children[i].CenterRaw())); Assert.True(parent_cap.Contains(children[i].Center())); Assert.True(parent_rect.Contains(children[i].CenterRaw())); for (int j = 0; j < 4; ++j) { Assert.True(child_cap.Contains(children[i].Vertex(j))); Assert.True(child_rect.Contains(children[i].Vertex(j))); Assert.True(child_rect.Contains(children[i].VertexRaw(j))); Assert.True(parent_cap.Contains(children[i].Vertex(j))); Assert.True(parent_rect.Contains(children[i].Vertex(j))); Assert.True(parent_rect.Contains(children[i].VertexRaw(j))); if (j != i) { // The bounding caps and rectangles should be tight enough so that // they exclude at least two vertices of each adjacent cell. int cap_count = 0; int rect_count = 0; for (int k = 0; k < 4; ++k) { if (child_cap.Contains(children[j].Vertex(k))) { ++cap_count; } if (child_rect.Contains(children[j].VertexRaw(k))) { ++rect_count; } } Assert.True(cap_count <= 2); if (child_rect.LatLo().Radians > -S2.M_PI_2 && child_rect.LatHi().Radians < S2.M_PI_2) { // Bounding rectangles may be too large at the poles because the // pole itself has an arbitrary fixed longitude. Assert.True(rect_count <= 2); } } } // Check all children for the first few levels, and then sample randomly. // We also always subdivide the cells containing a few chosen points so // that we have a better chance of sampling the minimum and maximum metric // values. kMaxSizeUV is the absolute value of the u- and v-coordinate // where the cell size at a given level is maximal. double kMaxSizeUV = 0.3964182625366691; R2Point[] special_uv = { new R2Point(S2.DoubleEpsilon, S2.DoubleEpsilon), // Face center new R2Point(S2.DoubleEpsilon, 1), // Edge midpoint new R2Point(1, 1), // Face corner new R2Point(kMaxSizeUV, kMaxSizeUV), // Largest cell area new R2Point(S2.DoubleEpsilon, kMaxSizeUV), // Longest edge/diagonal }; bool force_subdivide = false; foreach (R2Point uv in special_uv) { if (children[i].BoundUV.Contains(uv)) { force_subdivide = true; } } var debugFlag = #if s2debug true; #else false; #endif if (force_subdivide || cell.Level < (debugFlag ? 5 : 6) || S2Testing.Random.OneIn(debugFlag ? 5 : 4)) { TestSubdivide(children[i]); } } // Check sum of child areas equals parent area. // // For ExactArea(), the best relative error we can expect is about 1e-6 // because the precision of the unit vector coordinates is only about 1e-15 // and the edge length of a leaf cell is about 1e-9. // // For ApproxArea(), the areas are accurate to within a few percent. // // For AverageArea(), the areas themselves are not very accurate, but // the average area of a parent is exactly 4 times the area of a child. Assert.True(Math.Abs(Math.Log(exact_area / cell.ExactArea())) <= Math.Abs(Math.Log((1 + 1e-6)))); Assert.True(Math.Abs(Math.Log((approx_area / cell.ApproxArea()))) <= Math.Abs(Math.Log((1.03)))); Assert.True(Math.Abs(Math.Log((average_area / cell.AverageArea()))) <= Math.Abs(Math.Log((1 + 1e-15)))); }