/** * Returns the smallest cell containing both points, or Sentinel if they are * not all on the same face. The points don't need to be normalized. */ private static S2CellId ContainingCell(S2Point pa, S2Point pb) { var a = S2CellId.FromPoint(pa); var b = S2CellId.FromPoint(pb); if (a.Face != b.Face) { return(S2CellId.Sentinel); } while (!a.Equals(b)) { a = a.Parent; b = b.Parent; } return(a); }
/** * Returns the smallest cell containing all four points, or * {@link S2CellId#sentinel()} if they are not all on the same face. The * points don't need to be normalized. */ private static S2CellId ContainingCell(S2Point pa, S2Point pb, S2Point pc, S2Point pd) { var a = S2CellId.FromPoint(pa); var b = S2CellId.FromPoint(pb); var c = S2CellId.FromPoint(pc); var d = S2CellId.FromPoint(pd); if (a.Face != b.Face || a.Face != c.Face || a.Face != d.Face) { return(S2CellId.Sentinel); } while (!a.Equals(b) || !a.Equals(c) || !a.Equals(d)) { a = a.Parent; b = b.Parent; c = c.Parent; d = d.Parent; } return(a); }
/** Computes a set of initial candidates that cover the given region. */ private void GetInitialCandidates() { // Optimization: if at least 4 cells are desired (the normal case), // start with a 4-cell covering of the region's bounding cap. This // lets us skip quite a few levels of refinement when the region to // be covered is relatively small. if (_maxCells >= 4) { // Find the maximum level such that the bounding cap contains at most one // cell vertex at that level. var cap = _region.CapBound; var level = Math.Min(S2Projections.MinWidth.GetMaxLevel(2 * cap.Angle.Radians), Math.Min(MaxLevel, S2CellId.MaxLevel - 1)); if (LevelMod > 1 && level > MinLevel) { level -= (level - MinLevel) % LevelMod; } // We don't bother trying to optimize the level == 0 case, since more than // four face cells may be required. if (level > 0) { // Find the leaf cell containing the cap axis, and determine which // subcell of the parent cell contains it. var @base = new List <S2CellId>(4); var id = S2CellId.FromPoint(cap.Axis); id.GetVertexNeighbors(level, @base); for (var i = 0; i < @base.Count; ++i) { AddCandidate(NewCandidate(new S2Cell(@base[i]))); } return; } } // Default: start with all six cube faces. for (var face = 0; face < 6; ++face) { AddCandidate(NewCandidate(FaceCells[face])); } }
// This is a static method in order to provide named parameters. // Convenience methods. public S2Cell(S2Point p) { Init(S2CellId.FromPoint(p)); }
/** * Computes a cell covering of an edge. Clears edgeCovering and returns the * level of the s2 cells used in the covering (only one level is ever used for * each call). * * If thickenEdge is true, the edge is thickened and extended by 1% of its * length. * * It is guaranteed that no child of a covering cell will fully contain the * covered edge. */ private int GetCovering( S2Point a, S2Point b, bool thickenEdge, List <S2CellId> edgeCovering) { edgeCovering.Clear(); // Selects the ideal s2 level at which to cover the edge, this will be the // level whose S2 cells have a width roughly commensurate to the length of // the edge. We multiply the edge length by 2*THICKENING to guarantee the // thickening is honored (it's not a big deal if we honor it when we don't // request it) when doing the covering-by-cap trick. var edgeLength = a.Angle(b); var idealLevel = S2Projections.MinWidth.GetMaxLevel(edgeLength * (1 + 2 * Thickening)); S2CellId containingCellId; if (!thickenEdge) { containingCellId = ContainingCell(a, b); } else { if (idealLevel == S2CellId.MaxLevel) { // If the edge is tiny, instabilities are more likely, so we // want to limit the number of operations. // We pretend we are in a cell much larger so as to trigger the // 'needs covering' case, so we won't try to thicken the edge. containingCellId = (new S2CellId(0xFFF0)).ParentForLevel(3); } else { var pq = (b - a) * Thickening; var ortho = (S2Point.Normalize(S2Point.CrossProd(pq, a))) * edgeLength * Thickening; var p = a - pq; var q = b + pq; // If p and q were antipodal, the edge wouldn't be lengthened, // and it could even flip! This is not a problem because // idealLevel != 0 here. The farther p and q can be is roughly // a quarter Earth away from each other, so we remain // Theta(THICKENING). containingCellId = ContainingCell(p - ortho, p + ortho, q - ortho, q + ortho); } } // Best case: edge is fully contained in a cell that's not too big. if (!containingCellId.Equals(S2CellId.Sentinel) && containingCellId.Level >= idealLevel - 2) { edgeCovering.Add(containingCellId); return(containingCellId.Level); } if (idealLevel == 0) { // Edge is very long, maybe even longer than a face width, so the // trick below doesn't work. For now, we will add the whole S2 sphere. // TODO(user): Do something a tad smarter (and beware of the // antipodal case). for (var cellid = S2CellId.Begin(0); !cellid.Equals(S2CellId.End(0)); cellid = cellid.Next) { edgeCovering.Add(cellid); } return(0); } // TODO(user): Check trick below works even when vertex is at // interface // between three faces. // Use trick as in S2PolygonBuilder.PointIndex.findNearbyPoint: // Cover the edge by a cap centered at the edge midpoint, then cover // the cap by four big-enough cells around the cell vertex closest to the // cap center. var middle = S2Point.Normalize((a + b) / 2); var actualLevel = Math.Min(idealLevel, S2CellId.MaxLevel - 1); S2CellId.FromPoint(middle).GetVertexNeighbors(actualLevel, edgeCovering); return(actualLevel); }
/** * Given a connected region and a starting point, return a set of cells at the * given level that cover the region. */ public static void GetSimpleCovering( IS2Region region, S2Point start, int level, List <S2CellId> output) { FloodFill(region, S2CellId.FromPoint(start).ParentForLevel(level), output); }