예제 #1
0
    public void Test_RectBounder_MaxLatitudeSimple()
    {
        // Check cases where the min/max latitude is attained at a vertex.
        var kCubeLat = Math.Asin(1 / Math.Sqrt(3));                // 35.26 degrees

        Assert.True(GetEdgeBound(1, 1, 1, 1, -1, -1).ApproxEquals( // NOLINT
                        new S2LatLngRect(new R1Interval(-kCubeLat, kCubeLat),
                                         new S1Interval(-S2.M_PI_4, S2.M_PI_4)), kRectError));
        Assert.True(GetEdgeBound(1, -1, 1, 1, 1, -1).ApproxEquals(  // NOLINT
                        new S2LatLngRect(new R1Interval(-kCubeLat, kCubeLat),
                                         new S1Interval(-S2.M_PI_4, S2.M_PI_4)), kRectError));

        // Check cases where the min/max latitude occurs in the edge interior.
        // These tests expect the result to be pretty close to the middle of the
        // allowable error range (i.e., by adding 0.5 * kRectError).

        // Max latitude, CW edge
        Assert2.DoubleEqual(S2.M_PI_4 + 0.5 * kRectError.LatRadians,
                            GetEdgeBound(1, 1, 1, 1, -1, 1).Lat.Hi);
        // Max latitude, CCW edge
        Assert2.DoubleEqual(S2.M_PI_4 + 0.5 * kRectError.LatRadians,
                            GetEdgeBound(1, -1, 1, 1, 1, 1).Lat.Hi); // NOLINT
        // Min latitude, CW edge
        Assert2.DoubleEqual(-S2.M_PI_4 - 0.5 * kRectError.LatRadians,
                            GetEdgeBound(1, -1, -1, -1, -1, -1).Lat.Lo); // NOLINT
        // Min latitude, CCW edge
        Assert2.DoubleEqual(-S2.M_PI_4 - 0.5 * kRectError.LatRadians,
                            GetEdgeBound(-1, 1, -1, -1, -1, -1).Lat.Lo); // NOLINT

        // Check cases where the edge passes through one of the poles.
        Assert.Equal(S2.M_PI_2, GetEdgeBound(.3, .4, 1, -.3, -.4, 1).Lat.Hi);    // NOLINT
        Assert.Equal(-S2.M_PI_2, GetEdgeBound(.3, .4, -1, -.3, -.4, -1).Lat.Lo); // NOLINT
    }
예제 #2
0
    public void Test_S2LatLngRect_GetDirectHausdorffDistancePointToRect()
    {
        // The Hausdorff distance from a point to a rect should be the same as its
        // distance to the rect.
        S2LatLngRect a1 = PointRectFromDegrees(5, 8);
        S2LatLngRect a2 = PointRectFromDegrees(90, 10);  // north pole

        S2LatLngRect b = RectFromDegrees(-85, -50, -80, 10);

        Assert2.DoubleEqual(a1.GetDirectedHausdorffDistance(b).Radians,
                            a1.GetDistance(b).Radians);
        Assert2.DoubleEqual(a2.GetDirectedHausdorffDistance(b).Radians,
                            a2.GetDistance(b).Radians);

        b = RectFromDegrees(4, -10, 80, 10);
        Assert2.DoubleEqual(a1.GetDirectedHausdorffDistance(b).Radians,
                            a1.GetDistance(b).Radians);
        Assert2.DoubleEqual(a2.GetDirectedHausdorffDistance(b).Radians,
                            a2.GetDistance(b).Radians);

        b = RectFromDegrees(70, 170, 80, -170);
        Assert2.DoubleEqual(a1.GetDirectedHausdorffDistance(b).Radians,
                            a1.GetDistance(b).Radians);
        Assert2.DoubleEqual(a2.GetDirectedHausdorffDistance(b).Radians,
                            a2.GetDistance(b).Radians);
    }
예제 #3
0
    public void Test_S1ChordAngle_Arithmetic()
    {
        S1ChordAngle zero     = S1ChordAngle.Zero;
        S1ChordAngle degree30 = S1ChordAngle.FromDegrees(30);   //0.26794919243112264
        S1ChordAngle degree60 = S1ChordAngle.FromDegrees(60);   //0.99999999999999978
        S1ChordAngle degree90 = S1ChordAngle.FromDegrees(90);
        //1.9999999999999996
        //2.0000000000000004
        S1ChordAngle degree120 = S1ChordAngle.FromDegrees(120); //2.9999999999999996
        S1ChordAngle degree180 = S1ChordAngle.Straight;

        Assert.Equal(0, (zero + zero).Degrees());
        Assert.Equal(0, (zero - zero).Degrees());
        Assert.Equal(0, (degree60 - degree60).Degrees());
        Assert.Equal(0, (degree180 - degree180).Degrees());
        Assert.Equal(0, (zero - degree60).Degrees());
        Assert.Equal(0, (degree30 - degree90).Degrees());
        Assert2.DoubleEqual(60, (degree60 + zero).Degrees());
        Assert2.DoubleEqual(60, (degree60 - zero).Degrees());
        Assert2.DoubleEqual(60, (zero + degree60).Degrees());
        Assert2.DoubleEqual(90, (degree30 + degree60).Degrees());
        Assert2.DoubleEqual(90, (degree60 + degree30).Degrees());
        Assert2.DoubleEqual(60, (degree90 - degree30).Degrees());
        Assert2.DoubleEqual(30, (degree90 - degree60).Degrees(), 5);
        Assert.Equal(180, (degree180 + zero).Degrees());
        Assert.Equal(180, (degree180 - zero).Degrees());
        Assert.Equal(180, (degree90 + degree90).Degrees());
        Assert.Equal(180, (degree120 + degree90).Degrees());
        Assert.Equal(180, (degree120 + degree120).Degrees());
        Assert.Equal(180, (degree30 + degree180).Degrees());
        Assert.Equal(180, (degree180 + degree180).Degrees());
    }
예제 #4
0
 public void Test_S1Angle_Trigonometry()
 {
     // Spot check a few angles to ensure that the correct function is called.
     Assert2.DoubleEqual(1, S1Angle.FromDegrees(0).Cos());
     Assert2.DoubleEqual(1, S1Angle.FromDegrees(90).Sin());
     Assert2.DoubleEqual(1, S1Angle.FromDegrees(45).Tan());
 }
예제 #5
0
    public void Test_GetPerimeter_MoreThanTwoPi()
    {
        // Make sure that GetPerimeter doesn't use S1ChordAngle, which can only
        // represent distances up to 2*Pi.
        var loop = ParsePointsOrDie("0:0, 0:90, 0:180, 90:0, 0:-90").ToArray();

        Assert2.DoubleEqual(5 * S2.M_PI_2, S2.GetPerimeter(loop).Radians);
    }
예제 #6
0
 public void Test_S1ChordAngle_FromLength2()
 {
     Assert.Equal(0, S1ChordAngle.FromLength2(0).Degrees());
     Assert2.DoubleEqual(60, S1ChordAngle.FromLength2(1).Degrees());
     Assert2.DoubleEqual(90, S1ChordAngle.FromLength2(2).Degrees());
     Assert.Equal(180, S1ChordAngle.FromLength2(4).Degrees());
     Assert.Equal(180, S1ChordAngle.FromLength2(5).Degrees());
 }
예제 #7
0
 public void Test_S1Angle_NormalizeCorrectlyCanonicalizesAngles()
 {
     Assert2.DoubleEqual(0.0, S1Angle.FromDegrees(360.0).Normalize().GetDegrees());
     Assert2.DoubleEqual(-90.0, S1Angle.FromDegrees(-90.0).Normalize().GetDegrees());
     Assert2.DoubleEqual(180.0, S1Angle.FromDegrees(-180.0).Normalize().GetDegrees());
     Assert2.DoubleEqual(180.0, S1Angle.FromDegrees(180.0).Normalize().GetDegrees());
     Assert2.DoubleEqual(180.0, S1Angle.FromDegrees(540.0).Normalize().GetDegrees());
     Assert2.DoubleEqual(90.0, S1Angle.FromDegrees(-270.0).Normalize().GetDegrees());
 }
예제 #8
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));
    }
예제 #9
0
 public void Test_S1ChordAngle_ToFromS1Angle()
 {
     Assert.Equal(0, new S1ChordAngle(S1Angle.Zero).Radians());
     Assert.Equal(4, new S1ChordAngle(S1Angle.FromRadians(Math.PI)).Length2);
     Assert.Equal(Math.PI, new S1ChordAngle(S1Angle.FromRadians(Math.PI)).Radians());
     Assert.Equal(S1Angle.Infinity, new S1ChordAngle(S1Angle.Infinity).ToAngle());
     Assert.True(new S1ChordAngle(S1Angle.FromRadians(-1)).Radians() < 0);
     Assert2.DoubleEqual(1.0, new S1ChordAngle(S1Angle.FromRadians(1.0)).Radians());
 }
예제 #10
0
 public void Test_S1IntervalTestBase_GetLength()
 {
     Assert.Equal(quad12.GetLength(), Math.PI);
     Assert.Equal(0, pi.GetLength());
     Assert.Equal(0, mipi.GetLength());
     Assert2.DoubleEqual(quad123.GetLength(), 1.5 * Math.PI);
     Assert.Equal(Math.Abs(quad23.GetLength()), Math.PI);
     Assert.Equal(full.GetLength(), S2.M_2_PI);
     Assert.True(empty.GetLength() < 0);
 }
예제 #11
0
 public void Test_S1IntervalTestBase_GetCenter()
 {
     Assert.Equal(quad12.GetCenter(), S2.M_PI_2);
     Assert2.DoubleEqual(new S1Interval(3.1, 2.9).GetCenter(), 3.0 - Math.PI);
     Assert2.DoubleEqual(new S1Interval(-2.9, -3.1).GetCenter(), Math.PI - 3.0);
     Assert2.DoubleEqual(new S1Interval(2.1, -2.1).GetCenter(), Math.PI);
     Assert.Equal(pi.GetCenter(), Math.PI);
     Assert.Equal(mipi.GetCenter(), Math.PI);
     Assert.Equal(Math.Abs(quad23.GetCenter()), Math.PI);
     Assert2.DoubleEqual(quad123.GetCenter(), 0.75 * Math.PI);
 }
예제 #12
0
    public void Test_S2LatLngRect_Accessors()
    {
        // Check various accessor methods.
        S2LatLngRect d1 = RectFromDegrees(-90, 0, -45, 180);

        Assert2.DoubleEqual(d1.LatLo().GetDegrees(), -90);
        Assert2.DoubleEqual(d1.LatHi().GetDegrees(), -45);
        Assert2.DoubleEqual(d1.LngLo().GetDegrees(), 0);
        Assert2.DoubleEqual(d1.LngHi().GetDegrees(), 180);
        Assert.Equal(d1.Lat, new(-S2.M_PI_2, -S2.M_PI_4));
        Assert.Equal(d1.Lng, new(0, Math.PI));
    }
예제 #13
0
        public void Test_EdgeTrueCentroid_SemiEquator()
        {
            // Test the centroid of polyline ABC that follows the equator and consists
            // of two 90 degree edges (i.e., C = -A).  The centroid (multiplied by
            // length) should point toward B and have a norm of 2.0.  (The centroid
            // itself has a norm of 2/Pi, and the total edge length is Pi.)
            S2Point a = new(0, -1, 0), b = new(1, 0, 0), c = new(0, 1, 0);
            S2Point centroid = S2Centroid.TrueCentroid(a, b) + S2Centroid.TrueCentroid(b, c);

            Assert.True(S2.ApproxEquals(b, centroid.Normalize()));
            Assert2.DoubleEqual(2.0, centroid.Norm());
        }
예제 #14
0
 public void Test_S1ChordAngle_TwoPointConstructor()
 {
     for (int iter = 0; iter < 100; ++iter)
     {
         S2Testing.GetRandomFrame(out var x, out var y, out var z);
         Assert.Equal(S1Angle.Zero, new S1ChordAngle(z, z).ToAngle());
         Assert2.Near(Math.PI, new S1ChordAngle(-z, z).Radians(), 1e-7);
         Assert2.DoubleEqual(S2.M_PI_2, new S1ChordAngle(x, z).Radians());
         S2Point w = (y + z).Normalize();
         Assert2.DoubleEqual(S2.M_PI_4, new S1ChordAngle(w, z).Radians());
     }
 }
예제 #15
0
 public void Test_S1Angle_E5E6E7Representations()
 {
     // Check that E5/E6/E7 representations work as expected.
     Assert2.DoubleEqual(S1Angle.FromDegrees(-45).Radians,
                         S1Angle.FromE5(-4500000).Radians);
     Assert2.DoubleEqual(S1Angle.FromDegrees(-60).Radians,
                         S1Angle.FromE6(-60000000).Radians);
     Assert2.DoubleEqual(S1Angle.FromDegrees(75).Radians,
                         S1Angle.FromE7(750000000).Radians);
     Assert.Equal(-17256123, S1Angle.FromDegrees(-172.56123).E5());
     Assert.Equal(12345678, S1Angle.FromDegrees(12.345678).E6());
     Assert.Equal(-123456789, S1Angle.FromDegrees(-12.3456789).E7());
 }
예제 #16
0
        public void Test_S2Cell_TestFaces()
        {
            var edge_counts   = new Dictionary <S2Point, int>();
            var vertex_counts = new Dictionary <S2Point, int>();

            for (int face = 0; face < 6; ++face)
            {
                S2CellId id   = S2CellId.FromFace(face);
                S2Cell   cell = new(id);
                Assert.Equal(id, cell.Id);
                Assert.Equal(face, cell.Face);
                Assert.Equal(0, cell.Level);
                // Top-level faces have alternating orientations to get RHS coordinates.
                Assert.Equal(face & S2.kSwapMask, cell.Orientation);
                Assert.False(cell.IsLeaf());
                for (int k = 0; k < 4; ++k)
                {
                    var key = cell.EdgeRaw(k);
                    if (edge_counts.ContainsKey(key))
                    {
                        edge_counts[key] += 1;
                    }
                    else
                    {
                        edge_counts[key] = 1;
                    }
                    var key2 = cell.VertexRaw(k);
                    if (vertex_counts.ContainsKey(key2))
                    {
                        vertex_counts[key2] += 1;
                    }
                    else
                    {
                        vertex_counts[key2] = 1;
                    }
                    Assert2.DoubleEqual(0.0, cell.VertexRaw(k).DotProd(key));
                    Assert2.DoubleEqual(0.0, cell.VertexRaw(k + 1).DotProd(key));
                    Assert2.DoubleEqual(1.0, cell.VertexRaw(k).CrossProd(cell.VertexRaw(k + 1))
                                        .Normalize().DotProd(cell.Edge(k)));
                }
            }
            // Check that edges have multiplicity 2 and vertices have multiplicity 3.
            foreach (var p in edge_counts)
            {
                Assert.Equal(2, p.Value);
            }
            foreach (var p in vertex_counts)
            {
                Assert.Equal(3, p.Value);
            }
        }
예제 #17
0
    public void Test_S2LatLng_TestBasic()
    {
        S2LatLng ll_rad = S2LatLng.FromRadians(S2.M_PI_4, S2.M_PI_2);

        Assert.Equal(S2.M_PI_4, ll_rad.LatRadians);
        Assert.Equal(S2.M_PI_2, ll_rad.LngRadians);
        Assert.True(ll_rad.IsValid());
        S2LatLng ll_deg = S2LatLng.FromDegrees(45, 90);

        Assert.Equal(ll_rad, ll_deg);
        Assert.True(ll_deg.IsValid());
        Assert.False(S2LatLng.FromDegrees(-91, 0).IsValid());
        Assert.False(S2LatLng.FromDegrees(0, 181).IsValid());

        S2LatLng bad = S2LatLng.FromDegrees(120, 200);

        Assert.False(bad.IsValid());
        S2LatLng better = bad.Normalized();

        Assert.True(better.IsValid());
        Assert.Equal(S1Angle.FromDegrees(90), better.Lat());
        Assert2.DoubleEqual(S1Angle.FromDegrees(-160).Radians, better.LngRadians);

        bad = S2LatLng.FromDegrees(-100, -360);
        Assert.False(bad.IsValid());
        better = bad.Normalized();
        Assert.True(better.IsValid());
        Assert.Equal(S1Angle.FromDegrees(-90), better.Lat());
        Assert2.DoubleEqual(0.0, better.LngRadians);

        Assert.True((S2LatLng.FromDegrees(10, 20) + S2LatLng.FromDegrees(20, 30)).
                    ApproxEquals(S2LatLng.FromDegrees(30, 50)));
        Assert.True((S2LatLng.FromDegrees(10, 20) - S2LatLng.FromDegrees(20, 30)).
                    ApproxEquals(S2LatLng.FromDegrees(-10, -10)));
        Assert.True((0.5 * S2LatLng.FromDegrees(10, 20)).
                    ApproxEquals(S2LatLng.FromDegrees(5, 10)));

        // Check that Invalid() returns an invalid point.
        S2LatLng invalid = S2LatLng.Invalid;

        Assert.False(invalid.IsValid());

        // Check that the default constructor sets latitude and longitude to 0.
        S2LatLng default_ll = S2LatLng.Center;

        Assert.True(default_ll.IsValid());
        Assert.Equal(0, default_ll.LatRadians);
        Assert.Equal(0, default_ll.LngRadians);
    }
예제 #18
0
    public void Test_S2LatLng_TestConversion()
    {
        // Test special cases: poles, "date line"
        Assert2.DoubleEqual(90.0, new S2LatLng(S2LatLng.FromDegrees(90.0, 65.0).ToPoint()).Lat().GetDegrees());
        Assert.Equal(-S2.M_PI_2, new S2LatLng(S2LatLng.FromRadians(-S2.M_PI_2, 1).ToPoint()).LatRadians);
        Assert2.DoubleEqual(180.0, Math.Abs(new S2LatLng(S2LatLng.FromDegrees(12.2, 180.0).ToPoint()).Lng().GetDegrees()));
        Assert.Equal(Math.PI, Math.Abs(new S2LatLng(S2LatLng.FromRadians(0.1, -Math.PI).ToPoint()).LngRadians));

        // Test a bunch of random points.
        for (int i = 0; i < 100000; ++i)
        {
            S2Point p = S2Testing.RandomPoint();
            Assert.True(S2.ApproxEquals(p, new S2LatLng(p).ToPoint()));
        }
    }
예제 #19
0
 public void Test_S1Angle_E6E7RepresentationsUnsigned()
 {
     // Check that unsigned E6/E7 representations work as expected.
     Assert2.DoubleEqual(
         S1Angle.FromDegrees(60).Radians,
         S1Angle.FromUnsignedE6((UInt32)60000000).Radians);
     Assert2.DoubleEqual(
         S1Angle.FromDegrees(-60).Radians,
         S1Angle.FromUnsignedE6(0xFFFFFFFFFC6C7900U).Radians); // (UInt32)-60000000
     Assert2.DoubleEqual(
         S1Angle.FromDegrees(75).Radians,
         S1Angle.FromUnsignedE7((UInt32)750000000).Radians);
     Assert2.DoubleEqual(
         S1Angle.FromDegrees(-75).Radians,
         S1Angle.FromUnsignedE7(0xFFFFFFFFD34BE880U).Radians); // (UInt32)-750000000
 }
예제 #20
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);
        }
    }
예제 #21
0
 public void Test_UVNorms()
 {
     // Check that GetUNorm and GetVNorm compute right-handed normals for
     // an edge in the increasing U or V direction.
     for (int face = 0; face < 6; ++face)
     {
         for (double x = -1; x <= 1; x += 1 / 1024.0)
         {
             Assert2.DoubleEqual(S2.FaceUVtoXYZ(face, x, -1)
                                 .CrossProd(S2.FaceUVtoXYZ(face, x, 1))
                                 .Angle(S2.GetUNorm(face, x)), 0);
             Assert2.DoubleEqual(S2.FaceUVtoXYZ(face, -1, x)
                                 .CrossProd(S2.FaceUVtoXYZ(face, 1, x))
                                 .Angle(S2.GetVNorm(face, x)), 0);
         }
     }
 }
예제 #22
0
    public void Test_S2_Frames()
    {
        var z = new S2Point(0.2, 0.5, -3.3).Normalize();
        var m = S2.GetFrame(z);

        Assert.True(S2.ApproxEquals(m.Col(2), z));
        Assert.True(m.Col(0).IsUnitLength());
        Assert.True(m.Col(1).IsUnitLength());
        Assert2.DoubleEqual(m.Det(), 1);

        Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(0)), new(1, 0, 0)));
        Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(1)), new(0, 1, 0)));
        Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(2)), new(0, 0, 1)));

        Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(1, 0, 0)), m.Col(0)));
        Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(0, 1, 0)), m.Col(1)));
        Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(0, 0, 1)), m.Col(2)));
    }
예제 #23
0
    public void Test_S1Angle_ArithmeticOperationsOnAngles()
    {
        Assert2.DoubleEqual(0.3, S1Angle.FromRadians(-0.3).Abs());
        Assert2.DoubleEqual(-0.1, (-S1Angle.FromRadians(0.1)).Radians);
        Assert2.DoubleEqual(0.4, (S1Angle.FromRadians(0.1) + S1Angle.FromRadians(0.3)).Radians);
        Assert2.DoubleEqual(-0.2, (S1Angle.FromRadians(0.1) - S1Angle.FromRadians(0.3)).Radians);
        Assert2.DoubleEqual(0.6, (2 * S1Angle.FromRadians(0.3)).Radians);
        Assert2.DoubleEqual(0.6, (S1Angle.FromRadians(0.3) * 2).Radians);
        Assert2.DoubleEqual(0.15, (S1Angle.FromRadians(0.3) / 2).Radians);
        Assert2.DoubleEqual(0.5, (S1Angle.FromRadians(0.3) / S1Angle.FromRadians(0.6)));

        S1Angle tmp = S1Angle.FromRadians(1.0);

        tmp += S1Angle.FromRadians(0.5);
        Assert2.DoubleEqual(1.5, tmp.Radians);
        tmp -= S1Angle.FromRadians(1.0);
        Assert2.DoubleEqual(0.5, tmp.Radians);
        tmp *= 5;
        Assert2.DoubleEqual(2.5, tmp.Radians);
        tmp /= 2;
        Assert2.DoubleEqual(1.25, tmp.Radians);
    }
예제 #24
0
 public void Test_S1ChordAngle_Right()
 {
     Assert2.DoubleEqual(90, S1ChordAngle.Right.Degrees());
 }
예제 #25
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)));
        }
예제 #26
0
    public void Test_GetPerimeter_Octant()
    {
        var loop = ParsePointsOrDie("0:0, 0:90, 90:0");

        Assert2.DoubleEqual(3 * S2.M_PI_2, S2.GetPerimeter(loop.ToArray()).Radians);
    }
예제 #27
0
 public void Test_S1Angle_ConstructorsThatMeasureAngles()
 {
     Assert2.DoubleEqual(S2.M_PI_2, new S1Angle(new S2Point(1, 0, 0), new S2Point(0, 0, 2)).Radians);
     Assert2.DoubleEqual(0.0, new S1Angle(new S2Point(1, 0, 0), new S2Point(1, 0, 0)).Radians);
     Assert2.Near(50.0, new S1Angle(S2LatLng.FromDegrees(20, 20), S2LatLng.FromDegrees(70, 20)).GetDegrees(), 1e-13);
 }
예제 #28
0
 public void Test_S2LatLngRect_Area()
 {
     Assert.Equal(0.0, S2LatLngRect.Empty.Area());
     Assert2.DoubleEqual(S2.M_4_PI, S2LatLngRect.Full.Area());
     Assert2.DoubleEqual(S2.M_PI_2, RectFromDegrees(0, 0, 90, 90).Area());
 }
예제 #29
0
    private void TestFaceClipping(S2Point a_raw, S2Point b_raw)
    {
        S2Point a = a_raw.Normalize();
        S2Point b = b_raw.Normalize();

        // First we test GetFaceSegments.
        FaceSegmentVector segments = new();

        GetFaceSegments(a, b, segments);
        int n = segments.Count;

        Assert.True(n >= 1);

        var msg = new StringBuilder($"\nA={a_raw}\nB={b_raw}\nN={S2.RobustCrossProd(a, b)}\nSegments:\n");
        int i1  = 0;

        foreach (var s in segments)
        {
            msg.AppendLine($"{i1++}: face={s.face}, a={s.a}, b={s.b}");
        }
        _logger.WriteLine(msg.ToString());

        R2Rect biunit        = new(new R1Interval(-1, 1), new R1Interval(-1, 1));
        var    kErrorRadians = kFaceClipErrorRadians;

        // The first and last vertices should approximately equal A and B.
        Assert.True(a.Angle(S2.FaceUVtoXYZ(segments[0].face, segments[0].a)) <=
                    kErrorRadians);
        Assert.True(b.Angle(S2.FaceUVtoXYZ(segments[n - 1].face, segments[n - 1].b)) <=
                    kErrorRadians);

        S2Point norm      = S2.RobustCrossProd(a, b).Normalize();
        S2Point a_tangent = norm.CrossProd(a);
        S2Point b_tangent = b.CrossProd(norm);

        for (int i = 0; i < n; ++i)
        {
            // Vertices may not protrude outside the biunit square.
            Assert.True(biunit.Contains(segments[i].a));
            Assert.True(biunit.Contains(segments[i].b));
            if (i == 0)
            {
                continue;
            }

            // The two representations of each interior vertex (on adjacent faces)
            // must correspond to exactly the same S2Point.
            Assert.NotEqual(segments[i - 1].face, segments[i].face);
            Assert.Equal(S2.FaceUVtoXYZ(segments[i - 1].face, segments[i - 1].b),
                         S2.FaceUVtoXYZ(segments[i].face, segments[i].a));

            // Interior vertices should be in the plane containing A and B, and should
            // be contained in the wedge of angles between A and B (i.e., the dot
            // products with a_tangent and b_tangent should be non-negative).
            S2Point p = S2.FaceUVtoXYZ(segments[i].face, segments[i].a).Normalize();
            Assert.True(Math.Abs(p.DotProd(norm)) <= kErrorRadians);
            Assert.True(p.DotProd(a_tangent) >= -kErrorRadians);
            Assert.True(p.DotProd(b_tangent) >= -kErrorRadians);
        }

        // Now we test ClipToPaddedFace (sometimes with a padding of zero).  We do
        // this by defining an (x,y) coordinate system for the plane containing AB,
        // and converting points along the great circle AB to angles in the range
        // [-Pi, Pi].  We then accumulate the angle intervals spanned by each
        // clipped edge; the union over all 6 faces should approximately equal the
        // interval covered by the original edge.
        double     padding = S2Testing.Random.OneIn(10) ? 0.0 : 1e-10 * Math.Pow(1e-5, S2Testing.Random.RandDouble());
        S2Point    x_axis = a, y_axis = a_tangent;
        S1Interval expected_angles = new(0, a.Angle(b));
        S1Interval max_angles      = expected_angles.Expanded(kErrorRadians);
        S1Interval actual_angles   = new();

        for (int face = 0; face < 6; ++face)
        {
            if (ClipToPaddedFace(a, b, face, padding, out var a_uv, out var b_uv))
            {
                S2Point a_clip = S2.FaceUVtoXYZ(face, a_uv).Normalize();
                S2Point b_clip = S2.FaceUVtoXYZ(face, b_uv).Normalize();
                Assert.True(Math.Abs(a_clip.DotProd(norm)) <= kErrorRadians);
                Assert.True(Math.Abs(b_clip.DotProd(norm)) <= kErrorRadians);
                if (a_clip.Angle(a) > kErrorRadians)
                {
                    Assert2.DoubleEqual(1 + padding, Math.Max(Math.Abs(a_uv[0]), Math.Abs(a_uv[1])));
                }
                if (b_clip.Angle(b) > kErrorRadians)
                {
                    Assert2.DoubleEqual(1 + padding, Math.Max(Math.Abs(b_uv[0]), Math.Abs(b_uv[1])));
                }
                double a_angle = Math.Atan2(a_clip.DotProd(y_axis), a_clip.DotProd(x_axis));
                double b_angle = Math.Atan2(b_clip.DotProd(y_axis), b_clip.DotProd(x_axis));
                // Rounding errors may cause b_angle to be slightly less than a_angle.
                // We handle this by constructing the interval with FromPointPair(),
                // which is okay since the interval length is much less than Math.PI.
                S1Interval face_angles = S1Interval.FromPointPair(a_angle, b_angle);
                Assert.True(max_angles.Contains(face_angles));
                actual_angles = actual_angles.Union(face_angles);
            }
        }
        Assert.True(actual_angles.Expanded(kErrorRadians).Contains(expected_angles));
    }