public void Test_S2Cell_GetMaxDistanceToEdge() { // Test an edge for which its antipode crosses the cell. Validates both the // standard and brute force implementations for this case. S2Cell cell = S2Cell.FromFacePosLevel(0, 0, 20); S2Point a = -S2.Interpolate(2.0, cell.Center(), cell.Vertex(0)); S2Point b = -S2.Interpolate(2.0, cell.Center(), cell.Vertex(2)); S1ChordAngle actual = cell.MaxDistance(a, b); S1ChordAngle expected = GetMaxDistanceToEdgeBruteForce(cell, a, b); Assert2.Near(expected.Radians(), S1ChordAngle.Straight.Radians(), S2.DoubleError); Assert2.Near(actual.Radians(), S1ChordAngle.Straight.Radians(), S2.DoubleError); }
private static void CompareS2CellToPadded(S2Cell cell, S2PaddedCell pcell, double padding) { Assert.Equal(cell.Id, pcell.Id); Assert.Equal(cell.Level, pcell.Level); Assert.Equal(padding, pcell.Padding); Assert.Equal(cell.BoundUV.Expanded(padding), pcell.Bound); var center_uv = cell.Id.CenterUV(); Assert.Equal(R2Rect.FromPoint(center_uv).Expanded(padding), pcell.Middle); Assert.Equal(cell.Center(), pcell.GetCenter()); }
// Returns true if any shape intersects "target". // // The implementation is conservative but not exact; if a shape is just // barely disjoint from the given cell then it may return true. The maximum // error is less than 10 * S2Constants.DoubleEpsilon radians (or about 15 nanometers). public bool MayIntersect(S2Cell target) { var(relation, pos) = Index().LocateCell(target.Id); // If "target" does not overlap any index cell, there is no intersection. if (relation == S2ShapeIndex.CellRelation.DISJOINT) { return(false); } // If "target" is subdivided into one or more index cells, then there is an // intersection to within the S2ShapeIndex error bound. if (relation == S2ShapeIndex.CellRelation.SUBDIVIDED) { return(true); } // Otherwise, the iterator points to an index cell containing "target". // // If "target" is an index cell itself, there is an intersection because index // cells are created only if they have at least one edge or they are // entirely contained by the loop. var icell = Index().GetIndexCell(pos); System.Diagnostics.Debug.Assert(icell.Value.Item1.Contains(target.Id)); if (icell.Value.Item1 == target.Id) { return(true); } // Test whether any shape intersects the target cell or contains its center. var cell = icell.Value.Item2; for (int s = 0; s < cell.NumClipped(); ++s) { var clipped = cell.Clipped(s); if (AnyEdgeIntersects(clipped, target)) { return(true); } if (Contains(icell.Value.Item1, clipped, target.Center())) { return(true); } } return(false); }
// Returns true if "target" is contained by any single shape. If the cell // is covered by a union of different shapes then it may return false. // // The implementation is conservative but not exact; if a shape just barely // contains the given cell then it may return false. The maximum error is // less than 10 * S2Constants.DoubleEpsilon radians (or about 15 nanometers). public bool Contains(S2Cell target) { var(relation, pos) = Index().LocateCell(target.Id); // If the relation is DISJOINT, then "target" is not contained. Similarly if // the relation is SUBDIVIDED then "target" is not contained, since index // cells are subdivided only if they (nearly) intersect too many edges. if (relation != S2ShapeIndex.CellRelation.INDEXED) { return(false); } // Otherwise, the iterator points to an index cell containing "target". // If any shape contains the target cell, we return true. var icell = Index().GetIndexCell(pos); System.Diagnostics.Debug.Assert(icell.Value.Item1.Contains(target.Id)); var cell = icell.Value.Item2; for (int s = 0; s < cell.NumClipped(); ++s) { var clipped = cell.Clipped(s); // The shape contains the target cell iff the shape contains the cell // center and none of its edges intersects the (padded) cell interior. if (icell.Value.Item1 == target.Id) { if (clipped.NumEdges == 0 && clipped.ContainsCenter) { return(true); } } else { // It is faster to call AnyEdgeIntersects() before Contains(). if (Index().Shape(clipped.ShapeId).Dimension() == 2 && !AnyEdgeIntersects(clipped, target) && Contains(icell.Value.Item1, clipped, target.Center())) { return(true); } } } return(false); }
// The implementation is approximate but conservative; it always returns // "false" if the cell is not contained by the buffered region, but it may // also return false in some cases where "cell" is in fact contained. public bool Contains(S2Cell cell) { // Return true if the buffered region is guaranteed to cover whole globe. if (radius_successor_ > S1ChordAngle.Straight) { return(true); } // To implement this method perfectly would require computing the directed // Hausdorff distance, which is expensive (and not currently implemented). // However the following heuristic is almost as good in practice and much // cheaper to compute. // Return true if the unbuffered region contains this cell. if (Index().MakeS2ShapeIndexRegion().Contains(cell)) { return(true); } // Otherwise approximate the cell by its bounding cap. // // NOTE(ericv): It would be slightly more accurate to first find the closest // point in the indexed geometry to the cell, and then measure the actual // maximum distance from that point to the cell (a poor man's Hausdorff // distance). But based on actual tests this is not worthwhile. S2Cap cap = cell.GetCapBound(); if (Radius < cap.Radius) { return(false); } // Return true if the distance to the cell center plus the radius of the // cell's bounding cap is less than or equal to "radius_". var target = new S2ClosestEdgeQuery.PointTarget(cell.Center()); return(query_.IsDistanceLess(target, radius_successor_ - cap.Radius)); }
// Visits all shapes that intersect "target", terminating early if the // "visitor" return false (in which case VisitIntersectingShapes returns // false as well). Each shape is visited at most once. // // This method can also be used to visit all shapes that fully contain // "target" (VisitContainingShapes) by simply having the ShapeVisitor // function immediately return true when "contains_target" is false. public bool VisitIntersectingShapes(S2Cell target, ShapeVisitor visitor) { var(cellRelation, pos) = Index().LocateCell(target.Id); S2ShapeIndex.Enumerator iter_ = new(Index()); iter_.SetPosition(pos); switch (cellRelation) { case S2ShapeIndex.CellRelation.DISJOINT: return(true); case S2ShapeIndex.CellRelation.SUBDIVIDED: { // A shape contains the target cell iff it appears in at least one cell, // it contains the center of all cells, and it has no edges in any cell. // It is easier to keep track of whether a shape does *not* contain the // target cell because boolean values default to false. Dictionary <int, bool> shape_not_contains = new(); for (var max = target.Id.RangeMax(); !iter_.Done() && iter_.Id <= max; iter_.MoveNext()) { var cell = iter_.Cell; for (int s = 0; s < cell.NumClipped(); ++s) { var clipped = cell.Clipped(s); shape_not_contains[clipped.ShapeId] |= clipped.NumEdges > 0 || !clipped.ContainsCenter; } } foreach (var(shape_id, not_contains) in shape_not_contains) { if (!visitor(Index().Shape(shape_id), !not_contains)) { return(false); } } return(true); } case S2ShapeIndex.CellRelation.INDEXED: { var cell = iter_.Cell; for (int s = 0; s < cell.NumClipped(); ++s) { // The shape contains the target cell iff the shape contains the cell // center and none of its edges intersects the (padded) cell interior. var clipped = cell.Clipped(s); bool contains = false; if (iter_.Id == target.Id) { contains = clipped.NumEdges == 0 && clipped.ContainsCenter; } else { if (!AnyEdgeIntersects(clipped, target)) { if (!Contains(iter_.Id, clipped, target.Center())) { continue; // Disjoint. } contains = true; } } if (!visitor(Index().Shape(clipped.ShapeId), contains)) { return(false); } } return(true); } } throw new Exception("(FATAL) Unhandled S2ShapeIndex::CellRelation"); }