public void Test_XYZToFaceSiTi() { // Check the conversion of random cells to center points and back. for (int level = 0; level <= S2.kMaxCellLevel; ++level) { for (int i = 0; i < 1000; ++i) { S2CellId id = S2Testing.GetRandomCellId(level); int actual_level = S2.XYZtoFaceSiTi(id.ToPoint(), out var face, out var si, out var ti); Assert.Equal(level, actual_level); S2CellId actual_id = S2CellId.FromFaceIJ(face, (int)(si / 2), (int)(ti / 2)).Parent(level); Assert.Equal(id, actual_id); // Now test a point near the cell center but not equal to it. S2Point p_moved = id.ToPoint() + new S2Point(1e-13, 1e-13, 1e-13); actual_level = S2.XYZtoFaceSiTi(p_moved, out var face_moved, out var si_moved, out var ti_moved); Assert.Equal(-1, actual_level); Assert.Equal(face, face_moved); Assert.Equal(si, si_moved); Assert.Equal(ti, ti_moved); // Finally, test some random (si,ti) values that may be at different // levels, or not at a valid level at all (for example, si == 0). int face_random = S2Testing.Random.Uniform(S2CellId.kNumFaces); uint si_random, ti_random; uint mask = ~0U << (S2.kMaxCellLevel - level); do { si_random = S2Testing.Random.Rand32() & mask; ti_random = S2Testing.Random.Rand32() & mask; } while (si_random > S2.kMaxSiTi || ti_random > S2.kMaxSiTi); S2Point p_random = S2.FaceSiTitoXYZ(face_random, si_random, ti_random); actual_level = S2.XYZtoFaceSiTi(p_random, out face, out si, out ti); if (face != face_random) { // The chosen point is on the edge of a top-level face cell. Assert.Equal(-1, actual_level); Assert.True(si == 0 || si == S2.kMaxSiTi || ti == 0 || ti == S2.kMaxSiTi); } else { Assert.Equal(si_random, si); Assert.Equal(ti_random, ti); if (actual_level >= 0) { Assert.Equal(p_random, S2CellId.FromFaceIJ(face, (int)(si / 2), (int)(ti / 2)).Parent(actual_level).ToPoint()); } } } } }
public void Test_GetReferencePoint_PartiallyDegenerateLoops() { for (var iter = 0; iter < 100; ++iter) { // First we construct a long convoluted edge chain that follows the // S2CellId Hilbert curve. At some random point along the curve, we // insert a small triangular loop. List <List <S2Point> > loops = new(1) { new() }; var loop = loops[0]; int num_vertices = 100; var start = S2Testing.GetRandomCellId(S2.kMaxCellLevel - 1); var end = start.AdvanceWrap(num_vertices); var loop_cellid = start.AdvanceWrap( S2Testing.Random.Uniform(num_vertices - 2) + 1); var triangle = new List <S2Point>(); for (var cellid = start; cellid != end; cellid = cellid.NextWrap()) { if (cellid == loop_cellid) { // Insert a small triangular loop. We save the loop so that we can // test whether it contains the origin later. triangle.Add(cellid.Child(0).ToPoint()); triangle.Add(cellid.Child(1).ToPoint()); triangle.Add(cellid.Child(2).ToPoint()); loop.AddRange(triangle); loop.Add(cellid.Child(0).ToPoint()); } else { loop.Add(cellid.ToPoint()); } } // Now we retrace our steps, except that we skip the three edges that form // the triangular loop above. for (S2CellId cellid = end; cellid != start; cellid = cellid.PrevWrap()) { if (cellid == loop_cellid) { loop.Add(cellid.Child(0).ToPoint()); } else { loop.Add(cellid.ToPoint()); } } S2LaxPolygonShape shape = new(loops); S2Loop triangle_loop = new(triangle); var rp = shape.GetReferencePoint(); Assert.Equal(triangle_loop.Contains(rp.Point), rp.Contained); } } }
public void Test_EncodedS2PointVectorTest_LastTwoPointsAtAllLevels() { // Test encoding the last two S2CellIds at each level. for (int level = 0; level <= S2.kMaxCellLevel; ++level) { _logger.WriteLine("Level = " + level); S2CellId id = S2CellId.End(level).Prev(); // Notice that this costs only 4 bits more than encoding the last S2CellId // by itself (see LastAtAllLevels). This is because encoding a block of // size 1 uses 8-bit deltas (which reduces the size of "base" by 4 bits), // while this test uses two 4-bit deltas. int expected_size = 6 + (level + 2) / 4; TestEncodedS2PointVector(new S2Point[] { id.ToPoint(), id.Prev().ToPoint() }, CodingHint.COMPACT, expected_size); } }
public void Test_S2ClosestCellQuery_TargetPointInsideIndexedCell() { // Tests a target point in the interior of an indexed cell. S2CellId cell_id = MakeCellIdOrDie("4/012"); S2CellIndex index = new(); index.Add(cell_id, 1); index.Build(); S2ClosestCellQuery query = new(index); S2ClosestCellQuery.PointTarget target = new(cell_id.ToPoint()); var result = query.FindClosestCell(target); Assert.Equal(S1ChordAngle.Zero, result.Distance); Assert.Equal(cell_id, result.CellId); Assert.Equal(1, result.Label); }
public void Test_EncodedS2PointVectorTest_ManyDuplicatePointsAtAllLevels() { // Test encoding 32 copies of the last S2CellId at each level. This uses // between 27 and 38 bytes depending on the level. (Note that the encoding // can use less than 1 byte per point in this situation.) for (int level = 0; level <= S2.kMaxCellLevel; ++level) { _logger.WriteLine("Level = " + level); S2CellId id = S2CellId.End(level).Prev(); // Encoding: header (2 bytes), base ((level + 2) / 4 bytes), block count // (1 byte), block offsets (2 bytes), block headers (2 bytes), 32 deltas // (16 bytes). At level 30 the encoding size goes up by 1 byte because // we can't encode an 8 byte "base" value, so instead this case uses a // base of 7 bytes plus a one-byte offset in each of the 2 blocks. int expected_size = 23 + (level + 2) / 4; if (level == 30) { expected_size += 1; } var points = new S2Point[32].Fill(id.ToPoint()); TestEncodedS2PointVector(points, CodingHint.COMPACT, expected_size); } }
public void Test_EncodedS2PointVectorTest_MaxFaceSiTiAtAllLevels() { // Similar to the test above, but tests encoding the S2CellId at each level // whose face, si, and ti values are all maximal. This turns out to be the // S2CellId whose human-readable form is 5/222...22 (0xb555555555555555), // however for clarity we construct it using S2CellId.FromFaceIJ. for (int level = 0; level <= S2.kMaxCellLevel; ++level) { _logger.WriteLine("Level = " + level); S2CellId id = S2CellId.FromFaceIJ(5, S2.kLimitIJ - 1, S2.kLimitIJ - 1) .Parent(level); // This encoding is one byte bigger than the previous test at levels 7, 11, // 15, 19, 23, and 27. This is because in the previous test, the // odd-numbered value bits are all zero (except for the face number), which // reduces the number of base bits needed by exactly 1. The encoding size // at level==3 is unaffected because for singleton blocks, the lowest 8 // value bits are encoded in the delta. int expected_size = (level < 4) ? 6 : 6 + (level + 1) / 4; TestEncodedS2PointVector(new S2Point[] { id.ToPoint() }, CodingHint.COMPACT, expected_size); } }
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)))); }