示例#1
0
    // Returns the centroid of the shape multiplied by the measure of the shape,
    // which 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 result is not unit length, so you may need to call
    // Normalize() before passing it to other S2 functions.
    //
    // The result is scaled by the measure defined above 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.  (This requires simply summing the
    // centroids of all shapes in the collection whose dimension is maximal.)
    public static S2Point GetCentroid(S2Shape shape)
    {
        var centroid = S2Point.Empty;

        S2Point[] vertices;
        int       dimension  = shape.Dimension();
        int       num_chains = shape.NumChains();

        for (int chain_id = 0; chain_id < num_chains; ++chain_id)
        {
            switch (dimension)
            {
            case 0:
                centroid += shape.GetEdge(chain_id).V0;
                break;

            case 1:
                GetChainVertices(shape, chain_id, out vertices);
                centroid += S2PolylineMeasures.GetCentroid(vertices);
                break;

            default:
                GetChainVertices(shape, chain_id, out vertices);
                centroid += S2.GetCentroid(vertices.ToList());
                break;
            }
        }
        return(centroid);
    }
示例#2
0
    // This is a helper function for GetReferencePoint() below.
    //
    // If the given vertex "vtest" is unbalanced (see definition below), sets
    // "result" to a ReferencePoint indicating whther "vtest" is contained and
    // returns true.  Otherwise returns false.
    private static bool GetReferencePointAtVertex(S2Shape shape, S2Point vtest, out S2Shape.ReferencePoint result)
    {
        // Let P be an unbalanced vertex.  Vertex P is defined to be inside the
        // region if the region contains a particular direction vector starting from
        // P, namely the direction S2::RefDir(P).  This can be calculated using
        // S2ContainsVertexQuery.
        var contains_query = new S2ContainsVertexQuery(vtest);
        int n = shape.NumEdges();

        for (int e = 0; e < n; ++e)
        {
            var edge = shape.GetEdge(e);
            if (edge.V0 == vtest)
            {
                contains_query.AddEdge(edge.V1, 1);
            }
            if (edge.V1 == vtest)
            {
                contains_query.AddEdge(edge.V0, -1);
            }
        }
        int contains_sign = contains_query.ContainsSign();

        if (contains_sign == 0)
        {
            result = new S2Shape.ReferencePoint(S2Point.Empty, false);
            return(false);  // There are no unmatched edges incident to this vertex.
        }
        result = new S2Shape.ReferencePoint(vtest, contains_sign > 0);
        return(true);
    }
示例#3
0
 // Converts a 1-dimensional S2Shape into an S2Polyline. Converts the first
 // chain in the shape to a vector of S2Point vertices and uses that to construct
 // the S2Polyline. Note that the input 1-dimensional S2Shape must contain at
 // most 1 chain, and that this method does not accept an empty shape (i.e. a
 // shape with no vertices).
 public static S2Polyline ShapeToS2Polyline(S2Shape line)
 {
     System.Diagnostics.Debug.Assert(line.Dimension() == 1);
     System.Diagnostics.Debug.Assert(line.NumChains() == 1);
     S2Point[] vertices;
     S2.GetChainVertices(line, 0, out vertices);
     return new S2Polyline(vertices);
 }
示例#4
0
 // Converts a 0-dimensional S2Shape into a list of S2Points.
 // This method does allow an empty shape (i.e. a shape with no vertices).
 public static List<S2Point> ShapeToS2Points(S2Shape multipoint)
 {
     System.Diagnostics.Debug.Assert(multipoint.Dimension() == 0);
     List<S2Point> points = new();
     points.Capacity = multipoint.NumEdges();
     for (int i = 0; i < multipoint.NumEdges(); ++i)
     {
         points.Add(multipoint.GetEdge(i).V0);
     }
     return points;
 }
示例#5
0
    // A ShapeEncoder that encode all standard S2Shape types, preferring
    // compact (but slower) encodings.  For example, points that have been snapped
    // to S2CellId centers will be encoded using at most 8 bytes.
    //
    // REQUIRES: "encoder" uses the default constructor, so that its buffer
    //           can be enlarged as necessary by calling Ensure(int).
    public static bool CompactEncodeShape(S2Shape shape, Encoder encoder)
    {
        var tag = shape.GetTypeTag();

        if (tag == S2Shape.TypeTag.kNoTypeTag)
        {
            System.Diagnostics.Debug.WriteLine("(DFATAL) Unsupported S2Shape type: " + tag);
            return(false);
        }
        // Update the following constant when adding new S2Shape encodings.
        System.Diagnostics.Debug.Assert(tag < S2Shape.TypeTag.kNextAvailableTypeTag);
        shape.Encode(encoder, CodingHint.COMPACT);
        return(true);
    }
示例#6
0
    private static void CompareS2LoopToShape(S2Loop loop, S2Shape shape)
    {
        MutableS2ShapeIndex index = new();

        index.Add(shape);
        S2Cap cap   = loop.GetCapBound();
        var   query = index.MakeS2ContainsPointQuery();

        for (int iter = 0; iter < 100; ++iter)
        {
            S2Point point = S2Testing.SamplePoint(cap);
            Assert.Equal(loop.Contains(point),
                         query.ShapeContains(index.Shape(0), point));
        }
    }
示例#7
0
    // For shapes of dimension 2, returns the area of the shape on the unit
    // sphere.  The result is between 0 and 4*Pi steradians.  Otherwise returns
    // zero.  This method has good relative accuracy for both very large and very
    // small regions.
    //
    // 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(S2Shape shape)
    {
        if (shape.Dimension() != 2)
        {
            return(0.0);
        }

        // Since S2Shape uses the convention that the interior of the shape is to
        // the left of all edges, in theory we could compute the area of the polygon
        // by simply adding up all the loop areas modulo 4*Pi.  The problem with
        // this approach is that polygons holes typically have areas near 4*Pi,
        // which can create large cancellation errors when computing the area of
        // small polygons with holes.  For example, a shell with an area of 4 square
        // meters (1e-13 steradians) surrounding a hole with an area of 3 square
        // meters (7.5e-14 sterians) would lose almost all of its accuracy if the
        // area of the hole was computed as 12.566370614359098.
        //
        // So instead we use S2.GetSignedArea() to ensure that all loops have areas
        // in the range [-2*Pi, 2*Pi].
        //
        // TODO(ericv): Rarely, this function returns the area of the complementary
        // region (4*Pi - area).  This can only happen when the true area is very
        // close to zero or 4*Pi and the polygon has multiple loops.  To make this
        // function completely robust requires checking whether the signed area sum is
        // ambiguous, and if so, determining the loop nesting structure.  This allows
        // the sum to be evaluated in a way that is guaranteed to have the correct
        // sign.
        double area       = 0;
        double max_error  = 0;
        int    num_chains = shape.NumChains();

        for (int chain_id = 0; chain_id < num_chains; ++chain_id)
        {
            GetChainVertices(shape, chain_id, out var vertices);
            area += S2.GetSignedArea(vertices.ToList());
#if DEBUG
            max_error += S2.GetCurvatureMaxError(new(vertices));
#endif
        }
        // Note that S2.GetSignedArea() guarantees that the full loop (containing
        // all points on the sphere) has a very small negative area.
        System.Diagnostics.Debug.Assert(Math.Abs(area) <= S2.M_4_PI + max_error);
        if (area < 0.0)
        {
            area += S2.M_4_PI;
        }
        return(area);
    }
示例#8
0
    // For shapes of dimension 1, returns the sum of all polyline lengths on the
    // unit sphere.  Otherwise returns zero.  (See GetPerimeter for shapes of
    // dimension 2.)
    //
    // 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(S2Shape shape)
    {
        if (shape.Dimension() != 1)
        {
            return(S1Angle.Zero);
        }
        var length     = S1Angle.Zero;
        int num_chains = shape.NumChains();

        for (int chain_id = 0; chain_id < num_chains; ++chain_id)
        {
            GetChainVertices(shape, chain_id, out var vertices);
            length += S2PolylineMeasures.GetLength(vertices);
        }
        return(length);
    }
示例#9
0
    // For shapes of dimension 2, returns the sum of all loop perimeters on the
    // unit sphere.  Otherwise returns zero.  (See GetLength for shapes of
    // dimension 1.)
    //
    // 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(S2Shape shape)
    {
        if (shape.Dimension() != 2)
        {
            return(S1Angle.Zero);
        }
        var perimeter  = S1Angle.Zero;
        int num_chains = shape.NumChains();

        for (int chain_id = 0; chain_id < num_chains; ++chain_id)
        {
            GetChainVertices(shape, chain_id, out var vertices);
            perimeter += S2.GetPerimeter(vertices);
        }
        return(perimeter);
    }
示例#10
0
    // Converts a 2-dimensional S2Shape into an S2Polygon. Each chain is converted
    // to an S2Loop and the vector of loops is used to construct the S2Polygon.
    public static S2Polygon ShapeToS2Polygon(S2Shape poly)
    {
        if (poly.IsFull())
        {
            return new S2Polygon(S2Loop.kFull);
        }
        System.Diagnostics.Debug.Assert(poly.Dimension() == 2);
        List<S2Loop> loops = new();
        for (int i = 0; i < poly.NumChains(); ++i)
        {
            S2.GetChainVertices(poly, i, out var vertices);
            loops.Add(new S2Loop(vertices));
        }
        var output_poly = new S2Polygon(loops, initOriented: true);

        return output_poly;
    }
        private static void AddShapeWithLabels(S2Shape shape, EdgeType edge_type,
                                               S2Builder builder, EdgeLabelMap edge_label_map)
        {
            const int kLabelBegin = 1234;  // Arbitrary.

            for (int e = 0; e < shape.NumEdges(); ++e)
            {
                Int32 label = kLabelBegin + e;
                builder.SetLabel(label);
                // For undirected edges, reverse the direction of every other input edge.
                S2Shape.Edge edge = shape.GetEdge(e);
                if (edge_type == EdgeType.UNDIRECTED && ((e & 1) != 0))
                {
                    edge = new S2Shape.Edge(edge.V1, edge.V0);
                }
                builder.AddEdge(edge.V0, edge.V1);
                edge_label_map[GetKey(edge, edge_type)].Add(label);
            }
        }
示例#12
0
    // Overwrites "vertices" with the vertices of the given edge chain of "shape".
    // If dimension == 1, the chain will have (chain.length + 1) vertices, and
    // otherwise it will have (chain.length) vertices.
    //
    // This is a low-level helper method used in the implementations of some of
    // the methods above.
    public static void GetChainVertices(S2Shape shape, int chain_id, out S2Point[] vertices)
    {
        S2Shape.Chain chain        = shape.GetChain(chain_id);
        int           num_vertices = chain.Length + (shape.Dimension() == 1 ? 1 : 0);
        int           e            = 0;
        var           verts        = new List <S2Point>();

        if ((num_vertices & 1) != 0)
        {
            verts.Add(shape.ChainEdge(chain_id, e++).V0);
        }
        for (; e < num_vertices; e += 2)
        {
            var edge = shape.ChainEdge(chain_id, e);
            verts.Add(edge.V0);
            verts.Add(edge.V1);
        }

        vertices = verts.ToArray();
    }
示例#13
0
 /// <summary>
 /// Verifies that all methods of the two S2Shapes return identical results,
 /// except for id() and type_tag().
 /// </summary>
 public static void ExpectEqual(S2Shape a, S2Shape b)
 {
     Assert.True(a.NumEdges() == b.NumEdges());
     for (int i = 0; i < a.NumEdges(); ++i)
     {
         Assert.Equal(a.GetEdge(i), b.GetEdge(i));
         Assert.True(a.GetChainPosition(i) == b.GetChainPosition(i));
     }
     Assert.True(a.Dimension() == b.Dimension());
     Assert.True(a.GetReferencePoint() == b.GetReferencePoint());
     Assert.True(a.NumChains() == b.NumChains());
     for (int i = 0; i < a.NumChains(); ++i)
     {
         Assert.True(a.GetChain(i) == b.GetChain(i));
         int chain_length = a.GetChain(i).Length;
         for (int j = 0; j < chain_length; ++j)
         {
             Assert.True(a.ChainEdge(i, j) == b.ChainEdge(i, j));
         }
     }
 }
示例#14
0
    // Like GetArea(), except that this method is faster and has more error.  The
    // additional error is at most 2.22e-15 steradians per vertex, which works out
    // to about 0.09 square meters per vertex on the Earth's surface.  For
    // example, a loop with 100 vertices has a maximum error of about 9 square
    // meters.  (The actual error is typically much smaller than this.)
    public static double GetApproxArea(S2Shape shape)
    {
        if (shape.Dimension() != 2)
        {
            return(0.0);
        }

        double area       = 0;
        int    num_chains = shape.NumChains();

        for (int chain_id = 0; chain_id < num_chains; ++chain_id)
        {
            GetChainVertices(shape, chain_id, out var vertices);
            area += S2.GetApproxArea(vertices.ToList()); //todo
        }
        // Special case to ensure that full polygons are handled correctly.
        if (area <= S2.M_4_PI)
        {
            return(area);
        }
        return(area % S2.M_4_PI);
    }
示例#15
0
    // Returns true if the given shape contains the given point.  Most clients
    // should not use this method, since its running time is linear in the number
    // of shape edges.  Instead clients should create an S2ShapeIndex and use
    // S2ContainsPointQuery, since this strategy is much more efficient when many
    // points need to be tested.
    //
    // Polygon boundaries are treated as being semi-open (see S2ContainsPointQuery
    // and S2VertexModel for other options).
    //
    // CAVEAT: Typically this method is only used internally.  Its running time is
    //         linear in the number of shape edges.
    public static bool ContainsBruteForce(this S2Shape shape, S2Point point)
    {
        if (shape.Dimension() < 2)
        {
            return(false);
        }

        var ref_point = shape.GetReferencePoint();

        if (ref_point.Point == point)
        {
            return(ref_point.Contained);
        }

        S2CopyingEdgeCrosser crosser = new(ref_point.Point, point);
        bool inside = ref_point.Contained;

        for (int e = 0; e < shape.NumEdges(); ++e)
        {
            var edge = shape.GetEdge(e);
            inside ^= crosser.EdgeOrVertexCrossing(edge.V0, edge.V1);
        }
        return(inside);
    }
示例#16
0
 // Returns a pointer to the clipped shape corresponding to the given shape,
 // or null if the shape does not intersect this cell.
 public S2ClippedShape FindClipped(S2Shape shape)
 {
     return(FindClipped(shape.Id));
 }
示例#17
0
    // This is a helper function for implementing S2Shape.GetReferencePoint().
    //
    // Given a shape consisting of closed polygonal loops, the interior of the
    // shape is defined as the region to the left of all edges (which must be
    // oriented consistently).  This function then chooses an arbitrary point and
    // returns true if that point is contained by the shape.
    //
    // Unlike S2Loop and S2Polygon, this method allows duplicate vertices and
    // edges, which requires some extra care with definitions.  The rule that we
    // apply is that an edge and its reverse edge "cancel" each other: the result
    // is the same as if that edge pair were not present.  Therefore shapes that
    // consist only of degenerate loop(s) are either empty or full; by convention,
    // the shape is considered full if and only if it contains an empty loop (see
    // S2LaxPolygonShape for details).
    //
    // Determining whether a loop on the sphere contains a point is harder than
    // the corresponding problem in 2D plane geometry.  It cannot be implemented
    // just by counting edge crossings because there is no such thing as a "point
    // at infinity" that is guaranteed to be outside the loop.
    public static S2Shape.ReferencePoint GetReferencePoint(this S2Shape shape)
    {
        System.Diagnostics.Debug.Assert(shape.Dimension() == 2);
        if (shape.NumEdges() == 0)
        {
            // A shape with no edges is defined to be full if and only if it
            // contains at least one chain.
            return(S2Shape.ReferencePoint.FromContained(shape.NumChains() > 0));
        }
        // Define a "matched" edge as one that can be paired with a corresponding
        // reversed edge.  Define a vertex as "balanced" if all of its edges are
        // matched. In order to determine containment, we must find an unbalanced
        // vertex.  Often every vertex is unbalanced, so we start by trying an
        // arbitrary vertex.
        var edge = shape.GetEdge(0);

        if (GetReferencePointAtVertex(shape, edge.V0, out var result))
        {
            return(result);
        }
        // That didn't work, so now we do some extra work to find an unbalanced
        // vertex (if any).  Essentially we gather a list of edges and a list of
        // reversed edges, and then sort them.  The first edge that appears in one
        // list but not the other is guaranteed to be unmatched.
        int n         = shape.NumEdges();
        var edges     = new S2Shape.Edge[n];
        var rev_edges = new S2Shape.Edge[n];

        for (int i = 0; i < n; ++i)
        {
            var edge2 = shape.GetEdge(i);
            edges[i]     = edge2;
            rev_edges[i] = new S2Shape.Edge(edge2.V1, edge2.V0);
        }
        Array.Sort(edges);
        Array.Sort(rev_edges);
        for (int i = 0; i < n; ++i)
        {
            if (edges[i] < rev_edges[i])
            {  // edges[i] is unmatched
                System.Diagnostics.Debug.Assert(GetReferencePointAtVertex(shape, edges[i].V0, out result));
                return(result);
            }
            if (rev_edges[i] < edges[i])
            {  // rev_edges[i] is unmatched
                System.Diagnostics.Debug.Assert(GetReferencePointAtVertex(shape, rev_edges[i].V0, out result));
                return(result);
            }
        }
        // All vertices are balanced, so this polygon is either empty or full except
        // for degeneracies.  By convention it is defined to be full if it contains
        // any chain with no edges.
        for (int i = 0; i < shape.NumChains(); ++i)
        {
            if (shape.GetChain(i).Length == 0)
            {
                return(S2Shape.ReferencePoint.FromContained(true));
            }
        }
        return(S2Shape.ReferencePoint.FromContained(false));
    }
示例#18
0
    // The purpose of this function is to construct polygons consisting of
    // multiple loops.  It takes as input a collection of loops whose boundaries
    // do not cross, and groups them into polygons whose interiors do not
    // intersect (where the boundary of each polygon may consist of multiple
    // loops).
    //
    // some of those islands have lakes, then the input to this function would
    // islands, and their lakes.  Each loop would actually be present twice, once
    // in each direction (see below).  The output would consist of one polygon
    // representing each lake, one polygon representing each island not including
    // islands or their lakes, and one polygon representing the rest of the world
    //
    // This method is intended for internal use; external clients should use
    // S2Builder, which has more convenient interface.
    //
    // The input consists of a set of connected components, where each component
    // consists of one or more loops.  The components must satisfy the following
    // properties:
    //
    //  - The loops in each component must form a subdivision of the sphere (i.e.,
    //    they must cover the entire sphere without overlap), except that a
    //    component may consist of a single loop if and only if that loop is
    //    degenerate (i.e., its interior is empty).
    //
    //  - The boundaries of different components must be disjoint (i.e. no
    //    crossing edges or shared vertices).
    //
    //  - No component should be empty, and no loop should have zero edges.
    //
    // The output consists of a set of polygons, where each polygon is defined by
    // the collection of loops that form its boundary.  This function does not
    // actually construct any S2Shapes; it simply identifies the loops that belong
    // to each polygon.
    public static void BuildPolygonBoundaries(List <List <S2Shape> > components, List <List <S2Shape> > polygons)
    {
        polygons.Clear();
        if (!components.Any())
        {
            return;
        }

        // Since the loop boundaries do not cross, a loop nesting hierarchy can be
        // defined by choosing any point on the sphere as the "point at infinity".
        // Loop A then contains loop B if (1) A contains the boundary of B and (2)
        // loop A does not contain the point at infinity.
        //
        // We choose S2.Origin for this purpose.  The loop nesting hierarchy then
        // determines the face structure.  Here are the details:
        //
        // 1. Build an S2ShapeIndex of all loops that do not contain S2.Origin.
        //    This leaves at most one unindexed loop per connected component
        //    (the "outer loop").
        //
        // 2. For each component, choose a representative vertex and determine
        //    which indexed loops contain it.  The "depth" of this component is
        //    defined as the number of such loops.
        //
        // 3. Assign the outer loop of each component to the containing loop whose
        //    depth is one less.  This generates a set of multi-loop polygons.
        //
        // 4. The outer loops of all components at depth 0 become a single face.

        var index = new MutableS2ShapeIndex();
        // A map from shape.id() to the corresponding component number.
        var component_ids = new List <int>();
        var outer_loops   = new List <S2Shape>();

        for (int i = 0; i < components.Count; ++i)
        {
            var component = components[i];
            foreach (var loop in component)
            {
                if (component.Count > 1 &&
                    !loop.ContainsBruteForce(S2.Origin))
                {
                    // Ownership is transferred back at the end of this function.
                    index.Add(loop);
                    component_ids.Add(i);
                }
                else
                {
                    outer_loops.Add(loop);
                }
            }
            // Check that there is exactly one outer loop in each component.
            System.Diagnostics.Debug.Assert(i + 1 == outer_loops.Count); // Component is not a subdivision
        }
        // Find the loops containing each component.
        var ancestors      = new List <List <S2Shape> >(components.Count);
        var contains_query = index.MakeS2ContainsPointQuery();

        for (int i = 0; i < outer_loops.Count; ++i)
        {
            var loop = outer_loops[i];
            System.Diagnostics.Debug.Assert(loop.NumEdges() > 0);
            ancestors[i] = contains_query.GetContainingShapes(loop.GetEdge(0).V0);
        }
        // Assign each outer loop to the component whose depth is one less.
        // Components at depth 0 become a single face.
        Dictionary <S2Shape, List <S2Shape> > children = new(); // btree_map

        for (int i = 0; i < outer_loops.Count; ++i)
        {
            S2Shape?ancestor = null;
            int     depth    = ancestors[i].Count;
            if (depth > 0)
            {
                foreach (var candidate in ancestors[i])
                {
                    if (ancestors[component_ids[candidate.Id]].Count == depth - 1)
                    {
                        System.Diagnostics.Debug.Assert(ancestor is null);
                        ancestor = candidate;
                    }
                }
                System.Diagnostics.Debug.Assert(ancestor is not null);
            }
            S2Shape notNullAncestor = ancestor !;
            if (!children.ContainsKey(notNullAncestor))
            {
                children.Add(notNullAncestor, new List <S2Shape>());
            }
            children[notNullAncestor].Add(outer_loops[i]);
        }
        // There is one face per loop that is not an outer loop, plus one for the
        // outer loops of components at depth 0.
        polygons.Resize(index.NumShapeIds() + 1, () => new List <S2Shape>());
        for (int i = 0; i < index.NumShapeIds(); ++i)
        {
            var polygon = polygons[i];
            var loop    = index.Shape(i);
            var itr     = children.ContainsKey(loop) ? children[loop] : null;
            if (itr != null)
            {
                polygon = itr;
            }
            polygon.Add(loop);
        }
        polygons[^ 1] = children[null];
示例#19
0
     public ShapeEdge(S2Shape shape, Int32 edge_id)
         : this(shape.Id, edge_id, shape.GetEdge(edge_id))
 {
 }
示例#20
0
 public S2WrappedShape(S2Shape shape) => shape_ = shape;