// Return the smallest cell that contains all descendants of this cell whose // bounds intersect "rect". For algorithms that use recursive subdivision // to find the cells that intersect a particular object, this method can be // used to skip all the initial subdivision steps where only one child needs // to be expanded. // // Note that this method is not the same as returning the smallest cell that // contains the intersection of this cell with "rect". Because of the // padding, even if one child completely contains "rect" it is still // possible that a neighboring child also intersects "rect". // // REQUIRES: bound().Intersects(rect) public S2CellId ShrinkToFit(R2Rect rect) { System.Diagnostics.Debug.Assert(Bound.Intersects(rect)); // Quick rejection test: if "rect" contains the center of this cell along // either axis, then no further shrinking is possible. int ij_size = S2CellId.SizeIJ(Level); if (Level == 0) { // Fast path (most calls to this function start with a face cell). if (rect[0].Contains(0) || rect[1].Contains(0)) { return(Id); } } else { if (rect[0].Contains(S2.STtoUV(S2.SiTitoST((uint)(2 * ij_lo_[0] + ij_size)))) || rect[1].Contains(S2.STtoUV(S2.SiTitoST((uint)(2 * ij_lo_[1] + ij_size))))) { return(Id); } } // Otherwise we expand "rect" by the given padding() on all sides and find // the range of coordinates that it spans along the i- and j-axes. We then // compute the highest bit position at which the min and max coordinates // differ. This corresponds to the first cell level at which at least two // children intersect "rect". // Increase the padding to compensate for the error in S2Coords.UVtoST(). // (The constant below is a provable upper bound on the additional error.) R2Rect padded = rect.Expanded(Padding + 1.5 * S2.DoubleEpsilon); var ij_min = new int[2]; // Min i- or j- coordinate spanned by "padded" var ij_xor = new int[2]; // XOR of the min and max i- or j-coordinates for (int d = 0; d < 2; ++d) { ij_min[d] = Math.Max(ij_lo_[d], S2.STtoIJ(S2.UVtoST(padded[d][0]))); int ij_max = Math.Min(ij_lo_[d] + ij_size - 1, S2.STtoIJ(S2.UVtoST(padded[d][1]))); ij_xor[d] = ij_min[d] ^ ij_max; } // Compute the highest bit position where the two i- or j-endpoints differ, // and then choose the cell level that includes both of these endpoints. So // if both pairs of endpoints are equal we choose kMaxLevel; if they differ // only at bit 0, we choose (kMaxLevel - 1), and so on. var level_msb = ((ij_xor[0] | ij_xor[1]) << 1) + 1; var level = S2.kMaxCellLevel - BitsUtils.FindMSBSetNonZero((uint)level_msb); if (level <= Level) { return(Id); } return(S2CellId.FromFaceIJ((int)Id.Face(), ij_min[0], ij_min[1]).Parent(level)); }
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_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); } }
public void Test_S2CellId_Neighbors() { // Check the edge neighbors of face 1. var out_faces = new[] { 5, 3, 2, 0 }; var face_nbrs = new S2CellId[4]; S2CellId.FromFace(1).EdgeNeighbors(face_nbrs); for (int i = 0; i < 4; ++i) { Assert.True(face_nbrs[i].IsFace()); Assert.Equal(out_faces[i], (int)face_nbrs[i].Face()); } // Check the edge neighbors of the corner cells at all levels. This case is // trickier because it requires projecting onto adjacent faces. const int kMaxIJ = S2CellId.kMaxSize - 1; for (int level = 1; level <= S2.kMaxCellLevel; ++level) { S2CellId id2 = S2CellId.FromFaceIJ(1, 0, 0).Parent(level); var nbrs2 = new S2CellId[4]; id2.EdgeNeighbors(nbrs2); // These neighbors were determined manually using the face and axis // relationships defined in s2coords.cc. int size_ij = S2CellId.SizeIJ(level); Assert.Equal(S2CellId.FromFaceIJ(5, kMaxIJ, kMaxIJ).Parent(level), nbrs2[0]); Assert.Equal(S2CellId.FromFaceIJ(1, size_ij, 0).Parent(level), nbrs2[1]); Assert.Equal(S2CellId.FromFaceIJ(1, 0, size_ij).Parent(level), nbrs2[2]); Assert.Equal(S2CellId.FromFaceIJ(0, kMaxIJ, 0).Parent(level), nbrs2[3]); } // Check the vertex neighbors of the center of face 2 at level 5. var nbrs = new List <S2CellId>(); new S2CellId(new S2Point(0, 0, 1)).AppendVertexNeighbors(5, nbrs); nbrs.Sort(); for (int i = 0; i < 4; ++i) { Assert.Equal(S2CellId.FromFaceIJ( 2, (1 << 29) - ((i < 2) ? 1 : 0), (1 << 29) - ((i == 0 || i == 3) ? 1 : 0)) .Parent(5), nbrs[i]); } nbrs.Clear(); // Check the vertex neighbors of the corner of faces 0, 4, and 5. S2CellId id1 = S2CellId.FromFacePosLevel(0, 0, S2.kMaxCellLevel); id1.AppendVertexNeighbors(0, nbrs); nbrs.Sort(); Assert.Equal(3, nbrs.Count); Assert.Equal(S2CellId.FromFace(0), nbrs[0]); Assert.Equal(S2CellId.FromFace(4), nbrs[1]); Assert.Equal(S2CellId.FromFace(5), nbrs[2]); // Check that AppendAllNeighbors produces results that are consistent // with AppendVertexNeighbors for a bunch of random cells. for (var i = 0; i < 1000; ++i) { S2CellId id2 = S2Testing.GetRandomCellId(); if (id2.IsLeaf()) { id2 = id2.Parent(); } // TestAllNeighbors computes approximately 2**(2*(diff+1)) cell ids, // so it's not reasonable to use large values of "diff". int max_diff = Math.Min(5, S2.kMaxCellLevel - id2.Level() - 1); int level = id2.Level() + S2Testing.Random.Uniform(max_diff + 1); TestAllNeighbors(id2, level); } }