Beispiel #1
0
    public void Test_S2LatLngRect_GetVertex()
    {
        S2LatLngRect r1 = new(new R1Interval(0, S2.M_PI_2), new S1Interval(-Math.PI, 0));

        Assert.Equal(r1.Vertex(0), S2LatLng.FromRadians(0, Math.PI));
        Assert.Equal(r1.Vertex(1), S2LatLng.FromRadians(0, 0));
        Assert.Equal(r1.Vertex(2), S2LatLng.FromRadians(S2.M_PI_2, 0));
        Assert.Equal(r1.Vertex(3), S2LatLng.FromRadians(S2.M_PI_2, Math.PI));

        // Make sure that GetVertex() returns vertices in CCW order.
        for (int i = 0; i < 4; ++i)
        {
            double       lat = S2.M_PI_4 * (i - 2);
            double       lng = S2.M_PI_2 * (i - 2) + 0.2;
            S2LatLngRect r   = new(
                new R1Interval(lat, lat + S2.M_PI_4),
                new S1Interval(
                    Math.IEEERemainder(lng, S2.M_2_PI),
                    Math.IEEERemainder(lng + S2.M_PI_2, S2.M_2_PI)));
            for (int k = 0; k < 4; ++k)
            {
                Assert.True(S2Pred.Sign(
                                r.Vertex(k - 1).ToPoint(),
                                r.Vertex(k).ToPoint(),
                                r.Vertex(k + 1).ToPoint()) > 0);
            }
        }
    }
Beispiel #2
0
    // Return the exterior angle at vertex B in the triangle ABC.  The return
    // value is positive if ABC is counterclockwise and negative otherwise.  If
    // you imagine a constant walking from A to B to C, this is the angle that the
    // constant turns at vertex B (positive = left = CCW, negative = right = CW).
    // This quantity is also known as the "geodesic curvature" at B.
    //
    // Ensures that TurnAngle(a,b,c) == -TurnAngle(c,b,a) for all distinct
    // a,b,c. The result is undefined if (a == b || b == c), but is either
    // -Pi or Pi if (a == c).  All points should be normalized.
    public static double TurnAngle(S2Point a, S2Point b, S2Point c)
    {
        // We use RobustCrossProd() to get good accuracy when two points are very
        // close together, and Sign() to ensure that the sign is correct for
        // turns that are close to 180 degrees.
        //
        // Unfortunately we can't save RobustCrossProd(a, b) and pass it as the
        // optional 4th argument to Sign(), because Sign() requires a.CrossProd(b)
        // exactly (the robust version differs in magnitude).
        double angle = S2.RobustCrossProd(a, b).Angle(S2.RobustCrossProd(b, c));

        // Don't return Sign() * angle because it is legal to have (a == c).
        return((S2Pred.Sign(a, b, c) > 0) ? angle : -angle);
    }
Beispiel #3
0
    // A slightly more efficient version of Project() where the cross product of
    // the two endpoints has been precomputed.  The cross product does not need to
    // be normalized, but should be computed using S2.RobustCrossProd() for the
    // most accurate results.  Requires that x, a, and b have unit length.
    public static S2Point Project(S2Point x, S2Point a, S2Point b, S2Point a_cross_b)
    {
        System.Diagnostics.Debug.Assert(a.IsUnitLength());
        System.Diagnostics.Debug.Assert(b.IsUnitLength());
        System.Diagnostics.Debug.Assert(x.IsUnitLength());

        // TODO(ericv): When X is nearly perpendicular to the plane containing AB,
        // the result is guaranteed to be close to the edge AB but may be far from
        // the true projected result.  This could be fixed by computing the product
        // (A x B) x X x (A x B) using methods similar to S2::RobustCrossProd() and
        // S2::GetIntersection().  However note that the error tolerance would need
        // to be significantly larger in order for this calculation to succeed in
        // double precision most of the time.  For example to avoid higher precision
        // when X is within 60 degrees of AB the minimum error would be 18 * DBL_ERR,
        // and to avoid higher precision when X is within 87 degrees of AB the
        // minimum error would be 120 * DBL_ERR.

        // The following is not necessary to meet accuracy guarantees but helps
        // to avoid unexpected results in unit tests.
        if (x == a || x == b)
        {
            return(x);
        }

        // Find the closest point to X along the great circle through AB.  Note that
        // we use "n" rather than a_cross_b in the final cross product in order to
        // avoid the possibility of underflow.
        S2Point n = a_cross_b.Normalize();
        S2Point p = S2.RobustCrossProd(n, x).CrossProd(n).Normalize();

        // If this point is on the edge AB, then it's the closest point.
        S2Point pn = p.CrossProd(n);

        if (S2Pred.Sign(p, n, a, pn) > 0 && S2Pred.Sign(p, n, b, pn) < 0)
        {
            return(p);
        }

        // Otherwise, the closest point is either A or B.
        return(((x - a).Norm2() <= (x - b).Norm2()) ? a : b);
    }
Beispiel #4
0
 // Like Area(), but returns a positive value for counterclockwise triangles
 // and a negative value otherwise.
 public static double SignedArea(S2Point a, S2Point b, S2Point c)
 {
     return(S2Pred.Sign(a, b, c) * Area(a, b, c));
 }
Beispiel #5
0
    // Compute the convex hull of the input geometry provided.
    //
    // If there is no geometry, this method returns an empty loop containing no
    // points (see S2Loop.IsEmpty).
    //
    // If the geometry spans more than half of the sphere, this method returns a
    // full loop containing the entire sphere (see S2Loop.IsFull).
    //
    // If the geometry contains 1 or 2 points, or a single edge, this method
    // returns a very small loop consisting of three vertices (which are a
    // superset of the input vertices).
    //
    // Note that this method does not clear the geometry; you can continue
    // adding to it and call this method again if desired.
    public S2Loop GetConvexHull()
    {
        // Test whether the bounding cap is convex.  We need this to proceed with
        // the algorithm below in order to construct a point "origin" that is
        // definitely outside the convex hull.
        S2Cap cap = GetCapBound();

        if (cap.Height() >= 1 - 10 * S2Pred.DBL_ERR)
        {
            return(S2Loop.kFull);
        }
        // This code implements Andrew's monotone chain algorithm, which is a simple
        // variant of the Graham scan.  Rather than sorting by x-coordinate, instead
        // we sort the points in CCW order around an origin O such that all points
        // are guaranteed to be on one side of some geodesic through O.  This
        // ensures that as we scan through the points, each new point can only
        // belong at the end of the chain (i.e., the chain is monotone in terms of
        // the angle around O from the starting point).
        S2Point origin = cap.Center.Ortho();

        points_.Sort(new OrderedCcwAround(origin));

        // Remove duplicates.  We need to do this before checking whether there are
        // fewer than 3 points.
        var tmp = points_.Distinct().ToList();

        points_.Clear();
        points_.AddRange(tmp);

        // Special cases for fewer than 3 points.
        if (points_.Count < 3)
        {
            if (!points_.Any())
            {
                return(S2Loop.kEmpty);
            }
            else if (points_.Count == 1)
            {
                return(GetSinglePointLoop(points_[0]));
            }
            else
            {
                return(GetSingleEdgeLoop(points_[0], points_[1]));
            }
        }

        // Verify that all points lie within a 180 degree span around the origin.
        System.Diagnostics.Debug.Assert(S2Pred.Sign(origin, points_.First(), points_.Last()) >= 0);

        // Generate the lower and upper halves of the convex hull.  Each half
        // consists of the maximal subset of vertices such that the edge chain makes
        // only left (CCW) turns.
        var lower = new List <S2Point>();
        var upper = new List <S2Point>();

        GetMonotoneChain(lower);
        points_.Reverse();
        GetMonotoneChain(upper);

        // Remove the duplicate vertices and combine the chains.
        System.Diagnostics.Debug.Assert(lower.First() == upper.Last());
        System.Diagnostics.Debug.Assert(lower.Last() == upper.First());
        lower.RemoveAt(lower.Count - 1);
        upper.RemoveAt(lower.Count - 1);
        lower.AddRange(upper);
        return(new S2Loop(lower));
    }
Beispiel #6
0
    private static void TestCrossing(S2Point a, S2Point b, S2Point c, S2Point d,
                                     int crossing_sign, int signed_crossing_sign)
    {
        // For degenerate edges, CrossingSign() is documented to return 0 if two
        // vertices from different edges are the same and -1 otherwise.  The
        // TestCrossings() function below uses various argument permutations that
        // can sometimes create this case, so we fix it now if necessary.
        if (a == c || a == d || b == c || b == d)
        {
            crossing_sign = 0;
        }

        // As a sanity check, make sure that the expected value of
        // "signed_crossing_sign" is consistent with its documented properties.
        if (crossing_sign == 1)
        {
            Assert.Equal(signed_crossing_sign, S2Pred.Sign(a, b, c));
        }
        else if (crossing_sign == 0 && signed_crossing_sign != 0)
        {
            Assert.Equal(signed_crossing_sign, (a == c || b == d) ? 1 : -1);
        }

        Assert.Equal(crossing_sign, S2.CrossingSign(a, b, c, d));

        S2EdgeCrosser crosser = new(a, b, c);

        Assert.Equal(crossing_sign, crosser.CrossingSign(d));
        Assert.Equal(crossing_sign, crosser.CrossingSign(c));
        Assert.Equal(crossing_sign, crosser.CrossingSign(d, c));
        Assert.Equal(crossing_sign, crosser.CrossingSign(c, d));

        Assert.Equal(signed_crossing_sign != 0, S2.EdgeOrVertexCrossing(a, b, c, d));
        crosser.RestartAt(c);
        Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(d));
        Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(c));
        Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(d, c));
        Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(c, d));

        crosser.RestartAt(c);
        Assert.Equal(signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(d));
        Assert.Equal(-signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(c));
        Assert.Equal(-signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(d, c));
        Assert.Equal(signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(c, d));

        // Check that the crosser can be re-used.
        crosser.Init(c, d);
        crosser.RestartAt(a);
        Assert.Equal(crossing_sign, crosser.CrossingSign(b));
        Assert.Equal(crossing_sign, crosser.CrossingSign(a));

        // Now try all the same tests with CopyingEdgeCrosser.
        S2CopyingEdgeCrosser crosser2 = new(a, b, c);

        Assert.Equal(crossing_sign, crosser2.CrossingSign(d));
        Assert.Equal(crossing_sign, crosser2.CrossingSign(c));
        Assert.Equal(crossing_sign, crosser2.CrossingSign(d, c));
        Assert.Equal(crossing_sign, crosser2.CrossingSign(c, d));

        crosser2.RestartAt(c);
        Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(d));
        Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(c));
        Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(d, c));
        Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(c, d));

        crosser2.RestartAt(c);
        Assert.Equal(signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(d));
        Assert.Equal(-signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(c));
        Assert.Equal(-signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(d, c));
        Assert.Equal(signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(c, d));

        // Check that the crosser can be re-used.
        crosser2.Init(c, d);
        crosser2.RestartAt(a);
        Assert.Equal(crossing_sign, crosser2.CrossingSign(b));
        Assert.Equal(crossing_sign, crosser2.CrossingSign(a));
    }