public void Test_S2CellUnion_Expand() { // This test generates coverings for caps of random sizes, expands // the coverings by a random radius, and then make sure that the new // covering covers the expanded cap. It also makes sure that the // new covering is not too much larger than expected. var coverer = new S2RegionCoverer(); for (var i = 0; i < 1000; ++i) { _logger.WriteLine($"Iteration {i}"); var cap = S2Testing.GetRandomCap( S2Cell.AverageArea(S2.kMaxCellLevel), S2.M_4_PI); // Expand the cap area by a random factor whose log is uniformly // distributed between 0 and log(1e2). var expanded_cap = S2Cap.FromCenterHeight( cap.Center, Math.Min(2.0, Math.Pow(1e2, rnd.RandDouble()) * cap.Height())); var radius = (expanded_cap.Radius - cap.Radius).Radians(); var max_level_diff = rnd.Uniform(8); // Generate a covering for the original cap, and measure the maximum // distance from the cap center to any point in the covering. coverer.Options_.MaxCells = 1 + rnd.Skewed(10); var covering = coverer.GetCovering(cap); S2Testing.CheckCovering(cap, covering, true); var covering_radius = GetRadius(covering, cap.Center); // This code duplicates the logic in Expand(min_radius, max_level_diff) // that figures out an appropriate cell level to use for the expansion. int min_level = S2.kMaxCellLevel; foreach (var id in covering) { min_level = Math.Min(min_level, id.Level()); } var expand_level = Math.Min(min_level + max_level_diff, S2.kMinWidth.GetLevelForMinValue(radius)); // Generate a covering for the expanded cap, and measure the new maximum // distance from the cap center to any point in the covering. covering.Expand(S1Angle.FromRadians(radius), max_level_diff); S2Testing.CheckCovering(expanded_cap, covering, false); double expanded_covering_radius = GetRadius(covering, cap.Center); // If the covering includes a tiny cell along the boundary, in theory the // maximum angle of the covering from the cap center can increase by up to // twice the maximum length of a cell diagonal. Assert.True(expanded_covering_radius - covering_radius <= 2 * S2.kMaxDiag.GetValue(expand_level)); } }
public void testRandomCaps() { Console.WriteLine("TestRandomCaps"); var kMaxLevel = S2CellId.MaxLevel; var coverer = new S2RegionCoverer(); for (var i = 0; i < 1000; ++i) { do { coverer.MinLevel = random(kMaxLevel + 1); coverer.MaxLevel = random(kMaxLevel + 1); } while (coverer.MinLevel > coverer.MaxLevel); coverer.MaxCells = skewed(10); coverer.LevelMod = 1 + random(3); var maxArea = Math.Min( 4 * S2.Pi, (3 * coverer.MaxCells + 1) * S2Cell.AverageArea(coverer.MinLevel)); var cap = getRandomCap(0.1 * S2Cell.AverageArea(kMaxLevel), maxArea); var covering = new List <S2CellId>(); var interior = new List <S2CellId>(); coverer.GetCovering(cap, covering); checkCovering(coverer, cap, covering, false); coverer.GetInteriorCovering(cap, interior); checkCovering(coverer, cap, interior, true); // Check that GetCovering is deterministic. var covering2 = new List <S2CellId>(); coverer.GetCovering(cap, covering2); assertTrue(covering.SequenceEqual(covering2)); // Also check S2CellUnion.denormalize(). The denormalized covering // may still be different and smaller than "covering" because // S2RegionCoverer does not guarantee that it will not output all four // children of the same parent. var cells = new S2CellUnion(); cells.InitFromCellIds(covering); var denormalized = new List <S2CellId>(); cells.Denormalize(coverer.MinLevel, coverer.LevelMod, denormalized); checkCovering(coverer, cap, denormalized, false); } }
public void testSimpleCoverings() { Console.WriteLine("TestSimpleCoverings"); var kMaxLevel = S2CellId.MaxLevel; var coverer = new S2RegionCoverer(); coverer.MaxCells = int.MaxValue; for (var i = 0; i < 1000; ++i) { var level = random(kMaxLevel + 1); coverer.MinLevel = level; coverer.MaxLevel = level; var maxArea = Math.Min(4 * S2.Pi, 1000 * S2Cell.AverageArea(level)); var cap = getRandomCap(0.1 * S2Cell.AverageArea(kMaxLevel), maxArea); var covering = new List <S2CellId>(); S2RegionCoverer.GetSimpleCovering(cap, cap.Axis, level, covering); checkCovering(coverer, cap, covering, false); } }
public void Test_S2RegionCoverer_SimpleCoverings() { Assert.True(false); //TODO const int kMaxLevel = S2.kMaxCellLevel; var options = new S2RegionCoverer.Options { MaxCells = Int32.MaxValue }; for (int i = 0; i < 1000; ++i) { int level = S2Testing.Random.Uniform(kMaxLevel + 1); options.MinLevel = (level); options.MaxLevel = (level); double max_area = Math.Min(S2.M_4_PI, 1000 * S2Cell.AverageArea(level)); S2Cap cap = S2Testing.GetRandomCap(0.1 * S2Cell.AverageArea(kMaxLevel), max_area); var covering = new List <S2CellId>(); S2RegionCoverer.GetSimpleCovering(cap, cap.Center, level, covering); CheckCovering(options, cap, covering, false); } }
public void Test_S2RegionCoverer_RandomCaps() { const int kMaxLevel = S2.kMaxCellLevel; S2RegionCoverer.Options options = new(); for (int i = 0; i < 1000; ++i) { do { options.MinLevel = (S2Testing.Random.Uniform(kMaxLevel + 1)); options.MaxLevel = (S2Testing.Random.Uniform(kMaxLevel + 1)); } while (options.MinLevel > options.MaxLevel); options.MaxCells = S2Testing.Random.Skewed(10); options.LevelMod = (1 + S2Testing.Random.Uniform(3)); double max_area = Math.Min(S2.M_4_PI, (3 * options.MaxCells + 1) * S2Cell.AverageArea(options.MinLevel)); S2Cap cap = S2Testing.GetRandomCap(0.1 * S2Cell.AverageArea(kMaxLevel), max_area); S2RegionCoverer coverer = new(options); coverer.GetCovering(cap, out var covering); CheckCovering(options, cap, covering, false); coverer.GetInteriorCovering(cap, out var interior); CheckCovering(options, cap, interior, true); // Check that GetCovering is deterministic. coverer.GetCovering(cap, out var covering2); Assert.Equal(covering, covering2); // Also check S2CellUnion.Denormalize(). The denormalized covering // may still be different and smaller than "covering" because // S2RegionCoverer does not guarantee that it will not output all four // children of the same parent. S2CellUnion cells = new(covering); var denormalized = new List <S2CellId>(); cells.Denormalize(options.MinLevel, options.LevelMod, denormalized); CheckCovering(options, cap, denormalized, false); } }
private void TestRandomCaps(S2RegionTermIndexer.Options options, QueryType query_type) { // This function creates an index consisting either of points (if // options.index_contains_points_only() is true) or S2Caps of random size. // It then executes queries consisting of points (if query_type == POINT) // or S2Caps of random size (if query_type == CAP). var indexer = new S2RegionTermIndexer(options); var coverer = new S2RegionCoverer(options); var caps = new List <S2Cap>(); var coverings = new List <S2CellUnion>(); var index = new Dictionary <string, List <int> >(); int index_terms = 0, query_terms = 0; for (int i = 0; i < iters; ++i) { // Choose the region to be indexed: either a single point or a cap // of random size (up to a full sphere). S2Cap cap; List <string> terms; if (options.IndexContainsPointsOnly) { cap = S2Cap.FromPoint(S2Testing.RandomPoint()); terms = indexer.GetIndexTerms(cap.Center, ""); } else { cap = S2Testing.GetRandomCap( 0.3 * S2Cell.AverageArea(options.MaxLevel), 4.0 * S2Cell.AverageArea(options.MinLevel)); terms = indexer.GetIndexTerms(cap, ""); } caps.Add(cap); coverings.Add(coverer.GetCovering(cap)); foreach (var term in terms) { if (!index.ContainsKey(term)) { index.Add(term, new List <int>()); } index[term].Add(i); } index_terms += terms.Count; } for (int i = 0; i < iters; ++i) { // Choose the region to be queried: either a random point or a cap of // random size. S2Cap cap; List <string> terms; if (query_type == QueryType.CAP) { cap = S2Cap.FromPoint(S2Testing.RandomPoint()); terms = indexer.GetQueryTerms(cap.Center, ""); } else { cap = S2Testing.GetRandomCap( 0.3 * S2Cell.AverageArea(options.MaxLevel), 4.0 * S2Cell.AverageArea(options.MinLevel)); terms = indexer.GetQueryTerms(cap, ""); } // Compute the expected results of the S2Cell query by brute force. S2CellUnion covering = coverer.GetCovering(cap); var expected = new List <int>(); var actual = new List <int>(); for (int j = 0; j < caps.Count; ++j) { if (covering.Intersects(coverings[j])) { expected.Add(j); } } foreach (var term in terms) { actual.AddRange(index[term]); } Assert.Equal(expected, actual); query_terms += terms.Count; } _logger.WriteLine($"Index terms/doc: {((double)index_terms) / iters:2f}, Query terms/doc: {((double)query_terms) / iters:2f}"); }
public void testSubdivide() { for (var face = 0; face < 6; ++face) { testSubdivide(S2Cell.FromFacePosLevel(face, (byte)0, 0)); } // The maximum edge *ratio* is the ratio of the longest edge of any cell to // the shortest edge of any cell at the same level (and similarly for the // maximum diagonal ratio). // // The maximum edge *aspect* is the maximum ratio of the longest edge of a // cell to the shortest edge of that same cell (and similarly for the // maximum diagonal aspect). Console .WriteLine("Level Area Edge Diag Approx Average\n"); Console .WriteLine(" Ratio Ratio Aspect Ratio Aspect Min Max Min Max\n"); for (var i = 0; i <= S2CellId.MaxLevel; ++i) { var s = levelStats[i]; if (s.count > 0) { s.avgArea /= s.count; s.avgWidth /= s.count; s.avgEdge /= s.count; s.avgDiag /= s.count; s.avgAngleSpan /= s.count; } Console.WriteLine( "%5d %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", i, s.maxArea / s.minArea, s.maxEdge / s.minEdge, s.maxEdgeAspect, s.maxDiag / s.minDiag, s.maxDiagAspect, s.minApproxRatio, s.maxApproxRatio, S2Cell.AverageArea(i) / s.maxArea, S2Cell .AverageArea(i) / s.minArea); } // Now check the validity of the S2 length and area metrics. for (var i = 0; i <= S2CellId.MaxLevel; ++i) { var s = levelStats[i]; if (s.count == 0) { continue; } Console.WriteLine( "Level {0} - metric (error/actual : error/tolerance)\n", i); // The various length calculations are only accurate to 1e-15 or so, // so we need to allow for this amount of discrepancy with the theoretical // minimums and maximums. The area calculation is accurate to about 1e-15 // times the cell width. testMinMaxAvg("area", i, s.count, 1e-15 * s.minWidth, s.minArea, s.maxArea, s.avgArea, S2Projections.MinArea, S2Projections.MaxArea, S2Projections.AvgArea); testMinMaxAvg("width", i, s.count, 1e-15, s.minWidth, s.maxWidth, s.avgWidth, S2Projections.MinWidth, S2Projections.MaxWidth, S2Projections.AvgWidth); testMinMaxAvg("edge", i, s.count, 1e-15, s.minEdge, s.maxEdge, s.avgEdge, S2Projections.MinEdge, S2Projections.MaxEdge, S2Projections.AvgEdge); testMinMaxAvg("diagonal", i, s.count, 1e-15, s.minDiag, s.maxDiag, s.avgDiag, S2Projections.MinDiag, S2Projections.MaxDiag, S2Projections.AvgDiag); testMinMaxAvg("angle span", i, s.count, 1e-15, s.minAngleSpan, s.maxAngleSpan, s.avgAngleSpan, S2Projections.MinAngleSpan, S2Projections.MaxAngleSpan, S2Projections.AvgAngleSpan); // The aspect ratio calculations are ratios of lengths and are therefore // less accurate at higher subdivision levels. Assert.True(s.maxEdgeAspect <= S2Projections.MaxEdgeAspect + 1e-15 * (1 << i)); Assert.True(s.maxDiagAspect <= S2Projections.MaxDiagAspect + 1e-15 * (1 << i)); } }
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))); }
public void Test_S2Cell_TestSubdivide() { // Only test a sample of faces to reduce the runtime. TestSubdivide(S2Cell.FromFace(0)); TestSubdivide(S2Cell.FromFace(3)); TestSubdivide(S2Cell.FromFace(5)); #region Print Header // This table is useful in evaluating the quality of the various S2 // projections. // // The maximum edge *ratio* is the ratio of the longest edge of any cell to // the shortest edge of any cell at the same level (and similarly for the // maximum diagonal ratio). // // The maximum edge *aspect* is the maximum ratio of the longest edge of a // cell to the shortest edge of that same cell (and similarly for the // maximum diagonal aspect). _logger.WriteLine( @"Ratio: (Max value for any cell) / (Min value for any cell) Aspect: (Max value / min value) for any cell Edge Diag Approx Area/ Avg Area/ Area Length Length Exact Area Exact Area Level Ratio Ratio Aspect Ratio Aspect Min Max Min Max --------------------------------------------------------------------"); #endregion for (int i = 0; i <= S2.kMaxCellLevel; ++i) { var s = level_stats[i]; if (s.Count > 0) { s.Avg_area /= s.Count; s.Avg_width /= s.Count; s.Avg_edge /= s.Count; s.Avg_diag /= s.Count; s.Avg_angle_span /= s.Count; } _logger.WriteLine($"{i:d5} {s.Max_area / s.Min_area:f6.3} {s.Max_edge / s.Min_edge:f6.3} {s.Max_edge_aspect:f6.3} {s.Max_diag / s.Min_diag:f6.3} {s.Max_diag_aspect:f6.3} {s.Min_approx_ratio:f6.3} {s.Max_approx_ratio:f6.3} {S2Cell.AverageArea(i) / s.Max_area:f6.3} {S2Cell.AverageArea(i) / s.Min_area:f6.3}"); } // Now check the validity of the S2 length and area metrics. for (int i = 0; i <= S2.kMaxCellLevel; ++i) { var s = level_stats[i]; if (s.Count == 0) { continue; } _logger.WriteLine($"Level {i:2d} - metric value (error/actual : error/tolerance)"); // The various length calculations are only accurate to 1e-15 or so, // so we need to allow for this amount of discrepancy with the theoretical // minimums and maximums. The area calculation is accurate to about 1e-15 // times the cell width. CheckMinMaxAvg("area", i, s.Count, 1e-15 * s.Min_width, s.Min_area, s.Max_area, s.Avg_area, S2.kMinArea, S2.kMaxArea, S2.kAvgArea); CheckMinMaxAvg("width", i, s.Count, 1e-15, s.Min_width, s.Max_width, s.Avg_width, S2.kMinWidth, S2.kMaxWidth, S2.kAvgWidth); CheckMinMaxAvg("edge", i, s.Count, 1e-15, s.Min_edge, s.Max_edge, s.Avg_edge, S2.kMinEdge, S2.kMaxEdge, S2.kAvgEdge); CheckMinMaxAvg("diagonal", i, s.Count, 1e-15, s.Min_diag, s.Max_diag, s.Avg_diag, S2.kMinDiag, S2.kMaxDiag, S2.kAvgDiag); CheckMinMaxAvg("angle span", i, s.Count, 1e-15, s.Min_angle_span, s.Max_angle_span, s.Avg_angle_span, S2.kMinAngleSpan, S2.kMaxAngleSpan, S2.kAvgAngleSpan); // The aspect ratio calculations are ratios of lengths and are therefore // less accurate at higher subdivision levels. Assert.True(s.Max_edge_aspect <= S2.kMaxEdgeAspect + 1e-15 * (1 << i)); Assert.True(s.Max_diag_aspect <= S2.kMaxDiagAspect + 1e-15 * (1 << i)); } }
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))); }