예제 #1
0
 void TestProjectUnproject(Projection projection, R2Point px, S2Point x)
 {
     // The arguments are chosen such that projection is exact, but
     // unprojection may not be.
     Assert.Equal(px, projection.Project(x));
     Assert.True(S2.ApproxEquals(x, projection.Unproject(px)));
 }
예제 #2
0
        public void Test_R2Rect_SimplePredicates()
        {
            // GetCenter(), GetVertex(), Contains(R2Point), InteriorContains(R2Point).
            R2Point sw1 = new(0, 0.25);
            R2Point ne1 = new(0.5, 0.75);
            R2Rect  r1  = new(sw1, ne1);

            Assert.Equal(new R2Point(0.25, 0.5), r1.GetCenter());
            Assert.Equal(new R2Point(0, 0.25), r1.GetVertex(0));
            Assert.Equal(new R2Point(0.5, 0.25), r1.GetVertex(1));
            Assert.Equal(new R2Point(0.5, 0.75), r1.GetVertex(2));
            Assert.Equal(new R2Point(0, 0.75), r1.GetVertex(3));
            Assert.True(r1.Contains(new R2Point(0.2, 0.4)));
            Assert.False(r1.Contains(new R2Point(0.2, 0.8)));
            Assert.False(r1.Contains(new R2Point(-0.1, 0.4)));
            Assert.False(r1.Contains(new R2Point(0.6, 0.1)));
            Assert.True(r1.Contains(sw1));
            Assert.True(r1.Contains(ne1));
            Assert.False(r1.InteriorContains(sw1));
            Assert.False(r1.InteriorContains(ne1));

            // Make sure that GetVertex() returns vertices in CCW order.
            for (int k = 0; k < 4; ++k)
            {
                R2Point a = r1.GetVertex(k - 1);
                R2Point b = r1.GetVertex(k);
                R2Point c = r1.GetVertex(k + 1);
                Assert.True((b - a).GetOrtho().DotProd(c - a) > 0);
            }
        }
예제 #3
0
    private static void TestIntervalOps(S1Interval x, S1Interval y, string expected_relation, S1Interval expected_union, S1Interval 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');

        // bounds() returns a reference to a member variable, so we need to
        // make a copy when invoking it on a temporary object.
        Assert.Equal(R2Point.FromCoords(x.Union(y).Bounds()).Bounds(), expected_union.Bounds());
        Assert.Equal(R2Point.FromCoords(x.Intersection(y).Bounds()).Bounds(),
                     expected_intersection.Bounds());

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

        if (y.Lo == y.Hi)
        {
            var r = S1Interval.AddPoint(x, y.Lo);
            Assert.Equal(r.Bounds(), expected_union.Bounds());
        }
    }
예제 #4
0
 // Given a point P representing a possibly clipped endpoint A of an edge AB,
 // verify that "clip" contains P, and that if clipping occurred (i.e., P != A)
 // then P is on the boundary of "clip".
 private static void CheckPointOnBoundary(R2Point p, R2Point a, R2Rect clip)
 {
     Assert.True(clip.Contains(p));
     if (p != a)
     {
         Assert.False(clip.Contains(new R2Point(MathUtils.NextAfter(p[0], a[0]),
                                                MathUtils.NextAfter(p[1], a[1]))));
     }
 }
예제 #5
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));
    }
예제 #6
0
    // Given an edge AB and a rectangle "clip", verify that IntersectsRect(),
    // ClipEdge(), and ClipEdgeBound() produce consistent results.
    private static void TestClipEdge(R2Point a, R2Point b, R2Rect clip)
    {
        // A bound for the error in edge clipping plus the error in the
        // IntersectsRect calculation below.
        double kError = kEdgeClipErrorUVDist + kIntersectsRectErrorUVDist;

        if (!ClipEdge(a, b, clip, out var a_clipped, out var b_clipped))
        {
            Assert.False(IntersectsRect(a, b, clip.Expanded(-kError)));
        }
예제 #7
0
    // Given a point X on the line AB (which is checked), return the fraction "t"
    // such that x = (1-t)*a + t*b.  Return 0 if A = B.
    private static double GetFraction(R2Point x, R2Point a, R2Point b)
    {
        // A bound for the error in edge clipping plus the error in the calculation
        // below (which is similar to IntersectsRect).
        double kError = kEdgeClipErrorUVDist + kIntersectsRectErrorUVDist;

        if (a == b)
        {
            return(0.0);
        }
        R2Point dir = R2Point.Normalize(b - a);

        Assert.True(Math.Abs((x - a).DotProd(dir.GetOrtho())) <= kError);
        return((x - a).DotProd(dir));
    }
예제 #8
0
    // Given a geodesic edge AB, split the edge as necessary and append all
    // projected vertices except the first to "vertices".
    //
    // The maximum recursion depth is (Math.PI / kMinTolerance()) < 45, and the
    // frame size is small so stack overflow should not be an issue.
    private void AppendProjected(R2Point pa, S2Point a, R2Point pb, S2Point b, List <R2Point> vertices)
    {
        R2Point pbTmp = proj_.WrapDestination(pa, pb);

        if (EstimateMaxError(pa, a, pbTmp, b) <= scaled_tolerance_)
        {
            vertices.Add(pbTmp);
        }
        else
        {
            var mid  = (a + b).Normalize();
            var pmid = proj_.WrapDestination(pa, proj_.Project(mid));
            AppendProjected(pa, a, pmid, mid, vertices);
            AppendProjected(pmid, mid, pbTmp, b, vertices);
        }
    }
예제 #9
0
    // Helper function that wraps the coordinates of B if necessary in order to
    // obtain the shortest edge AB.  For example, suppose that A = [170, 20],
    // B = [-170, 20], and the projection wraps so that [x, y] == [x + 360, y].
    // Then this function would return [190, 20] for point B (reducing the edge
    // length in the "x" direction from 340 to 20).
    public R2Point WrapDestination(R2Point a, R2Point b)
    {
        R2Point wrap = WrapDistance();
        double  x = b.X, y = b.Y;

        // The code below ensures that "b" is unmodified unless wrapping is required.
        if (wrap.X > 0 && Math.Abs(x - a.X) > 0.5 * wrap.X)
        {
            x = a.X + Math.IEEERemainder(x - a.X, wrap.X);
        }
        if (wrap.Y > 0 && Math.Abs(y - a.Y) > 0.5 * wrap.Y)
        {
            y = a.Y + Math.IEEERemainder(y - a.Y, wrap.Y);
        }
        return(new R2Point(x, y));
    }
예제 #10
0
    // Like AppendProjected, but interpolates a projected edge and appends the
    // corresponding points on the sphere.
    private void AppendUnprojected(R2Point pa, S2Point a, R2Point pb, S2Point b, List <S2Point> vertices)
    {
        // See notes above regarding measuring the interpolation error.
        var pbTmp = proj_.WrapDestination(pa, pb);

        if (EstimateMaxError(pa, a, pbTmp, b) <= scaled_tolerance_)
        {
            vertices.Add(b);
        }
        else
        {
            R2Point pmid = Projection.Interpolate(0.5, pa, pbTmp);
            S2Point mid  = proj_.Unproject(pmid);
            AppendUnprojected(pa, a, pmid, mid, vertices);
            AppendUnprojected(pmid, mid, pbTmp, b, vertices);
        }
    }
예제 #11
0
 // Choose a random point in the rectangle defined by points A and B, sometimes
 // returning a point on the edge AB or the points A and B themselves.
 private static R2Point ChooseRectPoint(R2Point a, R2Point b)
 {
     if (S2Testing.Random.OneIn(5))
     {
         return(S2Testing.Random.OneIn(2) ? a : b);
     }
     else if (S2Testing.Random.OneIn(3))
     {
         return(a + S2Testing.Random.RandDouble() * (b - a));
     }
     else
     {
         // a[i] may be >, <, or == b[i], so we write it like this instead
         // of using UniformDouble.
         return(new R2Point(a[0] + S2Testing.Random.RandDouble() * (b[0] - a[0]),
                            a[1] + S2Testing.Random.RandDouble() * (b[1] - a[1])));
     }
 }
예제 #12
0
        public void Test_PlateCarreeProjection_Interpolate()
        {
            PlateCarreeProjection proj = new(180);

            // Test that coordinates and/or arguments are not accidentally reversed.
            Assert.Equal(new R2Point(1.5, 6),
                         Projection.Interpolate(0.25, new R2Point(1, 5), new R2Point(3, 9)));

            // Test extrapolation.
            Assert.Equal(new R2Point(-3, 0), Projection.Interpolate(-2, new R2Point(1, 0), new R2Point(3, 0)));

            // Check that interpolation is exact at both endpoints.
            var a = new R2Point(1.234, -5.456e-20);
            var b = new R2Point(2.1234e-20, 7.456);

            Assert.Equal(a, Projection.Interpolate(0, a, b));
            Assert.Equal(b, Projection.Interpolate(1, a, b));
        }
예제 #13
0
        public void Test_S2PaddedCell_ShrinkToFit()
        {
            const int kIters = 1000;

            for (int iter = 0; iter < kIters; ++iter)
            {
                // Start with the desired result and work backwards.
                S2CellId result    = S2Testing.GetRandomCellId();
                R2Rect   result_uv = result.BoundUV();
                R2Point  size_uv   = result_uv.GetSize();

                // Find the biggest rectangle that fits in "result" after padding.
                // (These calculations ignore numerical errors.)
                double max_padding = 0.5 * Math.Min(size_uv[0], size_uv[1]);
                double padding     = max_padding * S2Testing.Random.RandDouble();
                R2Rect max_rect    = result_uv.Expanded(-padding);

                // Start with a random subset of the maximum rectangle.
                R2Point a = new(SampleInterval(max_rect[0]), SampleInterval(max_rect[1]));
                R2Point b = new(SampleInterval(max_rect[0]), SampleInterval(max_rect[1]));
                if (!result.IsLeaf())
                {
                    // If the result is not a leaf cell, we must ensure that no child of
                    // "result" also satisfies the conditions of ShrinkToFit().  We do this
                    // by ensuring that "rect" intersects at least two children of "result"
                    // (after padding).
                    int    axis   = S2Testing.Random.Uniform(2);
                    double center = result.CenterUV()[axis];

                    // Find the range of coordinates that are shared between child cells
                    // along that axis.
                    R1Interval shared = new(center - padding, center + padding);
                    double     mid    = SampleInterval(shared.Intersection(max_rect[axis]));
                    a = a.SetAxis(axis, SampleInterval(new R1Interval(max_rect[axis].Lo, mid)));
                    b = b.SetAxis(axis, SampleInterval(new R1Interval(mid, max_rect[axis].Hi)));
                }
                R2Rect rect = R2Rect.FromPointPair(a, b);

                // Choose an arbitrary ancestor as the S2PaddedCell.
                S2CellId initial_id = result.Parent(S2Testing.Random.Uniform(result.Level() + 1));
                Assert.Equal(result, new S2PaddedCell(initial_id, padding).ShrinkToFit(rect));
            }
        }
예제 #14
0
    private S1ChordAngle EstimateMaxError(R2Point pa, S2Point a, R2Point pb, S2Point b)
    {
        // See the algorithm description at the top of this file.

        // We always tessellate edges longer than 90 degrees on the sphere, since the
        // approximation below is not robust enough to handle such edges.
        if (a.DotProd(b) < -1e-14)
        {
            return(S1ChordAngle.Infinity);
        }

        const double t1    = kInterpolationFraction;
        const double t2    = 1 - kInterpolationFraction;
        S2Point      mid1  = S2.Interpolate(a, b, t1);
        S2Point      mid2  = S2.Interpolate(a, b, t2);
        S2Point      pmid1 = proj_.Unproject(Projection.Interpolate(t1, pa, pb));
        S2Point      pmid2 = proj_.Unproject(Projection.Interpolate(t2, pa, pb));

        return(S1ChordAngle.Max(new S1ChordAngle(mid1, pmid1), new S1ChordAngle(mid2, pmid2)));
    }
예제 #15
0
    // Converts the planar edge AB in the given projection to a chain of
    // spherical geodesic edges and appends the vertices to "vertices".
    //
    // This method can be called multiple times with the same output vector to
    // convert an entire polyline or loop.  All vertices of the first edge are
    // appended, but the first vertex of each subsequent edge is omitted (and is
    // required to match that last vertex of the previous edge).
    //
    // Note that to construct an S2Loop, you must call vertices.pop_back() at
    // the very end to eliminate the duplicate first and last vertex.  Note also
    // that if the given projection involves coordinate "wrapping" (e.g. across
    // the 180 degree meridian) then the first and last vertices may not be
    // exactly the same.
    public void AppendUnprojected(R2Point a, R2Point b, List <S2Point> vertices)
    {
        var pointA = proj_.Unproject(a);
        var pointB = proj_.Unproject(b);

        if (!vertices.Any())
        {
            vertices.Add(pointA);
        }
        else
        {
            // Note that coordinate wrapping can create a small amount of error.  For
            // example in the edge chain "0:-175, 0:179, 0:-177", the first edge is
            // transformed into "0:-175, 0:-181" while the second is transformed into
            // "0:179, 0:183".  The two coordinate pairs for the middle vertex
            // ("0:-181" and "0:179") may not yield exactly the same S2Point.
            System.Diagnostics.Debug.Assert(S2.ApproxEquals(vertices.Last(), pointA)); // Appended edges must form a chain
        }
        AppendUnprojected(a, pointA, b, pointB, vertices);
    }
예제 #16
0
 // Returns the point obtained by interpolating the given fraction of the
 // distance along the line from A to B.  Almost all projections should
 // use the default implementation of this method, which simply interpolates
 // linearly in R2 space.  Fractions < 0 or > 1 result in extrapolation
 // instead.
 //
 // The only reason to override this method is if you want edges to be
 // defined as something other than straight lines in the 2D projected
 // coordinate system.  For example, using a third-party library such as
 // GeographicLib you could define edges as geodesics over an ellipsoid model
 // of the Earth.  (Note that very few data sets define edges this way.)
 //
 // Also note that there is no reason to define a projection where edges are
 // geodesics over the sphere, because this is the native S2 interpretation.
 public static R2Point Interpolate(double f, R2Point a, R2Point b)
 {
     // Default implementation, suitable for any projection where edges are defined
     // as straight lines in the 2D projected space.
     return((1.0 - f) * a + f * b);
 }
예제 #17
0
 // Convenience function equivalent to S2LatLng(Unproject(p)), but the
 // implementation may be more efficient.
 public abstract S2LatLng ToLatLng(R2Point p);
예제 #18
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));
    }
예제 #19
0
 // Converts a projected 2D point to a point on the sphere.
 //
 // If wrapping is defined for a given axis (see below), then this method
 // should accept any real number for the corresponding coordinate.
 public abstract S2Point Unproject(R2Point p);
예제 #20
0
 public override S2Point Unproject(R2Point p)
 {
     return(ToLatLng(p).ToPoint());
 }