コード例 #1
0
ファイル: S2ShapeMeasures.cs プロジェクト: alas/s2geometry
    // 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
ファイル: Conversion.cs プロジェクト: alas/s2geometry
 // 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);
 }
コード例 #3
0
ファイル: Conversion.cs プロジェクト: alas/s2geometry
 // 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;
 }
コード例 #4
0
ファイル: S2ShapeMeasures.cs プロジェクト: alas/s2geometry
    // 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);
    }
コード例 #5
0
ファイル: S2ShapeMeasures.cs プロジェクト: alas/s2geometry
    // 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);
    }
コード例 #6
0
ファイル: S2ShapeMeasures.cs プロジェクト: alas/s2geometry
    // 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);
    }
コード例 #7
0
ファイル: Conversion.cs プロジェクト: alas/s2geometry
    // 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;
    }
コード例 #8
0
ファイル: S2ShapeMeasures.cs プロジェクト: alas/s2geometry
    // 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();
    }
コード例 #9
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));
         }
     }
 }
コード例 #10
0
ファイル: S2ShapeMeasures.cs プロジェクト: alas/s2geometry
    // 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);
    }
コード例 #11
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);
    }
コード例 #12
0
ファイル: GetReferencePoint.cs プロジェクト: alas/s2geometry
    // 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));
    }