private readonly int orientation_; // Hilbert curve orientation of this cell (see s2coords.h) #region Constructors // Construct an S2PaddedCell for the given cell id and padding. public S2PaddedCell(S2CellId id, double padding) { ij_lo_ = new int[2]; Id = id; Padding = padding; if (Id.IsFace()) { // Fast path for constructing a top-level face (the most common case). double limit = 1 + padding; Bound = new R2Rect(new R1Interval(-limit, limit), new R1Interval(-limit, limit)); middle_ = new R2Rect(new R1Interval(-padding, padding), new R1Interval(-padding, padding)); ij_lo_[0] = ij_lo_[1] = 0; orientation_ = (int)(Id.Face() & 1); Level = 0; } else { var ij = new int[2]; id.ToFaceIJOrientation(out ij[0], out ij[1], out orientation_, true); Level = id.Level(); Bound = S2CellId.IJLevelToBoundUV(ij, Level).Expanded(padding); int ij_size = S2CellId.SizeIJ(Level); ij_lo_[0] = ij[0] & -ij_size; ij_lo_[1] = ij[1] & -ij_size; } }
// Construct the child of "parent" with the given (i,j) index. The four // child cells have indices of (0,0), (0,1), (1,0), (1,1), where the i and j // indices correspond to increasing u- and v-values respectively. public S2PaddedCell(S2PaddedCell parent, int i, int j) { ij_lo_ = new int[2]; Padding = parent.Padding; Level = parent.Level + 1; // Compute the position and orientation of the child incrementally from the // orientation of the parent. int pos = S2.kIJtoPos[parent.orientation_][2 * i + j]; Id = parent.Id.Child(pos); int ij_size = S2CellId.SizeIJ(Level); ij_lo_[0] = parent.ij_lo_[0] + i * ij_size; ij_lo_[1] = parent.ij_lo_[1] + j * ij_size; orientation_ = parent.orientation_ ^ S2.kPosToOrientation[pos]; // For each child, one corner of the bound is taken directly from the parent // while the diagonally opposite corner is taken from middle(). var middle = parent.Middle; var x = new double[] { parent.Bound[0][0], parent.Bound[0][1] }; var y = new double[] { parent.Bound[1][0], parent.Bound[1][1] }; x[1 - i] = middle[0][1 - i]; y[1 - j] = middle[1][1 - j]; Bound = new R2Rect(new R1Interval(x), new R1Interval(y)); }
// Return the center of this cell. public S2Point GetCenter() { var ij_size = S2CellId.SizeIJ(Level); var si = (uint)(2 * ij_lo_[0] + ij_size); var ti = (uint)(2 * ij_lo_[1] + ij_size); return(S2.FaceSiTitoXYZ((int)Id.Face(), si, ti).Normalize()); }
// 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)); }
// Return the vertex where the S2 space-filling curve enters this cell. public S2Point GetEntryVertex() { // The curve enters at the (0,0) vertex unless the axis directions are // reversed, in which case it enters at the (1,1) vertex. uint i = (uint)ij_lo_[0]; uint j = (uint)ij_lo_[1]; if ((orientation_ & S2.kInvertMask) != 0) { int ij_size = S2CellId.SizeIJ(Level); i = (uint)(i + ij_size); j = (uint)(j + ij_size); } return(S2.FaceSiTitoXYZ((int)Id.Face(), 2 * i, 2 * j).Normalize()); }
// Return the vertex where the S2 space-filling curve exits this cell. public S2Point GetExitVertex() { // The curve exits at the (1,0) vertex unless the axes are swapped or // inverted but not both, in which case it exits at the (0,1) vertex. uint i = (uint)ij_lo_[0]; uint j = (uint)ij_lo_[1]; int ij_size = S2CellId.SizeIJ(Level); if (orientation_ == 0 || orientation_ == S2.kSwapMask + S2.kInvertMask) { i = (uint)(i + ij_size); } else { j = (uint)(j + ij_size); } return(S2.FaceSiTitoXYZ((int)Id.Face(), 2 * i, 2 * j).Normalize()); }
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); } }