예제 #1
0
    public void Test_LoopTestBase_GetAreaConsistentWithOrientation()
    {
        // Test that GetArea() returns an area near 0 for degenerate loops that
        // contain almost no points, and an area near 4*Pi for degenerate loops that
        // contain almost all points.

        const int kMaxVertices = 6;

        for (int i = 0; i < 50; ++i)
        {
            int num_vertices = 3 + S2Testing.Random.Uniform(kMaxVertices - 3 + 1);
            // Repeatedly choose N vertices that are exactly on the equator until we
            // find some that form a valid loop.
            S2PointLoopSpan loop = new();
            do
            {
                for (int i2 = 0; i2 < num_vertices; ++i2)
                {
                    // We limit longitude to the range [0, 90] to ensure that the loop is
                    // degenerate (as opposed to following the entire equator).
                    loop.Add(
                        S2LatLng.FromRadians(0, S2Testing.Random.RandDouble() * S2.M_PI_2).ToPoint());
                }
            } while (!new S2Loop(loop, S2Debug.DISABLE).IsValid());
            bool ccw = S2.IsNormalized(loop);
            // The error bound is sufficient for current tests but not guaranteed.
            _ = i + ": " + loop.ToDebugString();
            Assert2.Near(ccw ? 0 : S2.M_4_PI, S2.GetArea(loop), 1e-14);
            Assert.Equal(!ccw, new S2Loop(loop).Contains(new S2Point(0, 0, 1)));
        }
    }
예제 #2
0
    public void Test_LoopTestBase_GetCurvature()
    {
        Assert.Equal(-S2.M_2_PI, S2.GetCurvature(full_));

        Assert.Equal(S2.M_2_PI, S2.GetCurvature(v_loop_));
        CheckCurvatureInvariants(v_loop_);

        // This curvature should be computed exactly.
        Assert.Equal(0, S2.GetCurvature(north_hemi3_));
        CheckCurvatureInvariants(north_hemi3_);

        Assert2.Near(0, S2.GetCurvature(west_hemi_), 1e-15);
        CheckCurvatureInvariants(west_hemi_);

        // We don't have an easy way to estimate the curvature of these loops, but
        // we can still check that the expected invariants hold.
        CheckCurvatureInvariants(candy_cane_);
        CheckCurvatureInvariants(three_leaf_clover_);

        Assert2.DoubleEqual(S2.M_2_PI, S2.GetCurvature(line_triangle_));
        CheckCurvatureInvariants(line_triangle_);

        Assert2.DoubleEqual(S2.M_2_PI, S2.GetCurvature(skinny_chevron_));
        CheckCurvatureInvariants(skinny_chevron_);

        // Build a narrow spiral loop starting at the north pole.  This is designed
        // to test that the error in GetCurvature is linear in the number of
        // vertices even when the partial sum of the curvatures gets very large.
        // The spiral consists of two "arms" defining opposite sides of the loop.
        // This is a pathological loop that contains many long parallel edges.
        int             kArmPoints = 10000; // Number of vertices in each "arm"
        double          kArmRadius = 0.01;  // Radius of spiral.
        S2PointLoopSpan spiral     = new(2 * kArmPoints);

        spiral[kArmPoints] = new S2Point(0, 0, 1);
        for (int i = 0; i < kArmPoints; ++i)
        {
            double angle = (S2.M_2_PI / 3) * i;
            double x     = Math.Cos(angle);
            double y     = Math.Sin(angle);
            double r1    = i * kArmRadius / kArmPoints;
            double r2    = (i + 1.5) * kArmRadius / kArmPoints;
            spiral[kArmPoints - i - 1] = new S2Point(r1 * x, r1 * y, 1).Normalize();
            spiral[kArmPoints + i]     = new S2Point(r2 * x, r2 * y, 1).Normalize();
        }

        // Check that GetCurvature() is consistent with GetArea() to within the
        // error bound of the former.  We actually use a tiny fraction of the
        // worst-case error bound, since the worst case only happens when all the
        // roundoff errors happen in the same direction and this test is not
        // designed to achieve that.  The error in GetArea() can be ignored for the
        // purposes of this test since it is generally much smaller.
        Assert2.Near(
            S2.M_2_PI - S2.GetArea(spiral),
            S2.GetCurvature(spiral),
            0.01 * S2.GetCurvatureMaxError(spiral));
    }
예제 #3
0
    public void Test_GetArea_TinyShellAndHole()
    {
        // A square about 20 um on each side with a hole 10 um on each side.
        double side = S1Angle.FromDegrees(1e-10).Radians;

        Assert.Equal(3 * side * side, S2.GetArea(MakeLaxPolygonOrDie(
                                                     "0:0, 0:2e-10, 2e-10:2e-10, 2e-10:0; " +
                                                     "0.5e-10:0.5e-10, 1.5e-10:0.5e-10, 1.5e-10:1.5e-10, 0.5e-10:1.5e-10")));
    }
예제 #4
0
    public void Test_GetArea_TwoTinyShells()
    {
        // Two small squares with sides about 10 um (micrometers) long.
        double side = S1Angle.FromDegrees(1e-10).Radians;

        Assert.Equal(2 * side * side, S2.GetArea(MakeLaxPolygonOrDie(
                                                     "0:0, 0:1e-10, 1e-10:1e-10, 1e-10:0; " +
                                                     "0:0, 0:-1e-10, -1e-10:-1e-10, -1e-10:0")));
    }
예제 #5
0
    private static void TestAreaConsistentWithCurvature(S2PointLoopSpan loop)
    {
        // Check that the area computed using GetArea() is consistent with the loop
        // curvature.  According to the Gauss-Bonnet theorem, the area of the loop
        // equals 2*Pi minus its curvature.
        double area       = S2.GetArea(loop);
        double gauss_area = S2.M_2_PI - S2.GetCurvature(loop);

        // The error bound below is sufficient for current tests but not guaranteed.
        _ = loop.ToDebugString();
        Assert.True(Math.Abs(area - gauss_area) <= 1e-14);
    }
예제 #6
0
    // 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);
    }
예제 #7
0
    public void Test_LoopTestBase_GetAreaAndCentroid()
    {
        Assert.Equal(S2.M_4_PI, S2.GetArea(full_));
        Assert.Equal(S2Point.Empty, S2.GetCentroid(full_));

        Assert2.DoubleEqual(S2.GetArea(north_hemi_), S2.M_2_PI);
        Assert2.Near(S2.M_2_PI, S2.GetArea(east_hemi_), 1e-12);

        // Construct spherical caps of random height, and approximate their boundary
        // with closely spaces vertices.  Then check that the area and centroid are
        // correct.
        for (int iter = 0; iter < 50; ++iter)
        {
            // Choose a coordinate frame for the spherical cap.
            S2Testing.GetRandomFrame(out var x, out var y, out var z);

            // Given two points at latitude phi and whose longitudes differ by dtheta,
            // the geodesic between the two points has a maximum latitude of
            // atan(tan(phi) / cos(dtheta/2)).  This can be derived by positioning
            // the two points at (-dtheta/2, phi) and (dtheta/2, phi).
            //
            // We want to position the vertices close enough together so that their
            // maximum distance from the boundary of the spherical cap is kMaxDist.
            // Thus we want Math.Abs(atan(tan(phi) / cos(dtheta/2)) - phi) <= kMaxDist.
            const double kMaxDist   = 1e-6;
            double       height     = 2 * S2Testing.Random.RandDouble();
            double       phi        = Math.Asin(1 - height);
            double       max_dtheta = 2 * Math.Acos(Math.Tan(Math.Abs(phi)) / Math.Tan(Math.Abs(phi) + kMaxDist));
            max_dtheta = Math.Min(Math.PI, max_dtheta);  // At least 3 vertices.

            S2PointLoopSpan loop = new();
            for (double theta = 0; theta < S2.M_2_PI;
                 theta += S2Testing.Random.RandDouble() * max_dtheta)
            {
                loop.Add(Math.Cos(theta) * Math.Cos(phi) * x +
                         Math.Sin(theta) * Math.Cos(phi) * y +
                         Math.Sin(phi) * z);
            }
            double  area          = S2.GetArea(loop);
            S2Point centroid      = S2.GetCentroid(loop);
            double  expected_area = S2.M_2_PI * height;
            Assert.True(Math.Abs(area - expected_area) <= S2.M_2_PI * kMaxDist);
            S2Point expected_centroid = expected_area * (1 - 0.5 * height) * z;
            Assert.True((centroid - expected_centroid).Norm() <= 2 * kMaxDist);
        }
    }
예제 #8
0
 public void Test_GetArea_FullPolygon()
 {
     Assert.Equal(4 * Math.PI, S2.GetArea(MakeLaxPolygonOrDie("full")));
     Assert.Equal(4 * Math.PI,
                  S2.GetArea(new S2Polygon.OwningShape(MakePolygonOrDie("full"))));
 }
예제 #9
0
 public void Test_GetArea_EmptyPolygon()
 {
     Assert.Equal(0.0, S2.GetArea(MakeLaxPolygonOrDie("empty")));
 }
예제 #10
0
 public void Test_GetArea_WrongDimension()
 {
     Assert.Equal(0.0, S2.GetArea(MakeIndexOrDie("0:0 # #").Shape(0)));
     Assert.Equal(0.0, S2.GetArea(MakeLaxPolylineOrDie("0:0, 0:1, 1:0")));
 }