// Constructs a region representing all points within the given radius of // any point in the given S2ShapeIndex. public S2ShapeIndexBufferedRegion(S2ShapeIndex index, S1ChordAngle radius) { Radius = radius; radius_successor_ = radius.Successor(); query_ = new S2ClosestEdgeQuery(index); query_.Options_.IncludeInteriors = (true); }
public EdgeEnumerator(S2ShapeIndex index) { index_ = index; shape_id_ = -1; num_edges_ = 0; edge_id_ = -1; }
// Equivalent to the constructor above. public void Init(S2ShapeIndex index, S1ChordAngle radius) { Radius = radius; radius_successor_ = radius.Successor(); query_.Init(index); query_.Options_.IncludeInteriors = (true); }
/// <summary> /// Returns a vector containing all edges in the given S2ShapeIndexCell vector. /// (The result is returned as an output parameter so that the same storage can /// be reused, rather than allocating a new temporary vector each time.) /// </summary> private static void GetShapeEdges(S2ShapeIndex index, List <S2ShapeIndexCell> cells, ShapeEdgeVector shape_edges) { shape_edges.Clear(); foreach (var cell in cells) { AppendShapeEdges(index, cell, shape_edges); } }
/// <summary> /// If "swapped" is true, the loops A and B have been swapped. This affects /// how arguments are passed to the given loop relation, since for example /// A.Contains(B) is not the same as B.Contains(A). /// </summary> public IndexCrosser(S2ShapeIndex a_index, S2ShapeIndex b_index, CrossingType type, EdgePairVisitor visitor, bool swapped) { a_index_ = a_index; b_index_ = b_index; visitor_ = visitor; min_crossing_sign_ = type == CrossingType.INTERIOR ? 1 : 0; swapped_ = swapped; b_query_ = new S2CrossingEdgeQuery(b_index_); }
// Convert the contents of an S2ShapeIndex to the format above. The index may // contain S2Shapes of any type. Shapes are reordered if necessary so that // all point geometry (shapes of dimension 0) are first, followed by all // polyline geometry, followed by all polygon geometry. public static string ToDebugString(this S2ShapeIndex index) { StringBuilder sb = new(); for (var dim = 0; dim < 3; ++dim) { if (dim > 0) { sb.Append('#'); } var count = 0; foreach (var shape in index) { if (shape == null || shape.Dimension() != dim) { continue; } sb.Append((count > 0) ? " | " : (dim > 0) ? " " : ""); for (var i = 0; i < shape.NumChains(); ++i, ++count) { if (i > 0) { sb.Append((dim == 2) ? "; " : " | "); } var chain = shape.GetChain(i); if (chain.Length == 0) { System.Diagnostics.Debug.Assert(dim == 2); sb.Append("full"); } else { sb.Append(AppendVertex(shape.GetEdge(chain.Start).V0)); } var limit = chain.Start + chain.Length; if (dim != 1) { --limit; } for (var e = chain.Start; e < limit; ++e) { sb.Append(", "); sb.Append(AppendVertex(shape.GetEdge(e).V1)); } } } // Example output: "# #", "0:0 # #", "# # 0:0, 0:1, 1:0" if (dim == 1 || (dim == 0 && count > 0)) { sb.Append(' '); } } return(sb.ToString()); }
private int[] GetContainingShapes(S2MinDistanceTarget target, S2ShapeIndex index, int max_shapes) { var shape_ids = new SortedSet <Int32>(); target.VisitContainingShapes( index, (S2Shape containing_shape, S2Point target_point) => { shape_ids.Add(containing_shape.Id); return(shape_ids.Count < max_shapes); }); return(shape_ids.ToArray()); }
/// <summary> /// Appends all edges in the given S2ShapeIndexCell to the given vector. /// </summary> private static void AppendShapeEdges(S2ShapeIndex index, S2ShapeIndexCell cell, ShapeEdgeVector shape_edges) { for (int s = 0; s < cell.NumClipped(); ++s) { var clipped = cell.Clipped(s); var shape = index.Shape(clipped.ShapeId); var num_edges = clipped.NumEdges; for (int i = 0; i < num_edges; i++) { shape_edges.Add(new ShapeEdge(shape, clipped.Edge(i))); } } }
// Returns the maximum dimension of any shape in the index. Returns -1 if the // index does not contain any shapes. // // Note that the dimension does *not* depend on whether the shapes in the // index contain any points; for example, the dimension of an empty point set // is 0, and the dimension of an empty polygon is 2. public static int GetDimension(S2ShapeIndex index) { int dim = -1; for (int i = 0; i < index.NumShapeIds(); ++i) { var shape = index.Shape(i); if (shape != null) { dim = Math.Max(dim, shape.Dimension()); } } return(dim); }
// Returns the number of points (objects of dimension zero) in the index. // Note that polyline and polygon vertices are *not* included in this count. public static int GetNumPoints(S2ShapeIndex index) { int count = 0; for (int i = 0; i < index.NumShapeIds(); ++i) { var shape = index.Shape(i); if (shape != null && shape.Dimension() == 0) { count += shape.NumEdges(); } } return(count); }
// Returns the total length of all polylines in the index. Returns zero if no // polylines are present. // // All edges are modeled as spherical geodesics. The result can be converted // to a distance on the Earth's surface (with a worst-case error of 0.562% // near the equator) using the functions in s2earth.h. public static S1Angle GetLength(S2ShapeIndex index) { var length = S1Angle.Zero; for (int i = 0; i < index.NumShapeIds(); ++i) { var shape = index.Shape(i); if (shape != null) { length += S2.GetLength(shape); } } return(length); }
// Returns the total perimeter of all polygons in the index (including both // "shells" and "holes"). Returns zero if no polygons are present. // // All edges are modeled as spherical geodesics. The result can be converted // to a distance on the Earth's surface (with a worst-case error of 0.562% // near the equator) using the functions in s2earth.h. public static S1Angle GetPerimeter(S2ShapeIndex index) { var perimeter = S1Angle.Zero; for (int i = 0; i < index.NumShapeIds(); ++i) { var shape = index.Shape(i); if (shape != null) { perimeter += S2.GetPerimeter(shape); } } return(perimeter); }
// Returns the total area of all polygons in the index. Returns zero if no // polygons are present. This method has good relative accuracy for both very // large and very small regions. Note that the result may exceed 4*Pi if the // index contains overlapping polygons. // // All edges are modeled as spherical geodesics. The result can be converted // to an area on the Earth's surface (with a worst-case error of 0.900% near // the poles) using the functions in s2earth.h. public static double GetArea(S2ShapeIndex index) { double area = 0; for (int i = 0; i < index.NumShapeIds(); ++i) { var shape = index.Shape(i); if (shape != null) { area += S2.GetArea(shape); } } return(area); }
// Returns the centroid of all shapes whose dimension is maximal within the // index, multiplied by the measure of those shapes. For example, if the // index contains points and polylines, then the result is defined as the // centroid of the polylines multiplied by the total length of those // polylines. The points would be ignored when computing the centroid. // // The measure of a given shape is defined as follows: // // - For dimension 0 shapes, the measure is shape.num_edges(). // - For dimension 1 shapes, the measure is GetLength(shape). // - For dimension 2 shapes, the measure is GetArea(shape). // // Note that the centroid is not unit length, so you may need to call // Normalize() before passing it to other S2 functions. Note that this // function returns (0, 0, 0) if the index contains no geometry. // // The centroid is scaled by the total measure of the shapes for two reasons: // (1) it is cheaper to compute this way, and (2) this makes it easier to // compute the centroid of a collection of shapes (since the individual // centroids can simply be summed). public static S2Point GetCentroid(S2ShapeIndex index) { int dim = GetDimension(index); var centroid = S2Point.Empty; for (int i = 0; i < index.NumShapeIds(); ++i) { var shape = index.Shape(i); if (shape != null && shape.Dimension() == dim) { centroid += S2.GetCentroid(shape); } } return(centroid); }
public VisitIntersectingShapesTest(S2ShapeIndex index) { index_ = index; iter_ = new(index); region_ = new(index); // Create an S2ShapeIndex for each shape in the original index, so that we // can use MayIntersect() and Contains() to determine the status of // individual shapes. for (int s = 0; s < index_.NumShapeIds(); ++s) { var shape_index = new MutableS2ShapeIndex(); shape_index.Add(new S2WrappedShape(index_.Shape(s))); shape_indexes_.Add(shape_index); } }
// Like CountEdges(), but stops once "max_edges" edges have been found (in // which case the current running total is returned). public static int GetCountEdgesUpTo(this S2ShapeIndex index, int max_edges) { int num_edges = 0; foreach (var shape in index) { if (shape == null) { continue; } num_edges += shape.NumEdges(); if (num_edges >= max_edges) { break; } } return(num_edges); }
/// <summary> /// Visits all pairs of crossing edges in the given S2ShapeIndex, terminating /// early if the given EdgePairVisitor function returns false (in which case /// VisitCrossings returns false as well). "type" indicates whether all /// crossings should be visited, or only interior crossings. /// /// If "need_adjacent" is false, then edge pairs of the form (AB, BC) may /// optionally be ignored (even if the two edges belong to different edge /// chains). This option exists for the benefit of FindSelfIntersection(), /// which does not need such edge pairs (see below). /// </summary> private static bool VisitCrossings(S2ShapeIndex index, CrossingType type, bool need_adjacent, EdgePairVisitor visitor) { // TODO(ericv): Use brute force if the total number of edges is small enough // (using a larger threshold if the S2ShapeIndex is not constructed yet). var count = index.GetEnumerableCount(); for (var pos = 0; pos < count; pos++) { var shape_edges = new ShapeEdgeVector(); var icell = index.GetIndexCell(pos); var indexCell = icell.Value.Item2; GetShapeEdges(index, indexCell, shape_edges); if (!VisitCrossings(shape_edges, type, need_adjacent, visitor)) { return(false); } } return(true); }
// Returns the total number of edges in all indexed shapes. This method takes // time linear in the number of shapes. public static int GetCountEdges(this S2ShapeIndex index) { return(GetCountEdgesUpTo(index, int.MaxValue)); }
/// <summary> /// Returns a vector containing all edges in the given S2ShapeIndexCell. /// (The result is returned as an output parameter so that the same storage can /// be reused, rather than allocating a new temporary vector each time.) /// </summary> private static void GetShapeEdges(S2ShapeIndex index, S2ShapeIndexCell cell, ShapeEdgeVector shape_edges) { shape_edges.Clear(); AppendShapeEdges(index, cell, shape_edges); }
// Finds all polygons in the given "query_index" that completely contain a // connected component of the target geometry. (For example, if the // target consists of 10 points, this method finds polygons that contain // any of those 10 points.) For each such polygon, "visitor" is called // with the S2Shape of the polygon along with a point of the target // geometry that is contained by that polygon. // // Optionally, any polygon that intersects the target geometry may also be // returned. In other words, this method returns all polygons that // contain any connected component of the target, along with an arbitrary // subset of the polygons that intersect the target. // // For example, suppose that "query_index" contains two abutting polygons // A and B. If the target consists of two points "a" contained by A and // "b" contained by B, then both A and B are returned. But if the target // consists of the edge "ab", then any subset of {A, B} could be returned // (because both polygons intersect the target but neither one contains // the edge "ab"). // // If "visitor" returns false, this method terminates early and returns // false as well. Otherwise returns true. // // NOTE(ericv): This method exists only for the purpose of implementing // S2ClosestEdgeQuery::Options::include_interiors() efficiently. Its API is // unlikely to be useful for other purposes. // // CAVEAT: Containing shapes may be visited more than once. public abstract bool VisitContainingShapes(S2ShapeIndex query_index, ShapeVisitor visitor);
private EdgeEnumerator(S2ShapeIndex index, int shape_id, int num_edges, int edge_id) { index_ = index; shape_id_ = shape_id; num_edges_ = num_edges; edge_id_ = edge_id; }
// Constructs an iterator positioned as specified. By default iterators // are unpositioned, since this avoids an extra seek in this situation // where one of the seek methods (such as Locate) is immediately called. // // If you want to position the iterator at the beginning, e.g. in order to // loop through the entire index, do this instead: // // for (S2ShapeIndex.Iterator it(out index, S2ShapeIndex.InitialPosition.BEGIN); // !it.done(); it.Next()) { ... } public Enumerator(S2ShapeIndex index) { _it = index.GetNewEnumerator(); }
/// <summary> /// Verifies that two S2ShapeIndexes have identical contents (including all the /// S2Shapes in both indexes). /// </summary> public static void ExpectEqual(S2ShapeIndex a, S2ShapeIndex b) { // Check that both indexes have identical shapes. Assert.True(a.NumShapeIds() == b.NumShapeIds()); for (int shape_id = 0; shape_id < a.NumShapeIds(); ++shape_id) { var a_shape = a.Shape(shape_id); var b_shape = b.Shape(shape_id); if (a_shape == null || b_shape == null) { Assert.True(a_shape == b_shape); } else { Assert.True(a_shape.Id == b_shape.Id); Assert.True(a_shape == b_shape); } } // Check that both indexes have identical cell contents. var a_it = a.GetNewEnumerator(); var b_it = b.GetNewEnumerator(); var aHasNext = a_it.MoveNext(); var bHasNext = b_it.MoveNext(); while (aHasNext && bHasNext) { Assert.True(a_it.Current.Item1 == b_it.Current.Item1); var a_cell = a_it.Current.Item2; var b_cell = b_it.Current.Item2; Assert.True(a_cell.NumClipped() == b_cell.NumClipped()); for (var i = 0; i < a_cell.NumClipped(); ++i) { var a_clipped = a_cell.Clipped(i); var b_clipped = b_cell.Clipped(i); Assert.True(a_clipped.ShapeId == b_clipped.ShapeId); Assert.True(a_clipped.ContainsCenter == b_clipped.ContainsCenter); Assert.True(a_clipped.NumEdges == b_clipped.NumEdges); for (int j = 0; j < a_clipped.NumEdges; ++j) { Assert.True(a_clipped.Edge(j) == b_clipped.Edge(j)); } } aHasNext = a_it.MoveNext(); bHasNext = b_it.MoveNext(); } Assert.True(!bHasNext); // Spot-check the other iterator methods. (We know that both indexes have // the same contents, so any differences are due to implementation bugs.) a_it.Reset(); b_it.Reset(); a_it.MoveNext(); b_it.MoveNext(); Assert.True(a_it.Current.Item1 == b_it.Current.Item1); aHasNext = a_it.MoveNext(); bHasNext = b_it.MoveNext(); if (aHasNext) { Assert.True(a_it.Current.Item1 == b_it.Current.Item1); Assert.True(aHasNext == bHasNext); // Assert.True(a_it.MovePrevious()); // Assert.True(b_it.MovePrevious()); Assert.True(a_it.Current.Item1 == b_it.Current.Item1); } // Assert.False(a_it.MovePrevious()); // Assert.False(b_it.MovePrevious()); // a_it.Finish(); // b_it.Finish(); // Assert.True(a_it.id() == b_it.id()); // a_it.Seek(a_it.id().Next); // b_it.Seek(b_it.id().Next); // Assert.True(a_it.id() == b_it.id()); }
/// <summary> /// Like the above, but visits all pairs of crossing edges where one edge comes /// from each S2ShapeIndex. /// /// CAVEAT: Crossings may be visited more than once. /// </summary> public static bool VisitCrossingEdgePairs(S2ShapeIndex a_index, S2ShapeIndex b_index, CrossingType type, EdgePairVisitor visitor) { // We look for S2CellId ranges where the indexes of A and B overlap, and // then test those edges for crossings. // TODO(ericv): Use brute force if the total number of edges is small enough // (using a larger threshold if the S2ShapeIndex is not constructed yet). var ai = new RangeEnumerator(a_index); var bi = new RangeEnumerator(b_index); var ab = new IndexCrosser(a_index, b_index, type, visitor, false); // Tests A against B var ba = new IndexCrosser(b_index, a_index, type, visitor, true); // Tests B against A while (!ai.Done() || !bi.Done()) { if (ai.RangeMax < bi.RangeMin) { // The A and B cells don't overlap, and A precedes B. ai.SeekTo(bi); } else if (bi.RangeMax < ai.RangeMin) { // The A and B cells don't overlap, and B precedes A. bi.SeekTo(ai); } else { // One cell contains the other. Determine which cell is larger. var ab_relation = ai.Id.LowestOnBit() - bi.Id.LowestOnBit(); if (ab_relation > 0) { // A's index cell is larger. if (!ab.VisitCrossings(ai, bi)) { return(false); } } else if (ab_relation < 0) { // B's index cell is larger. if (!ba.VisitCrossings(bi, ai)) { return(false); } } else { // The A and B cells are the same. if (ai.Cell.NumEdges() > 0 && bi.Cell.NumEdges() > 0) { if (!ab.VisitCellCellCrossings(ai.Cell, bi.Cell)) { return(false); } } ai.MoveNext(); bi.MoveNext(); } } } return(true); }
// Construct a new RangeIterator positioned at the first cell of the index. public RangeEnumerator(S2ShapeIndex index) { _index = index; _it = new S2ShapeIndex.Enumerator(index); //Refresh(); }
// Convenience constructor that accepts an S1Angle for the radius. // REQUIRES: radius >= S1Angle.Zero() public S2ShapeIndexBufferedRegion(S2ShapeIndex index, S1Angle radius) : this(index, new S1ChordAngle(radius)) { }
/// <summary> /// Visits all pairs of crossing edges in the given S2ShapeIndex, terminating /// early if the given EdgePairVisitor function returns false (in which case /// VisitCrossings returns false as well). "type" indicates whether all /// crossings should be visited, or only interior crossings. /// /// CAVEAT: Crossings may be visited more than once. /// </summary> public static bool VisitCrossingEdgePairs(S2ShapeIndex index, CrossingType type, EdgePairVisitor visitor) { var needAdjacent = type == CrossingType.ALL; return(VisitCrossings(index, type, needAdjacent, visitor)); }