private static void gatherStats(S2Cell cell) { var s = levelStats[cell.Level]; var exactArea = cell.ExactArea(); var approxArea = cell.ApproxArea(); double minEdge = 100, maxEdge = 0, avgEdge = 0; double minDiag = 100, maxDiag = 0; double minWidth = 100, maxWidth = 0; double minAngleSpan = 100, maxAngleSpan = 0; for (var i = 0; i < 4; ++i) { var edge = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw((i + 1) & 3)); minEdge = Math.Min(edge, minEdge); maxEdge = Math.Max(edge, maxEdge); avgEdge += 0.25 * edge; var mid = cell.GetVertexRaw(i) + cell.GetVertexRaw((i + 1) & 3); var width = S2.PiOver2 - mid.Angle(cell.GetEdgeRaw(i ^ 2)); minWidth = Math.Min(width, minWidth); maxWidth = Math.Max(width, maxWidth); if (i < 2) { var diag = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw(i ^ 2)); minDiag = Math.Min(diag, minDiag); maxDiag = Math.Max(diag, maxDiag); var angleSpan = cell.GetEdgeRaw(i).Angle( -cell.GetEdgeRaw(i ^ 2)); minAngleSpan = Math.Min(angleSpan, minAngleSpan); maxAngleSpan = Math.Max(angleSpan, maxAngleSpan); } } s.count += 1; s.minArea = Math.Min(exactArea, s.minArea); s.maxArea = Math.Max(exactArea, s.maxArea); s.avgArea += exactArea; s.minWidth = Math.Min(minWidth, s.minWidth); s.maxWidth = Math.Max(maxWidth, s.maxWidth); s.avgWidth += 0.5 * (minWidth + maxWidth); s.minEdge = Math.Min(minEdge, s.minEdge); s.maxEdge = Math.Max(maxEdge, s.maxEdge); s.avgEdge += avgEdge; s.maxEdgeAspect = Math.Max(maxEdge / minEdge, s.maxEdgeAspect); s.minDiag = Math.Min(minDiag, s.minDiag); s.maxDiag = Math.Max(maxDiag, s.maxDiag); s.avgDiag += 0.5 * (minDiag + maxDiag); s.maxDiagAspect = Math.Max(maxDiag / minDiag, s.maxDiagAspect); s.minAngleSpan = Math.Min(minAngleSpan, s.minAngleSpan); s.maxAngleSpan = Math.Max(maxAngleSpan, s.maxAngleSpan); s.avgAngleSpan += 0.5 * (minAngleSpan + maxAngleSpan); var approxRatio = approxArea / exactArea; s.minApproxRatio = Math.Min(approxRatio, s.minApproxRatio); s.maxApproxRatio = Math.Max(approxRatio, s.maxApproxRatio); }
private static void GatherStats(S2Cell cell) { var s = level_stats[cell.Level]; double exact_area = cell.ExactArea(); double approx_area = cell.ApproxArea(); double min_edge = 100, max_edge = 0, avg_edge = 0; double min_diag = 100, max_diag = 0; double min_width = 100, max_width = 0; double min_angle_span = 100, max_angle_span = 0; for (int i = 0; i < 4; ++i) { double edge = cell.VertexRaw(i).Angle(cell.VertexRaw(i + 1)); min_edge = Math.Min(edge, min_edge); max_edge = Math.Max(edge, max_edge); avg_edge += 0.25 * edge; S2Point mid = cell.VertexRaw(i) + cell.VertexRaw(i + 1); double width = S2.M_PI_2 - mid.Angle(cell.EdgeRaw(i + 2)); min_width = Math.Min(width, min_width); max_width = Math.Max(width, max_width); if (i < 2) { double diag = cell.VertexRaw(i).Angle(cell.VertexRaw(i + 2)); min_diag = Math.Min(diag, min_diag); max_diag = Math.Max(diag, max_diag); double angle_span = cell.EdgeRaw(i).Angle(-cell.EdgeRaw(i + 2)); min_angle_span = Math.Min(angle_span, min_angle_span); max_angle_span = Math.Max(angle_span, max_angle_span); } } s.Count += 1; s.Min_area = Math.Min(exact_area, s.Min_area); s.Max_area = Math.Max(exact_area, s.Max_area); s.Avg_area += exact_area; s.Min_width = Math.Min(min_width, s.Min_width); s.Max_width = Math.Max(max_width, s.Max_width); s.Avg_width += 0.5 * (min_width + max_width); s.Min_edge = Math.Min(min_edge, s.Min_edge); s.Max_edge = Math.Max(max_edge, s.Max_edge); s.Avg_edge += avg_edge; s.Max_edge_aspect = Math.Max(max_edge / min_edge, s.Max_edge_aspect); s.Min_diag = Math.Min(min_diag, s.Min_diag); s.Max_diag = Math.Max(max_diag, s.Max_diag); s.Avg_diag += 0.5 * (min_diag + max_diag); s.Max_diag_aspect = Math.Max(max_diag / min_diag, s.Max_diag_aspect); s.Min_angle_span = Math.Min(min_angle_span, s.Min_angle_span); s.Max_angle_span = Math.Max(max_angle_span, s.Max_angle_span); s.Avg_angle_span += 0.5 * (min_angle_span + max_angle_span); double approx_ratio = approx_area / exact_area; s.Min_approx_ratio = Math.Min(approx_ratio, s.Min_approx_ratio); s.Max_approx_ratio = Math.Max(approx_ratio, s.Max_approx_ratio); }
private static void gatherStats(S2Cell cell) { var s = levelStats[cell.Level]; var exactArea = cell.ExactArea(); var approxArea = cell.ApproxArea(); double minEdge = 100, maxEdge = 0, avgEdge = 0; double minDiag = 100, maxDiag = 0; double minWidth = 100, maxWidth = 0; double minAngleSpan = 100, maxAngleSpan = 0; for (var i = 0; i < 4; ++i) { var edge = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw((i + 1) & 3)); minEdge = Math.Min(edge, minEdge); maxEdge = Math.Max(edge, maxEdge); avgEdge += 0.25*edge; var mid = cell.GetVertexRaw(i) + cell.GetVertexRaw((i + 1) & 3); var width = S2.PiOver2 - mid.Angle(cell.GetEdgeRaw(i ^ 2)); minWidth = Math.Min(width, minWidth); maxWidth = Math.Max(width, maxWidth); if (i < 2) { var diag = cell.GetVertexRaw(i).Angle(cell.GetVertexRaw(i ^ 2)); minDiag = Math.Min(diag, minDiag); maxDiag = Math.Max(diag, maxDiag); var angleSpan = cell.GetEdgeRaw(i).Angle( -cell.GetEdgeRaw(i ^ 2)); minAngleSpan = Math.Min(angleSpan, minAngleSpan); maxAngleSpan = Math.Max(angleSpan, maxAngleSpan); } } s.count += 1; s.minArea = Math.Min(exactArea, s.minArea); s.maxArea = Math.Max(exactArea, s.maxArea); s.avgArea += exactArea; s.minWidth = Math.Min(minWidth, s.minWidth); s.maxWidth = Math.Max(maxWidth, s.maxWidth); s.avgWidth += 0.5*(minWidth + maxWidth); s.minEdge = Math.Min(minEdge, s.minEdge); s.maxEdge = Math.Max(maxEdge, s.maxEdge); s.avgEdge += avgEdge; s.maxEdgeAspect = Math.Max(maxEdge/minEdge, s.maxEdgeAspect); s.minDiag = Math.Min(minDiag, s.minDiag); s.maxDiag = Math.Max(maxDiag, s.maxDiag); s.avgDiag += 0.5*(minDiag + maxDiag); s.maxDiagAspect = Math.Max(maxDiag/minDiag, s.maxDiagAspect); s.minAngleSpan = Math.Min(minAngleSpan, s.minAngleSpan); s.maxAngleSpan = Math.Max(maxAngleSpan, s.maxAngleSpan); s.avgAngleSpan += 0.5*(minAngleSpan + maxAngleSpan); var approxRatio = approxArea/exactArea; s.minApproxRatio = Math.Min(approxRatio, s.minApproxRatio); s.maxApproxRatio = Math.Max(approxRatio, s.maxApproxRatio); }
public void testSubdivide(S2Cell cell) { gatherStats(cell); if (cell.IsLeaf) { return; } var children = new S2Cell[4]; for (var i = 0; i < children.Length; ++i) { children[i] = new S2Cell(); } Assert.True(cell.Subdivide(children)); var childId = cell.Id.ChildBegin; double exactArea = 0; double approxArea = 0; double averageArea = 0; for (var i = 0; i < 4; ++i, childId = childId.Next) { exactArea += children[i].ExactArea(); approxArea += children[i].ApproxArea(); averageArea += children[i].AverageArea(); // Check that the child geometry is consistent with its cell id. JavaAssert.Equal(children[i].Id, childId); Assert.True(children[i].Center.ApproxEquals(childId.ToPoint(), 1e-15)); var direct = new S2Cell(childId); JavaAssert.Equal(children[i].Face, direct.Face); JavaAssert.Equal(children[i].Level, direct.Level); JavaAssert.Equal(children[i].Orientation, direct.Orientation); JavaAssert.Equal(children[i].CenterRaw, direct.CenterRaw); for (var k = 0; k < 4; ++k) { JavaAssert.Equal(children[i].GetVertexRaw(k), direct.GetVertexRaw(k)); JavaAssert.Equal(children[i].GetEdgeRaw(k), direct.GetEdgeRaw(k)); } // Test Contains() and MayIntersect(). Assert.True(cell.Contains(children[i])); Assert.True(cell.MayIntersect(children[i])); Assert.True(!children[i].Contains(cell)); Assert.True(cell.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(cell.Contains(children[i].GetVertexRaw(j))); if (j != i) { Assert.True(!children[i].Contains(children[j].CenterRaw)); Assert.True(!children[i].MayIntersect(children[j])); } } // Test GetCapBound and GetRectBound. var parentCap = cell.CapBound; var parentRect = cell.RectBound; if (cell.Contains(new S2Point(0, 0, 1)) || cell.Contains(new S2Point(0, 0, -1))) { Assert.True(parentRect.Lng.IsFull); } var childCap = children[i].CapBound; var childRect = children[i].RectBound; Assert.True(childCap.Contains(children[i].Center)); Assert.True(childRect.Contains(children[i].CenterRaw)); Assert.True(parentCap.Contains(children[i].Center)); Assert.True(parentRect.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(childCap.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertexRaw(j))); Assert.True(parentCap.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertex(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertex(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertex(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertexRaw(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertexRaw(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertexRaw(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertexRaw(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. var capCount = 0; var rectCount = 0; for (var k = 0; k < 4; ++k) { if (childCap.Contains(children[j].GetVertex(k))) { ++capCount; } if (childRect.Contains(children[j].GetVertexRaw(k))) { ++rectCount; } } Assert.True(capCount <= 2); if (childRect.LatLo.Radians > -S2.PiOver2 && childRect.LatHi.Radians < S2.PiOver2) { // Bounding rectangles may be too large at the poles because the // pole itself has an arbitrary fixed longitude. Assert.True(rectCount <= 2); } } } // Check all children for the first few levels, and then sample randomly. // Also subdivide one corner cell, one edge cell, and one center cell // so that we have a better chance of sample the minimum metric values. var forceSubdivide = false; var center = S2Projections.GetNorm(children[i].Face); var edge = center + S2Projections.GetUAxis(children[i].Face); var corner = edge + S2Projections.GetVAxis(children[i].Face); for (var j = 0; j < 4; ++j) { var p = children[i].GetVertexRaw(j); if (p.Equals(center) || p.Equals(edge) || p.Equals(corner)) { forceSubdivide = true; } } if (forceSubdivide || cell.Level < (DEBUG_MODE ? 5 : 6) || random(DEBUG_MODE ? 10 : 4) == 0) { 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(exactArea / cell.ExactArea())) <= Math .Abs(Math.Log(1 + 1e-6))); Assert.True(Math.Abs(Math.Log(approxArea / cell.ApproxArea())) <= Math .Abs(Math.Log(1.03))); Assert.True(Math.Abs(Math.Log(averageArea / cell.AverageArea())) <= Math .Abs(Math.Log(1 + 1e-15))); }
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)))); }
public void testSubdivide(S2Cell cell) { gatherStats(cell); if (cell.IsLeaf) { return; } var children = new S2Cell[4]; for (var i = 0; i < children.Length; ++i) { children[i] = new S2Cell(); } Assert.True(cell.Subdivide(children)); var childId = cell.Id.ChildBegin; double exactArea = 0; double approxArea = 0; double averageArea = 0; for (var i = 0; i < 4; ++i, childId = childId.Next) { exactArea += children[i].ExactArea(); approxArea += children[i].ApproxArea(); averageArea += children[i].AverageArea(); // Check that the child geometry is consistent with its cell id. JavaAssert.Equal(children[i].Id, childId); Assert.True(children[i].Center.ApproxEquals(childId.ToPoint(), 1e-15)); var direct = new S2Cell(childId); JavaAssert.Equal(children[i].Face, direct.Face); JavaAssert.Equal(children[i].Level, direct.Level); JavaAssert.Equal(children[i].Orientation, direct.Orientation); JavaAssert.Equal(children[i].CenterRaw, direct.CenterRaw); for (var k = 0; k < 4; ++k) { JavaAssert.Equal(children[i].GetVertexRaw(k), direct.GetVertexRaw(k)); JavaAssert.Equal(children[i].GetEdgeRaw(k), direct.GetEdgeRaw(k)); } // Test Contains() and MayIntersect(). Assert.True(cell.Contains(children[i])); Assert.True(cell.MayIntersect(children[i])); Assert.True(!children[i].Contains(cell)); Assert.True(cell.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(cell.Contains(children[i].GetVertexRaw(j))); if (j != i) { Assert.True(!children[i].Contains(children[j].CenterRaw)); Assert.True(!children[i].MayIntersect(children[j])); } } // Test GetCapBound and GetRectBound. var parentCap = cell.CapBound; var parentRect = cell.RectBound; if (cell.Contains(new S2Point(0, 0, 1)) || cell.Contains(new S2Point(0, 0, -1))) { Assert.True(parentRect.Lng.IsFull); } var childCap = children[i].CapBound; var childRect = children[i].RectBound; Assert.True(childCap.Contains(children[i].Center)); Assert.True(childRect.Contains(children[i].CenterRaw)); Assert.True(parentCap.Contains(children[i].Center)); Assert.True(parentRect.Contains(children[i].CenterRaw)); for (var j = 0; j < 4; ++j) { Assert.True(childCap.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertex(j))); Assert.True(childRect.Contains(children[i].GetVertexRaw(j))); Assert.True(parentCap.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertex(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertex(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertex(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertex(j))); if (!parentRect.Contains(children[i].GetVertexRaw(j))) { Console.WriteLine("cell: " + cell + " i: " + i + " j: " + j); Console.WriteLine("Children " + i + ": " + children[i]); Console.WriteLine("Parent rect: " + parentRect); Console.WriteLine("Vertex raw(j) " + children[i].GetVertexRaw(j)); Console.WriteLine("Latlng of vertex: " + new S2LatLng(children[i].GetVertexRaw(j))); Console.WriteLine("RectBound: " + cell.RectBound); } Assert.True(parentRect.Contains(children[i].GetVertexRaw(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. var capCount = 0; var rectCount = 0; for (var k = 0; k < 4; ++k) { if (childCap.Contains(children[j].GetVertex(k))) { ++capCount; } if (childRect.Contains(children[j].GetVertexRaw(k))) { ++rectCount; } } Assert.True(capCount <= 2); if (childRect.LatLo.Radians > -S2.PiOver2 && childRect.LatHi.Radians < S2.PiOver2) { // Bounding rectangles may be too large at the poles because the // pole itself has an arbitrary fixed longitude. Assert.True(rectCount <= 2); } } } // Check all children for the first few levels, and then sample randomly. // Also subdivide one corner cell, one edge cell, and one center cell // so that we have a better chance of sample the minimum metric values. var forceSubdivide = false; var center = S2Projections.GetNorm(children[i].Face); var edge = center + S2Projections.GetUAxis(children[i].Face); var corner = edge + S2Projections.GetVAxis(children[i].Face); for (var j = 0; j < 4; ++j) { var p = children[i].GetVertexRaw(j); if (p.Equals(center) || p.Equals(edge) || p.Equals(corner)) { forceSubdivide = true; } } if (forceSubdivide || cell.Level < (DEBUG_MODE ? 5 : 6) || random(DEBUG_MODE ? 10 : 4) == 0) { 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(exactArea/cell.ExactArea())) <= Math .Abs(Math.Log(1 + 1e-6))); Assert.True(Math.Abs(Math.Log(approxArea/cell.ApproxArea())) <= Math .Abs(Math.Log(1.03))); Assert.True(Math.Abs(Math.Log(averageArea/cell.AverageArea())) <= Math .Abs(Math.Log(1 + 1e-15))); }