Esempio n. 1
0
        private bool loopsEqual(S2Loop a, S2Loop b, double maxError)
        {
            // Return true if two loops have the same cyclic vertex sequence.

            if (a.NumVertices != b.NumVertices)
            {
                return(false);
            }
            for (var offset = 0; offset < a.NumVertices; ++offset)
            {
                if (S2.ApproxEquals(a.Vertex(offset), b.Vertex(0), maxError))
                {
                    var success = true;
                    for (var i = 0; i < a.NumVertices; ++i)
                    {
                        if (!S2.ApproxEquals(a.Vertex(i + offset), b.Vertex(i), maxError))
                        {
                            success = false;
                            break;
                        }
                    }
                    if (success)
                    {
                        return(true);
                    }
                    // Otherwise continue looping. There may be more than one candidate
                    // starting offset since vertices are only matched approximately.
                }
            }
            return(false);
        }
 public void Test_GetCentroid_Polyline()
 {
     // Checks that points are ignored when computing the centroid.
     Assert.True(S2.ApproxEquals(
                     new S2Point(1, 1, 0),
                     S2ShapeIndexMeasures.GetCentroid(MakeIndexOrDie("5:5 | 6:6 # 0:0, 0:90 #"))));
 }
 public void Test_GetCentroid_Polygon()
 {
     // Checks that points and polylines are ignored when computing the centroid.
     Assert.True(S2.ApproxEquals(
                     new S2Point(S2.M_PI_4, S2.M_PI_4, S2.M_PI_4),
                     S2ShapeIndexMeasures.GetCentroid(MakeIndexOrDie("5:5 # 6:6, 7:7 # 0:0, 0:90, 90:0"))));
 }
Esempio n. 4
0
    // Given a point X and an edge AB, check that the distance from X to AB is
    // "distance_radians" and the closest point on AB is "expected_closest".
    private static void CheckDistance(S2Point x, S2Point a, S2Point b, double distance_radians, S2Point expected_closest)
    {
        x = x.Normalize();
        a = a.Normalize();
        b = b.Normalize();
        expected_closest = expected_closest.Normalize();
        Assert2.Near(distance_radians, S2.GetDistance(x, a, b).Radians, S2.DoubleError);
        S2Point closest = S2.Project(x, a, b);

        Assert.True(S2Pred.CompareEdgeDistance(
                        closest, a, b, new S1ChordAngle(S2.kProjectPerpendicularErrorS1Angle)) < 0);

        // If X is perpendicular to AB then there is nothing further we can expect.
        if (distance_radians != S2.M_PI_2)
        {
            if (expected_closest == new S2Point())
            {
                // This special value says that the result should be A or B.
                Assert.True(closest == a || closest == b);
            }
            else
            {
                Assert.True(S2.ApproxEquals(closest, expected_closest));
            }
        }
        S1ChordAngle min_distance = S1ChordAngle.Zero;

        Assert.False(S2.UpdateMinDistance(x, a, b, ref min_distance));
        min_distance = S1ChordAngle.Infinity;
        Assert.True(S2.UpdateMinDistance(x, a, b, ref min_distance));
        Assert2.Near(distance_radians, min_distance.ToAngle().Radians, S2.DoubleError);
    }
Esempio n. 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);
        }
Esempio n. 6
0
 public void Test_GetCentroid_Polygon()
 {
     // GetCentroid returns the centroid multiplied by the area of the polygon.
     Assert.True(S2.ApproxEquals(
                     new S2Point(S2.M_PI_4, S2.M_PI_4, S2.M_PI_4),
                     S2.GetCentroid(MakeLaxPolygonOrDie("0:0, 0:90, 90:0"))));
 }
Esempio n. 7
0
 public void Test_GetCentroid_Polyline()
 {
     // GetCentroid returns the centroid multiplied by the length of the polyline.
     Assert.True(S2.ApproxEquals(
                     new S2Point(1, 1, 0),
                     S2ShapeIndexMeasures.GetCentroid(MakeIndexOrDie("0:0, 0:90"))));
 }
Esempio n. 8
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)));
 }
Esempio n. 9
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());
        }
Esempio n. 10
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()));
        }
    }
Esempio n. 11
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)));
    }
        public void testInterpolate()
        {
            var vertices = new List <S2Point>();

            vertices.Add(new S2Point(1, 0, 0));
            vertices.Add(new S2Point(0, 1, 0));
            vertices.Add(S2Point.Normalize(new S2Point(0, 1, 1)));
            vertices.Add(new S2Point(0, 0, 1));
            var line = new S2Polyline(vertices);

            assertEquals(line.Interpolate(-0.1), vertices[0]);
            assertTrue(S2.ApproxEquals(
                           line.Interpolate(0.1), S2Point.Normalize(new S2Point(1, Math.Tan(0.2 * S2.Pi / 2), 0))));
            assertTrue(S2.ApproxEquals(line.Interpolate(0.25), S2Point.Normalize(new S2Point(1, 1, 0))));

            assertTrue(S2.ApproxEquals(line.Interpolate(0.5), vertices[1]));
            assertTrue(S2.ApproxEquals(line.Interpolate(0.75), vertices[2]));
            assertTrue(S2.ApproxEquals(line.Interpolate(1.1), vertices[3]));
        }
        public void testProject()
        {
            var latLngs = new List <S2Point>();

            latLngs.Add(S2LatLng.FromDegrees(0, 0).ToPoint());
            latLngs.Add(S2LatLng.FromDegrees(0, 1).ToPoint());
            latLngs.Add(S2LatLng.FromDegrees(0, 2).ToPoint());
            latLngs.Add(S2LatLng.FromDegrees(1, 2).ToPoint());
            var line = new S2Polyline(latLngs);

            var     edgeIndex = -1;
            S2Point testPoint = default(S2Point);

            testPoint = S2LatLng.FromDegrees(0.5, -0.5).ToPoint();
            edgeIndex = line.GetNearestEdgeIndex(testPoint);
            assertTrue(S2.ApproxEquals(
                           line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 0).ToPoint()));
            assertEquals(0, edgeIndex);

            testPoint = S2LatLng.FromDegrees(0.5, 0.5).ToPoint();
            edgeIndex = line.GetNearestEdgeIndex(testPoint);
            assertTrue(S2.ApproxEquals(
                           line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 0.5).ToPoint()));
            assertEquals(0, edgeIndex);

            testPoint = S2LatLng.FromDegrees(0.5, 1).ToPoint();
            edgeIndex = line.GetNearestEdgeIndex(testPoint);
            assertTrue(S2.ApproxEquals(
                           line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 1).ToPoint()));
            assertEquals(0, edgeIndex);

            testPoint = S2LatLng.FromDegrees(-0.5, 2.5).ToPoint();
            edgeIndex = line.GetNearestEdgeIndex(testPoint);
            assertTrue(S2.ApproxEquals(
                           line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 2).ToPoint()));
            assertEquals(1, edgeIndex);

            testPoint = S2LatLng.FromDegrees(2, 2).ToPoint();
            edgeIndex = line.GetNearestEdgeIndex(testPoint);
            assertTrue(S2.ApproxEquals(
                           line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(1, 2).ToPoint()));
            assertEquals(2, edgeIndex);
        }
Esempio n. 14
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);
    }
Esempio n. 15
0
    // Given two edges a0a1 and b0b1, check that the minimum distance between them
    // is "distance_radians", and that GetEdgePairClosestPoints() returns
    // "expected_a" and "expected_b" as the points that achieve this distance.
    // S2Point.Empty may be passed for "expected_a" or "expected_b" to indicate
    // that both endpoints of the corresponding edge are equally distant, and
    // therefore either one might be returned.
    //
    // Parameters are passed by value so that this function can normalize them.
    private static void CheckEdgePairMinDistance(S2Point a0, S2Point a1, S2Point b0, S2Point b1, double distance_radians, S2Point expected_a, S2Point expected_b)
    {
        a0         = a0.Normalize();
        a1         = a1.Normalize();
        b0         = b0.Normalize();
        b1         = b1.Normalize();
        expected_a = expected_a.Normalize();
        expected_b = expected_b.Normalize();
        var     closest  = S2.GetEdgePairClosestPoints(a0, a1, b0, b1);
        S2Point actual_a = closest.Item1;
        S2Point actual_b = closest.Item2;

        if (expected_a == S2Point.Empty)
        {
            // This special value says that the result should be a0 or a1.
            Assert.True(actual_a == a0 || actual_a == a1);
        }
        else
        {
            Assert.True(S2.ApproxEquals(expected_a, actual_a));
        }
        if (expected_b == S2Point.Empty)
        {
            // This special value says that the result should be b0 or b1.
            Assert.True(actual_b == b0 || actual_b == b1);
        }
        else
        {
            Assert.True(S2.ApproxEquals(expected_b, actual_b));
        }
        S1ChordAngle min_distance = S1ChordAngle.Zero;

        Assert.False(S2.UpdateEdgePairMinDistance(a0, a1, b0, b1, ref min_distance));
        min_distance = S1ChordAngle.Infinity;
        Assert.True(S2.UpdateEdgePairMinDistance(a0, a1, b0, b1, ref min_distance));
        Assert2.Near(distance_radians, min_distance.Radians(), S2.DoubleError);
    }
        // Given a point X and an edge AB, check that the distance from X to AB is
        // "distanceRadians" and the closest point on AB is "expectedClosest".
        private static void checkDistance(
            S2Point x, S2Point a, S2Point b, double distanceRadians, S2Point expectedClosest)
        {
            var kEpsilon = 1e-10;

            x = S2Point.Normalize(x);
            a = S2Point.Normalize(a);
            b = S2Point.Normalize(b);
            expectedClosest = S2Point.Normalize(expectedClosest);

            assertEquals(distanceRadians, S2EdgeUtil.GetDistance(x, a, b).Radians, kEpsilon);

            var closest = S2EdgeUtil.GetClosestPoint(x, a, b);

            if (expectedClosest.Equals(new S2Point(0, 0, 0)))
            {
                // This special value says that the result should be A or B.
                assertTrue(closest == a || closest == b);
            }
            else
            {
                assertTrue(S2.ApproxEquals(closest, expectedClosest));
            }
        }
Esempio n. 17
0
    // Common back end for AddPoint() and AddLatLng().  b and b_latlng
    // must refer to the same vertex.
    private void AddInternal(S2Point b, S2LatLng b_latlng)
    {
        // Simple consistency check to verify that b and b_latlng are alternate
        // representations of the same vertex.
        System.Diagnostics.Debug.Assert(S2.ApproxEquals(b, b_latlng.ToPoint()));

        if (bound_.IsEmpty())
        {
            bound_ = bound_.AddPoint(b_latlng);
        }
        else
        {
            // First compute the cross product N = A x B robustly.  This is the normal
            // to the great circle through A and B.  We don't use S2.RobustCrossProd()
            // since that method returns an arbitrary vector orthogonal to A if the two
            // vectors are proportional, and we want the zero vector in that case.
            var n = (a_ - b).CrossProd(a_ + b);  // N = 2 * (A x B)

            // The relative error in N gets large as its norm gets very small (i.e.,
            // when the two points are nearly identical or antipodal).  We handle this
            // by choosing a maximum allowable error, and if the error is greater than
            // this we fall back to a different technique.  Since it turns out that
            // the other sources of error in converting the normal to a maximum
            // latitude add up to at most 1.16 * S2Constants.DoubleEpsilon (see below), and it is
            // desirable to have the total error be a multiple of S2Constants.DoubleEpsilon, we have
            // chosen to limit the maximum error in the normal to 3.84 * S2Constants.DoubleEpsilon.
            // It is possible to show that the error is less than this when
            //
            //   n.Norm >= 8 * Math.Sqrt(3) / (3.84 - 0.5 - Math.Sqrt(3)) * S2Constants.DoubleEpsilon
            //            = 1.91346e-15 (about 8.618 * S2Constants.DoubleEpsilon)
            var n_norm = n.Norm();
            if (n_norm < 1.91346e-15)
            {
                // A and B are either nearly identical or nearly antipodal (to within
                // 4.309 * S2Constants.DoubleEpsilon, or about 6 nanometers on the earth's surface).
                if (a_.DotProd(b) < 0)
                {
                    // The two points are nearly antipodal.  The easiest solution is to
                    // assume that the edge between A and B could go in any direction
                    // around the sphere.
                    bound_ = S2LatLngRect.Full;
                }
                else
                {
                    // The two points are nearly identical (to within 4.309 * S2Constants.DoubleEpsilon).
                    // In this case we can just use the bounding rectangle of the points,
                    // since after the expansion done by GetBound() this rectangle is
                    // guaranteed to include the (lat,lng) values of all points along AB.
                    bound_ = bound_.Union(S2LatLngRect.FromPointPair(a_latlng_, b_latlng));
                }
            }
            else
            {
                // Compute the longitude range spanned by AB.
                var lng_ab = S1Interval.FromPointPair(a_latlng_.LngRadians, b_latlng.LngRadians);
                if (lng_ab.GetLength() >= Math.PI - 2 * S2.DoubleEpsilon)
                {
                    // The points lie on nearly opposite lines of longitude to within the
                    // maximum error of the calculation.  (Note that this test relies on
                    // the fact that Math.PI is slightly less than the true value of Pi, and
                    // that representable values near Math.PI are 2 * S2Constants.DoubleEpsilon apart.)
                    // The easiest solution is to assume that AB could go on either side
                    // of the pole.
                    lng_ab = S1Interval.Full;
                }

                // Next we compute the latitude range spanned by the edge AB.  We start
                // with the range spanning the two endpoints of the edge:
                var lat_ab = R1Interval.FromPointPair(a_latlng_.LatRadians, b_latlng.LatRadians);

                // This is the desired range unless the edge AB crosses the plane
                // through N and the Z-axis (which is where the great circle through A
                // and B attains its minimum and maximum latitudes).  To test whether AB
                // crosses this plane, we compute a vector M perpendicular to this
                // plane and then project A and B onto it.
                var m   = n.CrossProd(new S2Point(0, 0, 1));
                var m_a = m.DotProd(a_);
                var m_b = m.DotProd(b);

                // We want to test the signs of "m_a" and "m_b", so we need to bound
                // the error in these calculations.  It is possible to show that the
                // total error is bounded by
                //
                //  (1 + Math.Sqrt(3)) * S2Constants.DoubleEpsilon * n_norm + 8 * Math.Sqrt(3) * (S2Constants.DoubleEpsilon**2)
                //    = 6.06638e-16 * n_norm + 6.83174e-31

                double m_error = 6.06638e-16 * n_norm + 6.83174e-31;
                if (m_a * m_b < 0 || Math.Abs(m_a) <= m_error || Math.Abs(m_b) <= m_error)
                {
                    // Minimum/maximum latitude *may* occur in the edge interior.
                    //
                    // The maximum latitude is 90 degrees minus the latitude of N.  We
                    // compute this directly using atan2 in order to get maximum accuracy
                    // near the poles.
                    //
                    // Our goal is compute a bound that contains the computed latitudes of
                    // all S2Points P that pass the point-in-polygon containment test.
                    // There are three sources of error we need to consider:
                    //  - the directional error in N (at most 3.84 * S2Constants.DoubleEpsilon)
                    //  - converting N to a maximum latitude
                    //  - computing the latitude of the test point P
                    // The latter two sources of error are at most 0.955 * S2Constants.DoubleEpsilon
                    // individually, but it is possible to show by a more complex analysis
                    // that together they can add up to at most 1.16 * S2Constants.DoubleEpsilon, for a
                    // total error of 5 * S2Constants.DoubleEpsilon.
                    //
                    // We add 3 * S2Constants.DoubleEpsilon to the bound here, and GetBound() will pad
                    // the bound by another 2 * S2Constants.DoubleEpsilon.
                    var max_lat = Math.Min(
                        Math.Atan2(Math.Sqrt(n[0] * n[0] + n[1] * n[1]), Math.Abs(n[2])) + 3 * S2.DoubleEpsilon,
                        S2.M_PI_2);

                    // In order to get tight bounds when the two points are close together,
                    // we also bound the min/max latitude relative to the latitudes of the
                    // endpoints A and B.  First we compute the distance between A and B,
                    // and then we compute the maximum change in latitude between any two
                    // points along the great circle that are separated by this distance.
                    // This gives us a latitude change "budget".  Some of this budget must
                    // be spent getting from A to B; the remainder bounds the round-trip
                    // distance (in latitude) from A or B to the min or max latitude
                    // attained along the edge AB.
                    //
                    // There is a maximum relative error of 4.5 * DBL_EPSILON in computing
                    // the squared distance (a_ - b), which means a maximum error of (4.5
                    // / 2 + 0.5) == 2.75 * DBL_EPSILON in computing Norm().  The sin()
                    // and multiply each have a relative error of 0.5 * DBL_EPSILON which
                    // we round up to a total of 4 * DBL_EPSILON.
                    var          lat_budget_z = 0.5 * (a_ - b).Norm() * Math.Sin(max_lat);
                    const double folded       = (1 + 4 * S2.DoubleEpsilon);
                    var          lat_budget   = 2 * Math.Asin(Math.Min(folded * lat_budget_z, 1.0));
                    var          max_delta    = 0.5 * (lat_budget - lat_ab.GetLength()) + S2.DoubleEpsilon;

                    // Test whether AB passes through the point of maximum latitude or
                    // minimum latitude.  If the dot product(s) are small enough then the
                    // result may be ambiguous.
                    if (m_a <= m_error && m_b >= -m_error)
                    {
                        lat_ab = new R1Interval(lat_ab.Lo, Math.Min(max_lat, lat_ab.Hi + max_delta));
                    }
                    if (m_b <= m_error && m_a >= -m_error)
                    {
                        lat_ab = new R1Interval(Math.Max(-max_lat, lat_ab.Lo - max_delta), lat_ab.Lo);
                    }
                }
                bound_ = bound_.Union(new S2LatLngRect(lat_ab, lng_ab));
            }
        }
        a_        = b;
        a_latlng_ = b_latlng;
    }
Esempio n. 18
0
        static void TestSubdivide(S2Cell cell)
        {
            GatherStats(cell);
            if (cell.IsLeaf())
            {
                return;
            }

            var children = new S2Cell[4];

            Assert.True(cell.Subdivide(children));
            S2CellId child_id     = cell.Id.ChildBegin();
            double   exact_area   = 0;
            double   approx_area  = 0;
            double   average_area = 0;

            for (int i = 0; i < 4; ++i, child_id = child_id.Next())
            {
                exact_area   += children[i].ExactArea();
                approx_area  += children[i].ApproxArea();
                average_area += children[i].AverageArea();

                // Check that the child geometry is consistent with its cell ID.
                Assert.Equal(child_id, children[i].Id);
                Assert.True(S2.ApproxEquals(children[i].Center(), child_id.ToPoint()));
                S2Cell direct = new(child_id);
                Assert.Equal(direct.Face, children[i].Face);
                Assert.Equal(direct.Level, children[i].Level);
                Assert.Equal(direct.Orientation, children[i].Orientation);
                Assert.Equal(direct.CenterRaw(), children[i].CenterRaw());
                for (int k = 0; k < 4; ++k)
                {
                    Assert.Equal(direct.VertexRaw(k), children[i].VertexRaw(k));
                    Assert.Equal(direct.EdgeRaw(k), children[i].EdgeRaw(k));
                }

                // Test Contains() and MayIntersect().
                Assert.True(cell.Contains(children[i]));
                Assert.True(cell.MayIntersect(children[i]));
                Assert.False(children[i].Contains(cell));
                Assert.True(cell.Contains(children[i].CenterRaw()));
                for (int j = 0; j < 4; ++j)
                {
                    Assert.True(cell.Contains(children[i].VertexRaw(j)));
                    if (j != i)
                    {
                        Assert.False(children[i].Contains(children[j].CenterRaw()));
                        Assert.False(children[i].MayIntersect(children[j]));
                    }
                }

                // Test GetCapBound and GetRectBound.
                S2Cap        parent_cap  = cell.GetCapBound();
                S2LatLngRect parent_rect = cell.GetRectBound();
                if (cell.Contains(new S2Point(0, 0, 1)) || cell.Contains(new S2Point(0, 0, -1)))
                {
                    Assert.True(parent_rect.Lng.IsFull());
                }
                S2Cap        child_cap  = children[i].GetCapBound();
                S2LatLngRect child_rect = children[i].GetRectBound();
                Assert.True(child_cap.Contains(children[i].Center()));
                Assert.True(child_rect.Contains(children[i].CenterRaw()));
                Assert.True(parent_cap.Contains(children[i].Center()));
                Assert.True(parent_rect.Contains(children[i].CenterRaw()));
                for (int j = 0; j < 4; ++j)
                {
                    Assert.True(child_cap.Contains(children[i].Vertex(j)));
                    Assert.True(child_rect.Contains(children[i].Vertex(j)));
                    Assert.True(child_rect.Contains(children[i].VertexRaw(j)));
                    Assert.True(parent_cap.Contains(children[i].Vertex(j)));
                    Assert.True(parent_rect.Contains(children[i].Vertex(j)));
                    Assert.True(parent_rect.Contains(children[i].VertexRaw(j)));
                    if (j != i)
                    {
                        // The bounding caps and rectangles should be tight enough so that
                        // they exclude at least two vertices of each adjacent cell.
                        int cap_count  = 0;
                        int rect_count = 0;
                        for (int k = 0; k < 4; ++k)
                        {
                            if (child_cap.Contains(children[j].Vertex(k)))
                            {
                                ++cap_count;
                            }
                            if (child_rect.Contains(children[j].VertexRaw(k)))
                            {
                                ++rect_count;
                            }
                        }
                        Assert.True(cap_count <= 2);
                        if (child_rect.LatLo().Radians > -S2.M_PI_2 &&
                            child_rect.LatHi().Radians < S2.M_PI_2)
                        {
                            // Bounding rectangles may be too large at the poles because the
                            // pole itself has an arbitrary fixed longitude.
                            Assert.True(rect_count <= 2);
                        }
                    }
                }

                // Check all children for the first few levels, and then sample randomly.
                // We also always subdivide the cells containing a few chosen points so
                // that we have a better chance of sampling the minimum and maximum metric
                // values.  kMaxSizeUV is the absolute value of the u- and v-coordinate
                // where the cell size at a given level is maximal.
                double    kMaxSizeUV = 0.3964182625366691;
                R2Point[] special_uv =
                {
                    new R2Point(S2.DoubleEpsilon, S2.DoubleEpsilon), // Face center
                    new R2Point(S2.DoubleEpsilon, 1),                // Edge midpoint
                    new R2Point(1, 1),                               // Face corner
                    new R2Point(kMaxSizeUV, kMaxSizeUV),             // Largest cell area
                    new R2Point(S2.DoubleEpsilon, kMaxSizeUV),       // Longest edge/diagonal
                };
                bool force_subdivide = false;
                foreach (R2Point uv in special_uv)
                {
                    if (children[i].BoundUV.Contains(uv))
                    {
                        force_subdivide = true;
                    }
                }

                var debugFlag =
#if s2debug
                    true;
#else
                    false;
#endif

                if (force_subdivide ||
                    cell.Level < (debugFlag ? 5 : 6) ||
                    S2Testing.Random.OneIn(debugFlag ? 5 : 4))
                {
                    TestSubdivide(children[i]);
                }
            }

            // Check sum of child areas equals parent area.
            //
            // For ExactArea(), the best relative error we can expect is about 1e-6
            // because the precision of the unit vector coordinates is only about 1e-15
            // and the edge length of a leaf cell is about 1e-9.
            //
            // For ApproxArea(), the areas are accurate to within a few percent.
            //
            // For AverageArea(), the areas themselves are not very accurate, but
            // the average area of a parent is exactly 4 times the area of a child.

            Assert.True(Math.Abs(Math.Log(exact_area / cell.ExactArea())) <= Math.Abs(Math.Log((1 + 1e-6))));
            Assert.True(Math.Abs(Math.Log((approx_area / cell.ApproxArea()))) <= Math.Abs(Math.Log((1.03))));
            Assert.True(Math.Abs(Math.Log((average_area / cell.AverageArea()))) <= Math.Abs(Math.Log((1 + 1e-15))));
        }