Exemple #1
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));
    }
Exemple #2
0
        public void Test_S2Cap_Basic()
        {
            // Test basic properties of empty and full caps.
            S2Cap empty = S2Cap.Empty;
            S2Cap full  = S2Cap.Full;

            Assert.True(empty.IsValid());
            Assert.True(empty.IsEmpty());
            Assert.True(empty.Complement().IsFull());
            Assert.True(full.IsValid());
            Assert.True(full.IsFull());
            Assert.True(full.Complement().IsEmpty());
            Assert.Equal(2, full.Height());
            Assert2.DoubleEqual(180.0, full.Radius.Degrees());

            // Test ==/!=.
            Assert.Equal(full, full);
            Assert.Equal(empty, empty);
            Assert.NotEqual(full, empty);

            // Test the S1Angle constructor using out-of-range arguments.
            Assert.True(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(-20)).IsEmpty());
            Assert.True(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(5)).IsFull());
            Assert.True(new S2Cap(new S2Point(1, 0, 0), S1Angle.Infinity).IsFull());

            // Check that the default S2Cap is identical to Empty().
            var default_empty = S2Cap.Empty;

            Assert.True(default_empty.IsValid());
            Assert.True(default_empty.IsEmpty());
            Assert.Equal(empty.Center, default_empty.Center);
            Assert.Equal(empty.Height(), default_empty.Height());

            // Containment and intersection of empty and full caps.
            Assert.True(empty.Contains(empty));
            Assert.True(full.Contains(empty));
            Assert.True(full.Contains(full));
            Assert.False(empty.InteriorIntersects(empty));
            Assert.True(full.InteriorIntersects(full));
            Assert.False(full.InteriorIntersects(empty));

            // Singleton cap containing the x-axis.
            S2Cap xaxis = S2Cap.FromPoint(new S2Point(1, 0, 0));

            Assert.True(xaxis.Contains(new S2Point(1, 0, 0)));
            Assert.False(xaxis.Contains(new S2Point(1, 1e-20, 0)));
            Assert.Equal(0, xaxis.Radius.Radians());

            // Singleton cap containing the y-axis.
            S2Cap yaxis = S2Cap.FromPoint(new S2Point(0, 1, 0));

            Assert.False(yaxis.Contains(xaxis.Center));
            Assert.Equal(0, xaxis.Height());

            // Check that the complement of a singleton cap is the full cap.
            S2Cap xcomp = xaxis.Complement();

            Assert.True(xcomp.IsValid());
            Assert.True(xcomp.IsFull());
            Assert.True(xcomp.Contains(xaxis.Center));

            // Check that the complement of the complement is *not* the original.
            Assert.True(xcomp.Complement().IsValid());
            Assert.True(xcomp.Complement().IsEmpty());
            Assert.False(xcomp.Complement().Contains(xaxis.Center));

            // Check that very small caps can be represented accurately.
            // Here "kTinyRad" is small enough that unit vectors perturbed by this
            // amount along a tangent do not need to be renormalized.
            S2Cap tiny    = new(new S2Point(1, 2, 3).Normalize(), S1Angle.FromRadians(kTinyRad));
            var   tangent = tiny.Center.CrossProd(new S2Point(3, 2, 1)).Normalize();

            Assert.True(tiny.Contains(tiny.Center + 0.99 * kTinyRad * tangent));
            Assert.False(tiny.Contains(tiny.Center + 1.01 * kTinyRad * tangent));

            // Basic tests on a hemispherical cap.
            S2Cap hemi = S2Cap.FromCenterHeight(new S2Point(1, 0, 1).Normalize(), 1);

            Assert.Equal(-hemi.Center, hemi.Complement().Center);
            Assert.Equal(1, hemi.Complement().Height());
            Assert.True(hemi.Contains(new S2Point(1, 0, 0)));
            Assert.False(hemi.Complement().Contains(new S2Point(1, 0, 0)));
            Assert.True(hemi.Contains(new S2Point(1, 0, -(1 - kEps)).Normalize()));
            Assert.False(hemi.InteriorContains(new S2Point(1, 0, -(1 + kEps)).Normalize()));

            // A concave cap.  Note that the error bounds for point containment tests
            // increase with the cap angle, so we need to use a larger error bound
            // here.  (It would be painful to do this everywhere, but this at least
            // gives an example of how to compute the maximum error.)
            S2Point      center    = GetLatLngPoint(80, 10);
            S1ChordAngle radius    = new(S1Angle.FromDegrees(150));
            double       max_error =
                radius.GetS2PointConstructorMaxError() +
                radius.S1AngleConstructorMaxError +
                3 * S2.DoubleEpsilon;  // GetLatLngPoint() error
            S2Cap concave     = new(center, radius);
            S2Cap concave_min = new(center, radius.PlusError(-max_error));
            S2Cap concave_max = new(center, radius.PlusError(max_error));

            Assert.True(concave_max.Contains(GetLatLngPoint(-70, 10)));
            Assert.False(concave_min.Contains(GetLatLngPoint(-70, 10)));
            Assert.True(concave_max.Contains(GetLatLngPoint(-50, -170)));
            Assert.False(concave_min.Contains(GetLatLngPoint(-50, -170)));

            // Cap containment tests.
            Assert.False(empty.Contains(xaxis));
            Assert.False(empty.InteriorIntersects(xaxis));
            Assert.True(full.Contains(xaxis));
            Assert.True(full.InteriorIntersects(xaxis));
            Assert.False(xaxis.Contains(full));
            Assert.False(xaxis.InteriorIntersects(full));
            Assert.True(xaxis.Contains(xaxis));
            Assert.False(xaxis.InteriorIntersects(xaxis));
            Assert.True(xaxis.Contains(empty));
            Assert.False(xaxis.InteriorIntersects(empty));
            Assert.True(hemi.Contains(tiny));
            Assert.True(hemi.Contains(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.M_PI_4 - kEps))));
            Assert.False(hemi.Contains(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.M_PI_4 + kEps))));
            Assert.True(concave.Contains(hemi));
            Assert.True(concave.InteriorIntersects(hemi.Complement()));
            Assert.False(concave.Contains(S2Cap.FromCenterHeight(-concave.Center, 0.1)));
        }