Exemple #1
0
    // This function assumes that GetDirectedHausdorffDistance() always returns
    // a distance from some point in a to b. So the function mainly tests whether
    // the returned distance is large enough, and only does a weak test on whether
    // it is small enough.
    private static void VerifyGetDirectedHausdorffDistance(S2LatLngRect a, S2LatLngRect b)
    {
        S1Angle hausdorff_distance = a.GetDirectedHausdorffDistance(b);

        const double kResolution  = 0.1;
        S1Angle      max_distance = S1Angle.Zero;

        int sample_size_on_lat =
            (int)(a.Lat.GetLength() / kResolution) + 1;
        int sample_size_on_lng =
            (int)(a.Lng.GetLength() / kResolution) + 1;
        double delta_on_lat = a.Lat.GetLength() / sample_size_on_lat;
        double delta_on_lng = a.Lng.GetLength() / sample_size_on_lng;

        double lng = a.Lng.Lo;

        for (int i = 0; i <= sample_size_on_lng; ++i, lng += delta_on_lng)
        {
            double lat = a.Lat.Lo;
            for (int j = 0; j <= sample_size_on_lat; ++j, lat += delta_on_lat)
            {
                S2LatLng latlng        = S2LatLng.FromRadians(lat, lng).Normalized();
                S1Angle  distance_to_b = b.GetDistance(latlng);

                if (distance_to_b >= max_distance)
                {
                    max_distance = distance_to_b;
                }
            }
        }

        Assert.True(max_distance.Radians <= hausdorff_distance.Radians + 1e-10);
        Assert.True(max_distance.Radians >= hausdorff_distance.Radians - kResolution);
    }
Exemple #2
0
    private static void TestIntervalOps(S2LatLngRect x, S2LatLngRect y, string expected_relation, S2LatLngRect expected_union, S2LatLngRect expected_intersection)
    {
        // Test all of the interval operations on the given pair of intervals.
        // "expected_relation" is a sequence of "T" and "F" characters corresponding
        // to the expected results of Contains(), InteriorContains(), Intersects(),
        // and InteriorIntersects() respectively.

        Assert.Equal(x.Contains(y), expected_relation[0] == 'T');
        Assert.Equal(x.InteriorContains(y), expected_relation[1] == 'T');
        Assert.Equal(x.Intersects(y), expected_relation[2] == 'T');
        Assert.Equal(x.InteriorIntersects(y), expected_relation[3] == 'T');

        Assert.Equal(x.Contains(y), x.Union(y) == x);
        Assert.Equal(x.Intersects(y), !x.Intersection(y).IsEmpty());

        Assert.Equal(x.Union(y), expected_union);
        Assert.Equal(x.Intersection(y), expected_intersection);

        if (y.Size() == S2LatLng.FromRadians(0, 0))
        {
            S2LatLngRect r = x;
            r.AddPoint(y.Lo());
            Assert.Equal(r, expected_union);
        }
    }
Exemple #3
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);
            }
        }
    }
        public void testIntervalOps(S2LatLngRect x, S2LatLngRect y, String expectedRelation,
                                    S2LatLngRect expectedUnion, S2LatLngRect expectedIntersection)
        {
            // Test all of the interval operations on the given pair of intervals.
            // "expected_relation" is a sequence of "T" and "F" characters corresponding
            // to the expected results of Contains(), InteriorContains(), Intersects(),
            // and InteriorIntersects() respectively.

            assertEquals(x.Contains(y), expectedRelation[0] == 'T');
            assertEquals(x.InteriorContains(y), expectedRelation[1] == 'T');
            assertEquals(x.Intersects(y), expectedRelation[2] == 'T');
            assertEquals(x.InteriorIntersects(y), expectedRelation[3] == 'T');

            assertEquals(x.Contains(y), x.Union(y).Equals(x));
            assertEquals(x.Intersects(y), !x.Intersection(y).IsEmpty);

            assertTrue(x.Union(y).Equals(expectedUnion));
            assertTrue(x.Intersection(y).Equals(expectedIntersection));

            if (y.Size == S2LatLng.FromRadians(0, 0))
            {
                var r = x.AddPoint(y.Lo);
                assertTrue(r == expectedUnion);
            }
        }
Exemple #5
0
        public void testConversion()
        {
            // Test special cases: poles, "date line"
            assertDoubleNear(
                new S2LatLng(S2LatLng.FromDegrees(90.0, 65.0).ToPoint()).Lat.Degrees, 90.0);
            assertEquals(
                new S2LatLng(S2LatLng.FromRadians(-S2.PiOver2, 1).ToPoint()).Lat.Radians, -S2.PiOver2);
            assertDoubleNear(
                Math.Abs(new S2LatLng(S2LatLng.FromDegrees(12.2, 180.0).ToPoint()).Lng.Degrees), 180.0);
            assertEquals(
                Math.Abs(new S2LatLng(S2LatLng.FromRadians(0.1, -S2.Pi).ToPoint()).Lng.Radians),
                S2.Pi);

            // Test a bunch of random points.
            for (var i = 0; i < 100000; ++i)
            {
                var p = randomPoint();
                assertTrue(S2.ApproxEquals(p, new S2LatLng(p).ToPoint()));
            }

            // Test generation from E5
            var test = S2LatLng.FromE5(123456, 98765);

            assertDoubleNear(test.Lat.Degrees, 1.23456);
            assertDoubleNear(test.Lng.Degrees, 0.98765);
        }
    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)));
        }
    }
    // Returns the bounding rectangle of the edge chain that connects the
    // vertices defined so far.  This bound satisfies the guarantee made
    // above, i.e. if the edge chain defines a loop, then the bound contains
    // the S2LatLng coordinates of all S2Points contained by the loop.
    public S2LatLngRect GetBound()
    {
        // To save time, we ignore numerical errors in the computed S2LatLngs while
        // accumulating the bounds and then account for them here.
        //
        // S2LatLng(S2Point) has a maximum error of 0.955 * S2Constants.DoubleEpsilon in latitude.
        // In the worst case, we might have rounded "inwards" when computing the
        // bound and "outwards" when computing the latitude of a contained point P,
        // therefore we expand the latitude bounds by 2 * S2Constants.DoubleEpsilon in each
        // direction.  (A more complex analysis shows that 1.5 * S2Constants.DoubleEpsilon is
        // enough, but the expansion amount should be a multiple of S2Constants.DoubleEpsilon in
        // order to avoid rounding errors during the expansion itself.)
        //
        // S2LatLng(S2Point) has a maximum error of S2Constants.DoubleEpsilon in longitude, which
        // is simply the maximum rounding error for results in the range [-Pi, Pi].
        // This is true because the Gnu implementation of atan2() comes from the IBM
        // Accurate Mathematical Library, which implements correct rounding for this
        // instrinsic (i.e., it returns the infinite precision result rounded to the
        // nearest representable value, with ties rounded to even values).  This
        // implies that we don't need to expand the longitude bounds at all, since
        // we only guarantee that the bound contains the *rounded* latitudes of
        // contained points.  The *true* latitudes of contained points may lie up to
        // S2Constants.DoubleEpsilon outside of the returned bound.
        S2LatLng kExpansion = S2LatLng.FromRadians(2 * S2.DoubleEpsilon, 0);

        return(bound_.Expanded(kExpansion).PolarClosure());
    }
Exemple #8
0
    public void Test_S2LatLngRect_GetCenterSize()
    {
        S2LatLngRect r1 = new(new R1Interval(0, S2.M_PI_2), new S1Interval(-Math.PI, 0));

        Assert.Equal(r1.Center(), S2LatLng.FromRadians(S2.M_PI_4, -S2.M_PI_2));
        Assert.Equal(r1.Size(), S2LatLng.FromRadians(S2.M_PI_2, Math.PI));
        Assert.True(S2LatLngRect.Empty.Size().LatRadians < 0);
        Assert.True(S2LatLngRect.Empty.Size().LngRadians < 0);
    }
Exemple #9
0
    public override S2LatLng ToLatLng(R2Point p)
    {
        // This formula is more accurate near zero than the atan(exp()) version.
        double x = to_radians_ * Math.IEEERemainder(p.X, x_wrap_);
        double k = Math.Exp(2 * to_radians_ * p.Y);
        double y = double.IsInfinity(k) ? S2.M_PI_2 : Math.Asin((k - 1) / (k + 1));

        return(S2LatLng.FromRadians(y, x));
    }
Exemple #10
0
    public void Test_S2LatLngRect_AddPoint()
    {
        S2LatLngRect p = S2LatLngRect.Empty;

        p = p.AddPoint(S2LatLng.FromDegrees(0, 0));
        Assert.True(p.IsPoint());
        p = p.AddPoint(S2LatLng.FromRadians(0, -S2.M_PI_2));
        Assert.False(p.IsPoint());
        p = p.AddPoint(S2LatLng.FromRadians(S2.M_PI_4, -Math.PI));
        p = p.AddPoint(new S2Point(0, 0, 1));
        Assert.Equal(p, RectFromDegrees(0, -180, 90, 0));
    }
 // Returns the maximum error in GetBound() provided that the result does
 // not include either pole.  It is only to be used for testing purposes
 // (e.g., by passing it to S2LatLngRect.ApproxEquals).
 public static S2LatLng MaxErrorForTests()
 {
     // The maximum error in the latitude calculation is
     //    3.84 * DBL_EPSILON   for the cross product calculation (see above)
     //    0.96 * S2Constants.DoubleEpsilon   for the Latitude() calculation
     //    5    * S2Constants.DoubleEpsilon   added by AddPoint/GetBound to compensate for error
     //    ------------------
     //    9.80 * S2Constants.DoubleEpsilon   maximum error in result
     //
     // The maximum error in the longitude calculation is S2Constants.DoubleEpsilon.  GetBound
     // does not do any expansion because this isn't necessary in order to
     // bound the *rounded* longitudes of contained points.
     return(S2LatLng.FromRadians(10 * S2.DoubleEpsilon, 1 * S2.DoubleEpsilon));
 }
Exemple #12
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);
    }
Exemple #13
0
        public void Test_MercatorProjection_ProjectUnproject()
        {
            MercatorProjection proj = new(180);
            double             inf  = double.PositiveInfinity;

            TestProjectUnproject(proj, new R2Point(0, 0), new S2Point(1, 0, 0));
            TestProjectUnproject(proj, new R2Point(180, 0), new S2Point(-1, 0, 0));
            TestProjectUnproject(proj, new R2Point(90, 0), new S2Point(0, 1, 0));
            TestProjectUnproject(proj, new R2Point(-90, 0), new S2Point(0, -1, 0));
            TestProjectUnproject(proj, new R2Point(0, inf), new S2Point(0, 0, 1));
            TestProjectUnproject(proj, new R2Point(0, -inf), new S2Point(0, 0, -1));

            // Test one arbitrary point as a sanity check.
            TestProjectUnproject(proj, new R2Point(0, 70.255578967830246), S2LatLng.FromRadians(1, 0).ToPoint());
        }
Exemple #14
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()));
        }
    }
Exemple #15
0
    public void Test_S2LatLngRect_Contains()
    {
        // Contains(S2LatLng), InteriorContains(S2LatLng), Contains()
        S2LatLng     eq_m180    = S2LatLng.FromRadians(0, -Math.PI);
        S2LatLng     north_pole = S2LatLng.FromRadians(S2.M_PI_2, 0);
        S2LatLngRect r1         = new(eq_m180, north_pole);

        Assert.True(r1.Contains(S2LatLng.FromDegrees(30, -45)));
        Assert.True(r1.InteriorContains(S2LatLng.FromDegrees(30, -45)));
        Assert.False(r1.Contains(S2LatLng.FromDegrees(30, 45)));
        Assert.False(r1.InteriorContains(S2LatLng.FromDegrees(30, 45)));
        Assert.True(r1.Contains(eq_m180));
        Assert.False(r1.InteriorContains(eq_m180));
        Assert.True(r1.Contains(north_pole));
        Assert.False(r1.InteriorContains(north_pole));
        Assert.True(r1.Contains(new S2Point(0.5, -0.3, 0.1)));
        Assert.False(r1.Contains(new S2Point(0.5, 0.2, 0.1)));
    }
Exemple #16
0
    public void Test_S2_Interpolate()
    {
        // Choose test points designed to expose floating-point errors.
        S2Point p1 = new S2Point(0.1, 1e-30, 0.3).Normalize();
        S2Point p2 = new S2Point(-0.7, -0.55, -1e30).Normalize();

        // A zero-length edge.
        TestInterpolate(p1, p1, 0, p1);
        TestInterpolate(p1, p1, 1, p1);

        // Start, end, and middle of a medium-length edge.
        TestInterpolate(p1, p2, 0, p1);
        TestInterpolate(p1, p2, 1, p2);
        TestInterpolate(p1, p2, 0.5, 0.5 * (p1 + p2));

        // Test that interpolation is done using distances on the sphere rather than
        // linear distances.
        TestInterpolate(new S2Point(1, 0, 0), new S2Point(0, 1, 0), 1.0 / 3,
                        new S2Point(Math.Sqrt(3), 1, 0));
        TestInterpolate(new S2Point(1, 0, 0), new S2Point(0, 1, 0), 2.0 / 3,
                        new S2Point(1, Math.Sqrt(3), 0));

        // Test that interpolation is accurate on a long edge (but not so long that
        // the definition of the edge itself becomes too unstable).
        {
            double  kLng = Math.PI - 1e-2;
            S2Point a    = S2LatLng.FromRadians(0, 0).ToPoint();
            S2Point b    = S2LatLng.FromRadians(0, kLng).ToPoint();
            for (double f = 0.4; f > 1e-15; f *= 0.1)
            {
                TestInterpolate(a, b, f, S2LatLng.FromRadians(0, f * kLng).ToPoint());
                TestInterpolate(a, b, 1 - f, S2LatLng.FromRadians(0, (1 - f) * kLng).ToPoint());
            }
        }

        // Test that interpolation on a 180 degree edge (antipodal endpoints) yields
        // a result with the correct distance from each endpoint.
        for (double t = 0; t <= 1; t += 0.125)
        {
            S2Point actual = S2.Interpolate(p1, -p1, t);
            Assert2.Near(new S1Angle(actual, p1).Radians, t * Math.PI, 3e-15);
        }
    }
Exemple #17
0
        public void testBasic()
        {
            var llRad = S2LatLng.FromRadians(S2.PiOver4, S2.PiOver2);

            assertTrue(llRad.Lat.Radians == S2.PiOver4);
            assertTrue(llRad.Lng.Radians == S2.PiOver2);
            assertTrue(llRad.IsValid);
            var llDeg = S2LatLng.FromDegrees(45, 90);

            assertEquals(llDeg, llRad);
            assertTrue(llDeg.IsValid);
            assertTrue(!S2LatLng.FromDegrees(-91, 0).IsValid);
            assertTrue(!S2LatLng.FromDegrees(0, 181).IsValid);

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

            assertTrue(!bad.IsValid);
            var better = bad.Normalized;

            assertTrue(better.IsValid);
            assertEquals(better.Lat, S1Angle.FromDegrees(90));
            assertDoubleNear(better.Lng.Radians, S1Angle.FromDegrees(-160).Radians);

            bad = S2LatLng.FromDegrees(-100, -360);
            assertTrue(!bad.IsValid);
            better = bad.Normalized;
            assertTrue(better.IsValid);
            assertEquals(better.Lat, S1Angle.FromDegrees(-90));
            assertDoubleNear(better.Lng.Radians, 0);

            assertTrue((S2LatLng.FromDegrees(10, 20) + S2LatLng.FromDegrees(20, 30)).ApproxEquals(
                           S2LatLng.FromDegrees(30, 50)));
            assertTrue((S2LatLng.FromDegrees(10, 20) - S2LatLng.FromDegrees(20, 30)).ApproxEquals(
                           S2LatLng.FromDegrees(-10, -10)));
            assertTrue((S2LatLng.FromDegrees(10, 20) * 0.5).ApproxEquals(S2LatLng.FromDegrees(5, 10)));
        }
    // Expands a bound returned by GetBound() so that it is guaranteed to
    // contain the bounds of any subregion whose bounds are computed using
    // this class.  For example, consider a loop L that defines a square.
    // GetBound() ensures that if a point P is contained by this square, then
    // S2LatLng(P) is contained by the bound.  But now consider a diamond
    // shaped loop S contained by L.  It is possible that GetBound() returns a
    // *larger* bound for S than it does for L, due to rounding errors.  This
    // method expands the bound for L so that it is guaranteed to contain the
    // bounds of any subregion S.
    //
    // More precisely, if L is a loop that does not contain either pole, and S
    // is a loop such that L.Contains(S), then
    //
    //   ExpandForSubregions(RectBound(L)).Contains(RectBound(S)).
    public static S2LatLngRect ExpandForSubregions(S2LatLngRect bound)
    {
        // Empty bounds don't need expansion.
        if (bound.IsEmpty())
        {
            return(bound);
        }

        // First we need to check whether the bound B contains any nearly-antipodal
        // points (to within 4.309 * S2Constants.DoubleEpsilon).  If so then we need to return
        // S2LatLngRect.Full(), since the subregion might have an edge between two
        // such points, and AddPoint() returns Full() for such edges.  Note that
        // this can happen even if B is not Full(); for example, consider a loop
        // that defines a 10km strip straddling the equator extending from
        // longitudes -100 to +100 degrees.
        //
        // It is easy to check whether B contains any antipodal points, but checking
        // for nearly-antipodal points is trickier.  Essentially we consider the
        // original bound B and its reflection through the origin B', and then test
        // whether the minimum distance between B and B' is less than 4.309 *
        // S2Constants.DoubleEpsilon.

        // "lng_gap" is a lower bound on the longitudinal distance between B and its
        // reflection B'.  (2.5 * S2Constants.DoubleEpsilon is the maximum combined error of the
        // endpoint longitude calculations and the GetLength() call.)
        double lng_gap = Math.Max(0.0, Math.PI - bound.Lng.GetLength() - 2.5 * S2.DoubleEpsilon);

        // "min_abs_lat" is the minimum distance from B to the equator (if zero or
        // negative, then B straddles the equator).
        double min_abs_lat = Math.Max(bound.Lat.Lo, -bound.Lat.Hi);

        // "lat_gap1" and "lat_gap2" measure the minimum distance from B to the
        // south and north poles respectively.
        double lat_gap1 = S2.M_PI_2 + bound.Lat.Lo;
        double lat_gap2 = S2.M_PI_2 - bound.Lat.Hi;

        if (min_abs_lat >= 0)
        {
            // The bound B does not straddle the equator.  In this case the minimum
            // distance is between one endpoint of the latitude edge in B closest to
            // the equator and the other endpoint of that edge in B'.  The latitude
            // distance between these two points is 2*min_abs_lat, and the longitude
            // distance is lng_gap.  We could compute the distance exactly using the
            // Haversine formula, but then we would need to bound the errors in that
            // calculation.  Since we only need accuracy when the distance is very
            // small (close to 4.309 * S2Constants.DoubleEpsilon), we substitute the Euclidean
            // distance instead.  This gives us a right triangle XYZ with two edges of
            // length x = 2*min_abs_lat and y ~= lng_gap.  The desired distance is the
            // length of the third edge "z", and we have
            //
            //         z  ~=  Math.Sqrt(x^2 + y^2)  >=  (x + y) / Math.Sqrt(2)
            //
            // Therefore the region may contain nearly antipodal points only if
            //
            //  2*min_abs_lat + lng_gap  <  Math.Sqrt(2) * 4.309 * S2Constants.DoubleEpsilon
            //                           ~= 1.354e-15
            //
            // Note that because the given bound B is conservative, "min_abs_lat" and
            // "lng_gap" are both lower bounds on their true values so we do not need
            // to make any adjustments for their errors.
            if (2 * min_abs_lat + lng_gap < 1.354e-15)
            {
                return(S2LatLngRect.Full);
            }
        }
        else if (lng_gap >= S2.M_PI_2)
        {
            // B spans at most Pi/2 in longitude.  The minimum distance is always
            // between one corner of B and the diagonally opposite corner of B'.  We
            // use the same distance approximation that we used above; in this case
            // we have an obtuse triangle XYZ with two edges of length x = lat_gap1
            // and y = lat_gap2, and angle Z >= Pi/2 between them.  We then have
            //
            //         z  >=  Math.Sqrt(x^2 + y^2)  >=  (x + y) / Math.Sqrt(2)
            //
            // Unlike the case above, "lat_gap1" and "lat_gap2" are not lower bounds
            // (because of the extra addition operation, and because S2Constants.M_PI_2 is not
            // exactly equal to Pi/2); they can exceed their true values by up to
            // 0.75 * S2Constants.DoubleEpsilon.  Putting this all together, the region may
            // contain nearly antipodal points only if
            //
            //   lat_gap1 + lat_gap2  <  (Math.Sqrt(2) * 4.309 + 1.5) * S2Constants.DoubleEpsilon
            //                        ~= 1.687e-15
            if (lat_gap1 + lat_gap2 < 1.687e-15)
            {
                return(S2LatLngRect.Full);
            }
        }
        else
        {
            // Otherwise we know that (1) the bound straddles the equator and (2) its
            // width in longitude is at least Pi/2.  In this case the minimum
            // distance can occur either between a corner of B and the diagonally
            // opposite corner of B' (as in the case above), or between a corner of B
            // and the opposite longitudinal edge reflected in B'.  It is sufficient
            // to only consider the corner-edge case, since this distance is also a
            // lower bound on the corner-corner distance when that case applies.

            // Consider the spherical triangle XYZ where X is a corner of B with
            // minimum absolute latitude, Y is the closest pole to X, and Z is the
            // point closest to X on the opposite longitudinal edge of B'.  This is a
            // right triangle (Z = Pi/2), and from the spherical law of sines we have
            //
            //     sin(z) / sin(Z)  =  sin(y) / sin(Y)
            //     sin(max_lat_gap) / 1  =  sin(d_min) / sin(lng_gap)
            //     sin(d_min)  =  sin(max_lat_gap) * sin(lng_gap)
            //
            // where "max_lat_gap" = Math.Max(lat_gap1, lat_gap2) and "d_min" is the
            // desired minimum distance.  Now using the facts that sin(t) >= (2/Pi)*t
            // for 0 <= t <= Pi/2, that we only need an accurate approximation when
            // at least one of "max_lat_gap" or "lng_gap" is extremely small (in
            // which case sin(t) ~= t), and recalling that "max_lat_gap" has an error
            // of up to 0.75 * S2Constants.DoubleEpsilon, we want to test whether
            //
            //   max_lat_gap * lng_gap  <  (4.309 + 0.75) * (Pi/2) * S2Constants.DoubleEpsilon
            //                          ~= 1.765e-15
            if (Math.Max(lat_gap1, lat_gap2) * lng_gap < 1.765e-15)
            {
                return(S2LatLngRect.Full);
            }
        }
        // Next we need to check whether the subregion might contain any edges that
        // span (Math.PI - 2 * S2Constants.DoubleEpsilon) radians or more in longitude, since AddPoint
        // sets the longitude bound to Full() in that case.  This corresponds to
        // testing whether (lng_gap <= 0) in "lng_expansion" below.

        // Otherwise, the maximum latitude error in AddPoint is 4.8 * S2Constants.DoubleEpsilon.
        // In the worst case, the errors when computing the latitude bound for a
        // subregion could go in the opposite direction as the errors when computing
        // the bound for the original region, so we need to double this value.
        // (More analysis shows that it's okay to round down to a multiple of
        // S2Constants.DoubleEpsilon.)
        //
        // For longitude, we rely on the fact that atan2 is correctly rounded and
        // therefore no additional bounds expansion is necessary.

        double lat_expansion = 9 * S2.DoubleEpsilon;
        double lng_expansion = (lng_gap <= 0) ? Math.PI : 0;

        return(bound.Expanded(S2LatLng.FromRadians(lat_expansion,
                                                   lng_expansion)).PolarClosure());
    }
Exemple #19
0
    public override S2LatLng ToLatLng(R2Point p)
    {
        var rem = Math.IEEERemainder(p.X, x_wrap_);

        return(S2LatLng.FromRadians(to_radians_ * p.Y, to_radians_ * rem));
    }
Exemple #20
0
    public void Test_S2_AreaMethods()
    {
        S2Point pz   = new(0, 0, 1);
        S2Point p000 = new(1, 0, 0);
        S2Point p045 = new S2Point(1, 1, 0).Normalize();
        S2Point p090 = new(0, 1, 0);
        S2Point p180 = new(-1, 0, 0);

        Assert2.Near(S2.Area(p000, p090, pz), S2.M_PI_2);
        Assert2.Near(S2.Area(p045, pz, p180), 3 * S2.M_PI_4);

        // Make sure that Area() has good *relative* accuracy even for
        // very small areas.
        const double eps       = 1e-10;
        S2Point      pepsx     = new S2Point(eps, 0, 1).Normalize();
        S2Point      pepsy     = new S2Point(0, eps, 1).Normalize();
        double       expected1 = 0.5 * eps * eps;

        Assert2.Near(S2.Area(pepsx, pepsy, pz), expected1, 1e-14 * expected1);

        // Make sure that it can handle degenerate triangles.
        S2Point pr = new S2Point(0.257, -0.5723, 0.112).Normalize();
        S2Point pq = new S2Point(-0.747, 0.401, 0.2235).Normalize();

        Assert.Equal(0, S2.Area(pr, pr, pr));
        // The following test is not exact due to rounding error.
        Assert2.Near(S2.Area(pr, pq, pr), 0, S2.DoubleError);
        Assert.Equal(0, S2.Area(p000, p045, p090));

        double max_girard = 0;

        for (int i = 0; i < 10000; ++i)
        {
            S2Point p0 = S2Testing.RandomPoint();
            S2Point d1 = S2Testing.RandomPoint();
            S2Point d2 = S2Testing.RandomPoint();
            S2Point p1 = (p0 + S2.DoubleError * d1).Normalize();
            S2Point p2 = (p0 + S2.DoubleError * d2).Normalize();
            // The actual displacement can be as much as 1.2e-15 due to roundoff.
            // This yields a maximum triangle area of about 0.7e-30.
            Assert.True(S2.Area(p0, p1, p2) <= 0.7e-30);
            max_girard = Math.Max(max_girard, S2.GirardArea(p0, p1, p2));
        }
        // This check only passes if GirardArea() uses RobustCrossProd().
        Assert.True(max_girard <= 1e-14);

        // Try a very long and skinny triangle.
        S2Point p045eps   = new S2Point(1, 1, eps).Normalize();
        double  expected2 = 5.8578643762690495119753e-11; // Mathematica.

        Assert2.Near(S2.Area(p000, p045eps, p090), expected2, 1e-9 * expected2);

        // Triangles with near-180 degree edges that sum to a quarter-sphere.
        const double eps2          = 1e-14;
        S2Point      p000eps2      = new S2Point(1, 0.1 * eps2, eps2).Normalize();
        double       quarter_area1 = S2.Area(p000eps2, p000, p045) +
                                     S2.Area(p000eps2, p045, p180) +
                                     S2.Area(p000eps2, p180, pz) +
                                     S2.Area(p000eps2, pz, p000);

        Assert2.Near(quarter_area1, Math.PI);

        // Four other triangles that sum to a quarter-sphere.
        S2Point p045eps2      = new S2Point(1, 1, eps2).Normalize();
        double  quarter_area2 = S2.Area(p045eps2, p000, p045) +
                                S2.Area(p045eps2, p045, p180) +
                                S2.Area(p045eps2, p180, pz) +
                                S2.Area(p045eps2, pz, p000);

        Assert2.Near(quarter_area2, Math.PI);

        // Compute the area of a hemisphere using four triangles with one near-180
        // degree edge and one near-degenerate edge.
        for (int i = 0; i < 100; ++i)
        {
            double  lng    = S2.M_2_PI * S2Testing.Random.RandDouble();
            S2Point p0     = S2LatLng.FromRadians(1e-20, lng).Normalized().ToPoint();
            S2Point p1     = S2LatLng.FromRadians(0, lng).Normalized().ToPoint();
            double  p2_lng = lng + S2Testing.Random.RandDouble();
            S2Point p2     = S2LatLng.FromRadians(0, p2_lng).Normalized().ToPoint();
            S2Point p3     = S2LatLng.FromRadians(0, lng + Math.PI).Normalized().ToPoint();
            S2Point p4     = S2LatLng.FromRadians(0, lng + 5.0).Normalized().ToPoint();
            double  area   = (S2.Area(p0, p1, p2) + S2.Area(p0, p2, p3) +
                              S2.Area(p0, p3, p4) + S2.Area(p0, p4, p1));
            Assert2.Near(area, S2.M_2_PI, 2e-15);
        }

        // This tests a case where the triangle has zero area, but S2.Area()
        // computes (dmin > 0) due to rounding errors.
        Assert.Equal(0.0, S2.Area(S2LatLng.FromDegrees(-45, -170).ToPoint(),
                                  S2LatLng.FromDegrees(45, -170).ToPoint(),
                                  S2LatLng.FromDegrees(0, -170).ToPoint()));
    }
        public void testBasic()
        {
            // Most of the S2LatLngRect methods have trivial implementations that
            // use the R1Interval and S1Interval classes, so most of the testing
            // is done in those unit tests.

            // Test basic properties of empty and full caps.
            var empty = S2LatLngRect.Empty;
            var full  = S2LatLngRect.Full;

            assertTrue(empty.IsValid);
            assertTrue(empty.IsEmpty);
            assertTrue(full.IsValid);
            assertTrue(full.IsFull);

            // assertTrue various constructors and accessor methods.
            var d1 = rectFromDegrees(-90, 0, -45, 180);

            assertDoubleNear(d1.LatLo.Degrees, -90);
            assertDoubleNear(d1.LatHi.Degrees, -45);
            assertDoubleNear(d1.LngLo.Degrees, 0);
            assertDoubleNear(d1.LngHi.Degrees, 180);
            assertTrue(d1.Lat.Equals(new R1Interval(-S2.PiOver2, -S2.PiOver4)));
            assertTrue(d1.Lng.Equals(new S1Interval(0, S2.Pi)));

            // FromCenterSize()
            assertTrue(
                S2LatLngRect.FromCenterSize(S2LatLng.FromDegrees(80, 170), S2LatLng.FromDegrees(40, 60))
                .ApproxEquals(rectFromDegrees(60, 140, 90, -160)));
            assertTrue(S2LatLngRect
                       .FromCenterSize(S2LatLng.FromDegrees(10, 40), S2LatLng.FromDegrees(210, 400)).IsFull);
            assertTrue(
                S2LatLngRect.FromCenterSize(S2LatLng.FromDegrees(-90, 180), S2LatLng.FromDegrees(20, 50))
                .ApproxEquals(rectFromDegrees(-90, 155, -80, -155)));

            // FromPoint(), FromPointPair()
            assertEquals(S2LatLngRect.FromPoint(d1.Lo), new S2LatLngRect(d1.Lo, d1.Lo));
            assertEquals(
                S2LatLngRect.FromPointPair(S2LatLng.FromDegrees(-35, -140), S2LatLng.FromDegrees(15, 155)),
                rectFromDegrees(-35, 155, 15, -140));
            assertEquals(
                S2LatLngRect.FromPointPair(S2LatLng.FromDegrees(25, -70), S2LatLng.FromDegrees(-90, 80)),
                rectFromDegrees(-90, -70, 25, 80));

            // GetCenter(), GetVertex(), Contains(S2LatLng), InteriorContains(S2LatLng).
            var eqM180    = S2LatLng.FromRadians(0, -S2.Pi);
            var northPole = S2LatLng.FromRadians(S2.PiOver2, 0);
            var r1        = new S2LatLngRect(eqM180, northPole);

            assertEquals(r1.Center, S2LatLng.FromRadians(S2.PiOver4, -S2.PiOver2));
            assertEquals(r1.GetVertex(0), S2LatLng.FromRadians(0, S2.Pi));
            assertEquals(r1.GetVertex(1), S2LatLng.FromRadians(0, 0));
            assertEquals(r1.GetVertex(2), S2LatLng.FromRadians(S2.PiOver2, 0));
            assertEquals(r1.GetVertex(3), S2LatLng.FromRadians(S2.PiOver2, S2.Pi));
            assertTrue(r1.Contains(S2LatLng.FromDegrees(30, -45)));
            assertTrue(!r1.Contains(S2LatLng.FromDegrees(30, 45)));
            assertTrue(!r1.InteriorContains(eqM180) && !r1.InteriorContains(northPole));
            assertTrue(r1.Contains(new S2Point(0.5, -0.3, 0.1)));
            assertTrue(!r1.Contains(new S2Point(0.5, 0.2, 0.1)));

            // Make sure that GetVertex() returns vertices in CCW order.
            for (var i = 0; i < 4; ++i)
            {
                var lat = S2.PiOver4 * (i - 2);
                var lng = S2.PiOver2 * (i - 2) + 0.2;
                var r   = new S2LatLngRect(new R1Interval(lat, lat + S2.PiOver4), new S1Interval(
                                               Math.IEEERemainder(lng, 2 * S2.Pi), Math.IEEERemainder(lng + S2.PiOver2, 2 * S2.Pi)));
                for (var k = 0; k < 4; ++k)
                {
                    assertTrue(
                        S2.SimpleCcw(r.GetVertex((k - 1) & 3).ToPoint(), r.GetVertex(k).ToPoint(),
                                     r.GetVertex((k + 1) & 3).ToPoint()));
                }
            }

            // Contains(S2LatLngRect), InteriorContains(S2LatLngRect),
            // Intersects(), InteriorIntersects(), Union(), Intersection().
            //
            // Much more testing of these methods is done in s1interval_unittest
            // and r1interval_unittest.

            var r1Mid      = rectFromDegrees(45, -90, 45, -90);
            var reqM180    = new S2LatLngRect(eqM180, eqM180);
            var rNorthPole = new S2LatLngRect(northPole, northPole);

            testIntervalOps(r1, r1Mid, "TTTT", r1, r1Mid);
            testIntervalOps(r1, reqM180, "TFTF", r1, reqM180);
            testIntervalOps(r1, rNorthPole, "TFTF", r1, rNorthPole);

            assertTrue(r1.Equals(rectFromDegrees(0, -180, 90, 0)));
            testIntervalOps(r1, rectFromDegrees(-10, -1, 1, 20), "FFTT", rectFromDegrees(-10, -180, 90, 20),
                            rectFromDegrees(0, -1, 1, 0));
            testIntervalOps(r1, rectFromDegrees(-10, -1, 0, 20), "FFTF", rectFromDegrees(-10, -180, 90, 20),
                            rectFromDegrees(0, -1, 0, 0));
            testIntervalOps(r1, rectFromDegrees(-10, 0, 1, 20), "FFTF", rectFromDegrees(-10, -180, 90, 20),
                            rectFromDegrees(0, 0, 1, 0));

            testIntervalOps(rectFromDegrees(-15, -160, -15, -150), rectFromDegrees(20, 145, 25, 155),
                            "FFFF", rectFromDegrees(-15, 145, 25, -150), empty);
            testIntervalOps(rectFromDegrees(70, -10, 90, -140), rectFromDegrees(60, 175, 80, 5), "FFTT",
                            rectFromDegrees(60, -180, 90, 180), rectFromDegrees(70, 175, 80, 5));

            // assertTrue that the intersection of two rectangles that overlap in
            // latitude
            // but not longitude is valid, and vice versa.
            testIntervalOps(rectFromDegrees(12, 30, 60, 60), rectFromDegrees(0, 0, 30, 18), "FFFF",
                            rectFromDegrees(0, 0, 60, 60), empty);
            testIntervalOps(rectFromDegrees(0, 0, 18, 42), rectFromDegrees(30, 12, 42, 60), "FFFF",
                            rectFromDegrees(0, 0, 42, 60), empty);

            // AddPoint()
            var p = S2LatLngRect.Empty;

            p = p.AddPoint(S2LatLng.FromDegrees(0, 0));
            p = p.AddPoint(S2LatLng.FromRadians(0, -S2.PiOver2));
            p = p.AddPoint(S2LatLng.FromRadians(S2.PiOver4, -S2.Pi));
            p = p.AddPoint(new S2Point(0, 0, 1));
            assertTrue(p.Equals(r1));

            // Expanded()
            assertTrue(
                rectFromDegrees(70, 150, 80, 170).Expanded(S2LatLng.FromDegrees(20, 30)).ApproxEquals(
                    rectFromDegrees(50, 120, 90, -160)));
            assertTrue(S2LatLngRect.Empty.Expanded(S2LatLng.FromDegrees(20, 30)).IsEmpty);
            assertTrue(S2LatLngRect.Full.Expanded(S2LatLng.FromDegrees(20, 30)).IsFull);
            assertTrue(
                rectFromDegrees(-90, 170, 10, 20).Expanded(S2LatLng.FromDegrees(30, 80)).ApproxEquals(
                    rectFromDegrees(-90, -180, 40, 180)));

            // ConvolveWithCap()
            var llr1 =
                new S2LatLngRect(S2LatLng.FromDegrees(0, 170), S2LatLng.FromDegrees(0, -170))
                .ConvolveWithCap(S1Angle.FromDegrees(15));
            var llr2 =
                new S2LatLngRect(S2LatLng.FromDegrees(-15, 155), S2LatLng.FromDegrees(15, -155));

            assertTrue(llr1.ApproxEquals(llr2));

            llr1 = new S2LatLngRect(S2LatLng.FromDegrees(60, 150), S2LatLng.FromDegrees(80, 10))
                   .ConvolveWithCap(S1Angle.FromDegrees(15));
            llr2 = new S2LatLngRect(S2LatLng.FromDegrees(45, -180), S2LatLng.FromDegrees(90, 180));
            assertTrue(llr1.ApproxEquals(llr2));

            // GetCapBound(), bounding cap at center is smaller:
            assertTrue(new S2LatLngRect(S2LatLng.FromDegrees(-45, -45), S2LatLng.FromDegrees(45, 45)).CapBound.ApproxEquals(S2Cap.FromAxisHeight(new S2Point(1, 0, 0), 0.5)));
            // GetCapBound(), bounding cap at north pole is smaller:
            assertTrue(new S2LatLngRect(S2LatLng.FromDegrees(88, -80), S2LatLng.FromDegrees(89, 80)).CapBound.ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, 1), S1Angle.FromDegrees(2))));
            // GetCapBound(), longitude span > 180 degrees:
            assertTrue(
                new S2LatLngRect(S2LatLng.FromDegrees(-30, -150), S2LatLng.FromDegrees(-10, 50)).CapBound
                .ApproxEquals(S2Cap.FromAxisAngle(new S2Point(0, 0, -1), S1Angle.FromDegrees(80))));

            // Contains(S2Cell), MayIntersect(S2Cell), Intersects(S2Cell)

            // Special cases.
            testCellOps(empty, S2Cell.FromFacePosLevel(3, (byte)0, 0), 0);
            testCellOps(full, S2Cell.FromFacePosLevel(2, (byte)0, 0), 4);
            testCellOps(full, S2Cell.FromFacePosLevel(5, (byte)0, 25), 4);

            // This rectangle includes the first quadrant of face 0. It's expanded
            // slightly because cell bounding rectangles are slightly conservative.
            var r4 = rectFromDegrees(-45.1, -45.1, 0.1, 0.1);

            testCellOps(r4, S2Cell.FromFacePosLevel(0, (byte)0, 0), 3);
            testCellOps(r4, S2Cell.FromFacePosLevel(0, (byte)0, 1), 4);
            testCellOps(r4, S2Cell.FromFacePosLevel(1, (byte)0, 1), 0);

            // This rectangle intersects the first quadrant of face 0.
            var r5 = rectFromDegrees(-10, -45, 10, 0);

            testCellOps(r5, S2Cell.FromFacePosLevel(0, (byte)0, 0), 3);
            testCellOps(r5, S2Cell.FromFacePosLevel(0, (byte)0, 1), 3);
            testCellOps(r5, S2Cell.FromFacePosLevel(1, (byte)0, 1), 0);

            // Rectangle consisting of a single point.
            testCellOps(rectFromDegrees(4, 4, 4, 4), S2Cell.FromFacePosLevel(0, (byte)0, 0), 3);

            // Rectangles that intersect the bounding rectangle of a face
            // but not the face itself.
            testCellOps(rectFromDegrees(41, -87, 42, -79), S2Cell.FromFacePosLevel(2, (byte)0, 0), 1);
            testCellOps(rectFromDegrees(-41, 160, -40, -160), S2Cell.FromFacePosLevel(5, (byte)0, 0), 1);
            {
                // This is the leaf cell at the top right hand corner of face 0.
                // It has two angles of 60 degrees and two of 120 degrees.
                var cell0tr  = new S2Cell(new S2Point(1 + 1e-12, 1, 1));
                var bound0tr = cell0tr.RectBound;
                var v0       = new S2LatLng(cell0tr.GetVertexRaw(0));
                testCellOps(
                    rectFromDegrees(v0.Lat.Degrees - 1e-8, v0.Lng.Degrees - 1e-8,
                                    v0.Lat.Degrees - 2e-10, v0.Lng.Degrees + 1e-10), cell0tr, 1);
            }

            // Rectangles that intersect a face but where no vertex of one region
            // is contained by the other region. The first one passes through
            // a corner of one of the face cells.
            testCellOps(rectFromDegrees(-37, -70, -36, -20), S2Cell.FromFacePosLevel(5, (byte)0, 0), 2);
            {
                // These two intersect like a diamond and a square.
                var cell202  = S2Cell.FromFacePosLevel(2, (byte)0, 2);
                var bound202 = cell202.RectBound;
                testCellOps(
                    rectFromDegrees(bound202.Lo.Lat.Degrees + 3, bound202.Lo.Lng.Degrees + 3,
                                    bound202.Hi.Lat.Degrees - 3, bound202.Hi.Lng.Degrees - 3), cell202, 2);
            }
        }