コード例 #1
0
        /**
  * 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.
  */

        private void testIntervalOps(R1Interval x, R1Interval y, String expectedRelation)
        {
            JavaAssert.Equal(x.Contains(y), expectedRelation[0] == 'T');
            JavaAssert.Equal(x.InteriorContains(y), expectedRelation[1] == 'T');
            JavaAssert.Equal(x.Intersects(y), expectedRelation[2] == 'T');
            JavaAssert.Equal(x.InteriorIntersects(y), expectedRelation[3] == 'T');

            JavaAssert.Equal(x.Contains(y), x.Union(y).Equals(x));
            JavaAssert.Equal(x.Intersects(y), !x.Intersection(y).IsEmpty);
        }
コード例 #2
0
        /**
         * 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.
         */

        private void testIntervalOps(R1Interval x, R1Interval y, String expectedRelation)
        {
            JavaAssert.Equal(x.Contains(y), expectedRelation[0] == 'T');
            JavaAssert.Equal(x.InteriorContains(y), expectedRelation[1] == 'T');
            JavaAssert.Equal(x.Intersects(y), expectedRelation[2] == 'T');
            JavaAssert.Equal(x.InteriorIntersects(y), expectedRelation[3] == 'T');

            JavaAssert.Equal(x.Contains(y), x.Union(y).Equals(x));
            JavaAssert.Equal(x.Intersects(y), !x.Intersection(y).IsEmpty);
        }
コード例 #3
0
        private static void TestIntervalOps(R1Interval x, R1Interval y, string expected)
        {
            // Test all of the interval operations on the given pair of intervals.
            // "expected" is a sequence of "T" and "F" characters corresponding to
            // the expected results of Contains(), InteriorContains(), Intersects(),
            // and InteriorIntersects() respectively.

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

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

            R1Interval z = x;

            z = R1Interval.AddInterval(z, y);
            Assert.Equal(x.Union(y), z);
        }
コード例 #4
0
ファイル: S2LatLngRectTests.cs プロジェクト: alas/s2geometry
    public void Test_S2LatLngRect_GetCentroid()
    {
        // Empty and full rectangles.
        Assert.Equal(new S2Point(), S2LatLngRect.Empty.Centroid());
        Assert.True(S2LatLngRect.Full.Centroid().Norm() <= 1e-15);

        // Rectangles that cover the full longitude range.
        for (int i = 0; i < 100; ++i)
        {
            double       lat1     = S2Testing.Random.UniformDouble(-S2.M_PI_2, S2.M_PI_2);
            double       lat2     = S2Testing.Random.UniformDouble(-S2.M_PI_2, S2.M_PI_2);
            S2LatLngRect r        = new(R1Interval.FromPointPair(lat1, lat2), S1Interval.Full);
            S2Point      centroid = r.Centroid();
            Assert2.Near(0.5 * (Math.Sin(lat1) + Math.Sin(lat2)) * r.Area(), centroid.Z, S2.DoubleError);
            Assert.True(new R2Point(centroid.X, centroid.Y).GetNorm() <= 1e-15);
        }

        // Rectangles that cover the full latitude range.
        for (int i = 0; i < 100; ++i)
        {
            double       lng1 = S2Testing.Random.UniformDouble(-Math.PI, Math.PI);
            double       lng2 = S2Testing.Random.UniformDouble(-Math.PI, Math.PI);
            S2LatLngRect r    = new(S2LatLngRect.FullLat,
                                    S1Interval.FromPointPair(lng1, lng2));
            S2Point centroid = r.Centroid();
            Assert.True(Math.Abs(centroid.Z) <= 1e-15);
            Assert2.Near(r.Lng.GetCenter(), new S2LatLng(centroid).LngRadians, S2.DoubleError);
            double alpha = 0.5 * r.Lng.GetLength();
            // TODO(Alas): the next Assert fails sometimes
            Assert2.Near(0.25 * Math.PI * Math.Sin(alpha) / alpha * r.Area(),
                         new R2Point(centroid.X, centroid.Y).GetNorm(), S2.DoubleError);
        }

        // Finally, verify that when a rectangle is recursively split into pieces,
        // the centroids of the pieces add to give the centroid of their parent.
        // To make the code simpler we avoid rectangles that cross the 180 degree
        // line of longitude.
        TestCentroidSplitting(
            new S2LatLngRect(S2LatLngRect.FullLat, new S1Interval(-3.14, 3.14)),
            10 /*splits_left*/);
    }
コード例 #5
0
        public void Test_R1Interval_ApproxEquals()
        {
            // Choose two values kLo and kHi such that it's okay to shift an endpoint by
            // kLo (i.e., the resulting interval is equivalent) but not by kHi.
            const double kLo = 4 * S2.DoubleEpsilon;  // < max_error default
            const double kHi = 6 * S2.DoubleEpsilon;  // > max_error default

            // Empty intervals.
            R1Interval empty = R1Interval.Empty;

            Assert.True(empty.ApproxEquals(empty));
            Assert.True(new R1Interval(0, 0).ApproxEquals(empty));
            Assert.True(empty.ApproxEquals(new R1Interval(0, 0)));
            Assert.True(new R1Interval(1, 1).ApproxEquals(empty));
            Assert.True(empty.ApproxEquals(new R1Interval(1, 1)));
            Assert.False(empty.ApproxEquals(new R1Interval(0, 1)));
            Assert.True(empty.ApproxEquals(new R1Interval(1, 1 + 2 * kLo)));
            Assert.False(empty.ApproxEquals(new R1Interval(1, 1 + 2 * kHi)));

            // Singleton intervals.
            Assert.True(new R1Interval(1, 1).ApproxEquals(new R1Interval(1, 1)));
            Assert.True(new R1Interval(1, 1).ApproxEquals(new R1Interval(1 - kLo, 1 - kLo)));
            Assert.True(new R1Interval(1, 1).ApproxEquals(new R1Interval(1 + kLo, 1 + kLo)));
            Assert.False(new R1Interval(1, 1).ApproxEquals(new R1Interval(1 - kHi, 1)));
            Assert.False(new R1Interval(1, 1).ApproxEquals(new R1Interval(1, 1 + kHi)));
            Assert.True(new R1Interval(1, 1).ApproxEquals(new R1Interval(1 - kLo, 1 + kLo)));
            Assert.False(new R1Interval(0, 0).ApproxEquals(new R1Interval(1, 1)));

            // Other intervals.
            Assert.True(new R1Interval(1 - kLo, 2 + kLo).ApproxEquals(new R1Interval(1, 2)));
            Assert.True(new R1Interval(1 + kLo, 2 - kLo).ApproxEquals(new R1Interval(1, 2)));
            Assert.False(new R1Interval(1 - kHi, 2 + kLo).ApproxEquals(new R1Interval(1, 2)));
            Assert.False(new R1Interval(1 + kHi, 2 - kLo).ApproxEquals(new R1Interval(1, 2)));
            Assert.False(new R1Interval(1 - kLo, 2 + kHi).ApproxEquals(new R1Interval(1, 2)));
            Assert.False(new R1Interval(1 + kLo, 2 - kHi).ApproxEquals(new R1Interval(1, 2)));
        }
コード例 #6
0
        public void R1IntervalBasicTest()
        {
            // Constructors and accessors.
            var unit = new R1Interval(0, 1);
            var negunit = new R1Interval(-1, 0);
            JavaAssert.Equal(unit.Lo, 0.0);
            JavaAssert.Equal(unit.Hi, 1.0);
            JavaAssert.Equal(negunit.Lo, -1.0);
            JavaAssert.Equal(negunit.Hi, 0.0);

            // is_empty()
            var half = new R1Interval(0.5, 0.5);
            Assert.True(!unit.IsEmpty);
            Assert.True(!half.IsEmpty);
            var empty = R1Interval.Empty;
            Assert.True(empty.IsEmpty);

            // GetCenter(), GetLength()
            JavaAssert.Equal(unit.Center, 0.5);
            JavaAssert.Equal(half.Center, 0.5);
            JavaAssert.Equal(negunit.Length, 1.0);
            JavaAssert.Equal(half.Length, 0.0);
            Assert.True(empty.Length < 0);

            // contains(double), interiorContains(double)
            Assert.True(unit.Contains(0.5));
            Assert.True(unit.InteriorContains(0.5));
            Assert.True(unit.Contains(0));
            Assert.True(!unit.InteriorContains(0));
            Assert.True(unit.Contains(1));
            Assert.True(!unit.InteriorContains(1));

            // contains(R1Interval), interiorContains(R1Interval)
            // Intersects(R1Interval), InteriorIntersects(R1Interval)
            testIntervalOps(empty, empty, "TTFF");
            testIntervalOps(empty, unit, "FFFF");
            testIntervalOps(unit, half, "TTTT");
            testIntervalOps(unit, unit, "TFTT");
            testIntervalOps(unit, empty, "TTFF");
            testIntervalOps(unit, negunit, "FFTF");
            testIntervalOps(unit, new R1Interval(0, 0.5), "TFTT");
            testIntervalOps(half, new R1Interval(0, 0.5), "FFTF");

            // addPoint()
            R1Interval r;
            r = empty.AddPoint(5);
            Assert.True(r.Lo == 5.0 && r.Hi == 5.0);
            r = r.AddPoint(-1);
            Assert.True(r.Lo == -1.0 && r.Hi == 5.0);
            r = r.AddPoint(0);
            Assert.True(r.Lo == -1.0 && r.Hi == 5.0);

            // fromPointPair()
            JavaAssert.Equal(R1Interval.FromPointPair(4, 4), new R1Interval(4, 4));
            JavaAssert.Equal(R1Interval.FromPointPair(-1, -2), new R1Interval(-2, -1));
            JavaAssert.Equal(R1Interval.FromPointPair(-5, 3), new R1Interval(-5, 3));

            // expanded()
            JavaAssert.Equal(empty.Expanded(0.45), empty);
            JavaAssert.Equal(unit.Expanded(0.5), new R1Interval(-0.5, 1.5));

            // union(), intersection()
            Assert.True(new R1Interval(99, 100).Union(empty).Equals(new R1Interval(99, 100)));
            Assert.True(empty.Union(new R1Interval(99, 100)).Equals(new R1Interval(99, 100)));
            Assert.True(new R1Interval(5, 3).Union(new R1Interval(0, -2)).IsEmpty);
            Assert.True(new R1Interval(0, -2).Union(new R1Interval(5, 3)).IsEmpty);
            Assert.True(unit.Union(unit).Equals(unit));
            Assert.True(unit.Union(negunit).Equals(new R1Interval(-1, 1)));
            Assert.True(negunit.Union(unit).Equals(new R1Interval(-1, 1)));
            Assert.True(half.Union(unit).Equals(unit));
            Assert.True(unit.Intersection(half).Equals(half));
            Assert.True(unit.Intersection(negunit).Equals(new R1Interval(0, 0)));
            Assert.True(negunit.Intersection(half).IsEmpty);
            Assert.True(unit.Intersection(empty).IsEmpty);
            Assert.True(empty.Intersection(unit).IsEmpty);
        }
コード例 #7
0
        public void R1IntervalBasicTest()
        {
            // Constructors and accessors.
            var unit    = new R1Interval(0, 1);
            var negunit = new R1Interval(-1, 0);

            JavaAssert.Equal(unit.Lo, 0.0);
            JavaAssert.Equal(unit.Hi, 1.0);
            JavaAssert.Equal(negunit.Lo, -1.0);
            JavaAssert.Equal(negunit.Hi, 0.0);

            // is_empty()
            var half = new R1Interval(0.5, 0.5);

            Assert.True(!unit.IsEmpty);
            Assert.True(!half.IsEmpty);
            var empty = R1Interval.Empty;

            Assert.True(empty.IsEmpty);

            // GetCenter(), GetLength()
            JavaAssert.Equal(unit.Center, 0.5);
            JavaAssert.Equal(half.Center, 0.5);
            JavaAssert.Equal(negunit.Length, 1.0);
            JavaAssert.Equal(half.Length, 0.0);
            Assert.True(empty.Length < 0);

            // contains(double), interiorContains(double)
            Assert.True(unit.Contains(0.5));
            Assert.True(unit.InteriorContains(0.5));
            Assert.True(unit.Contains(0));
            Assert.True(!unit.InteriorContains(0));
            Assert.True(unit.Contains(1));
            Assert.True(!unit.InteriorContains(1));

            // contains(R1Interval), interiorContains(R1Interval)
            // Intersects(R1Interval), InteriorIntersects(R1Interval)
            testIntervalOps(empty, empty, "TTFF");
            testIntervalOps(empty, unit, "FFFF");
            testIntervalOps(unit, half, "TTTT");
            testIntervalOps(unit, unit, "TFTT");
            testIntervalOps(unit, empty, "TTFF");
            testIntervalOps(unit, negunit, "FFTF");
            testIntervalOps(unit, new R1Interval(0, 0.5), "TFTT");
            testIntervalOps(half, new R1Interval(0, 0.5), "FFTF");

            // addPoint()
            R1Interval r;

            r = empty.AddPoint(5);
            Assert.True(r.Lo == 5.0 && r.Hi == 5.0);
            r = r.AddPoint(-1);
            Assert.True(r.Lo == -1.0 && r.Hi == 5.0);
            r = r.AddPoint(0);
            Assert.True(r.Lo == -1.0 && r.Hi == 5.0);

            // fromPointPair()
            JavaAssert.Equal(R1Interval.FromPointPair(4, 4), new R1Interval(4, 4));
            JavaAssert.Equal(R1Interval.FromPointPair(-1, -2), new R1Interval(-2, -1));
            JavaAssert.Equal(R1Interval.FromPointPair(-5, 3), new R1Interval(-5, 3));

            // expanded()
            JavaAssert.Equal(empty.Expanded(0.45), empty);
            JavaAssert.Equal(unit.Expanded(0.5), new R1Interval(-0.5, 1.5));

            // union(), intersection()
            Assert.True(new R1Interval(99, 100).Union(empty).Equals(new R1Interval(99, 100)));
            Assert.True(empty.Union(new R1Interval(99, 100)).Equals(new R1Interval(99, 100)));
            Assert.True(new R1Interval(5, 3).Union(new R1Interval(0, -2)).IsEmpty);
            Assert.True(new R1Interval(0, -2).Union(new R1Interval(5, 3)).IsEmpty);
            Assert.True(unit.Union(unit).Equals(unit));
            Assert.True(unit.Union(negunit).Equals(new R1Interval(-1, 1)));
            Assert.True(negunit.Union(unit).Equals(new R1Interval(-1, 1)));
            Assert.True(half.Union(unit).Equals(unit));
            Assert.True(unit.Intersection(half).Equals(half));
            Assert.True(unit.Intersection(negunit).Equals(new R1Interval(0, 0)));
            Assert.True(negunit.Intersection(half).IsEmpty);
            Assert.True(unit.Intersection(empty).IsEmpty);
            Assert.True(empty.Intersection(unit).IsEmpty);
        }
コード例 #8
0
        public void AddPoint(S2Point b)
        {
            // assert (S2.isUnitLength(b));

            var bLatLng = new S2LatLng(b);

            if (bound.IsEmpty)
            {
                bound = bound.AddPoint(bLatLng);
            }
            else
            {
                // We can't just call bound.addPoint(bLatLng) here, since we need to
                // ensure that all the longitudes between "a" and "b" are included.
                bound = bound.Union(S2LatLngRect.FromPointPair(aLatLng, bLatLng));

                // Check whether the Min/Max latitude occurs in the edge interior.
                // We find the normal to the plane containing AB, and then a vector
                // "dir" in this plane that also passes through the equator. We use
                // RobustCrossProd to ensure that the edge normal is accurate even
                // when the two points are very close together.
                var aCrossB = S2.RobustCrossProd(a, b);
                var dir = S2Point.CrossProd(aCrossB, new S2Point(0, 0, 1));
                var da = dir.DotProd(a);
                var db = dir.DotProd(b);

                if (da*db < 0)
                {
                    // Minimum/maximum latitude occurs in the edge interior. This affects
                    // the latitude bounds but not the longitude bounds.
                    var absLat = Math.Acos(Math.Abs(aCrossB[2]/aCrossB.Norm));
                    var lat = bound.Lat;
                    if (da < 0)
                    {
                        // It's possible that absLat < lat.lo() due to numerical errors.
                        lat = new R1Interval(lat.Lo, Math.Max(absLat, bound.Lat.Hi));
                    }
                    else
                    {
                        lat = new R1Interval(Math.Min(-absLat, bound.Lat.Lo), lat.Hi);
                    }
                    bound = new S2LatLngRect(lat, bound.Lng);
                }
            }
            a = b;
            aLatLng = bLatLng;
        }
コード例 #9
0
ファイル: S2PaddedCellTests.cs プロジェクト: alas/s2geometry
 private static double SampleInterval(R1Interval x)
 => S2Testing.Random.UniformDouble(x.Lo, x.Hi);
コード例 #10
0
        public void Test_R1Interval_TestBasic()
        {
            // Constructors and accessors.
            R1Interval unit    = new(0, 1);
            R1Interval negunit = new(-1, 0);

            Assert.Equal(0, unit.Lo);
            Assert.Equal(1, unit.Hi);
            Assert.Equal(-1, negunit[0]);
            Assert.Equal(0, negunit[1]);
            R1Interval ten = new(0, 10);

            Assert.Equal(new R1Interval(0, 10), ten);
            ten = new R1Interval(-10, ten.Hi);
            Assert.Equal(new R1Interval(-10, 10), ten);
            ten = new R1Interval(ten.Lo, 0);
            Assert.True(new R1Interval(-10, 0) == ten);
            ten = new R1Interval(0, 10);
            Assert.Equal(new R1Interval(0, 10), ten);

            // IsEmpty
            R1Interval half = new(0.5, 0.5);

            Assert.False(unit.IsEmpty());
            Assert.False(half.IsEmpty());
            R1Interval empty = R1Interval.Empty;

            Assert.True(empty.IsEmpty());

            // == and !=
            Assert.True(empty == R1Interval.Empty);
            Assert.True(unit == new R1Interval(0, 1));
            Assert.True(unit != empty);
            Assert.True(new R1Interval(1, 2) != new R1Interval(1, 3));

            // Check that the default R1Interval is identical to Empty().
            R1Interval default_empty = R1Interval.Empty;

            Assert.True(default_empty.IsEmpty());
            Assert.Equal(empty.Lo, default_empty.Lo);
            Assert.Equal(empty.Hi, default_empty.Hi);

            // GetCenter(), GetLength()
            Assert.Equal(0.5, unit.GetCenter());
            Assert.Equal(0.5, half.GetCenter());
            Assert.Equal(1.0, negunit.GetLength());
            Assert.Equal(0, half.GetLength());
            Assert.True(empty.GetLength() < 0);

            // Contains(double), InteriorContains(double)
            Assert.True(unit.Contains(0.5));
            Assert.True(unit.InteriorContains(0.5));
            Assert.True(unit.Contains(0));
            Assert.False(unit.InteriorContains(0));
            Assert.True(unit.Contains(1));
            Assert.False(unit.InteriorContains(1));

            // Contains(R1Interval), InteriorContains(R1Interval)
            // Intersects(R1Interval), InteriorIntersects(R1Interval)
            TestIntervalOps(empty, empty, "TTFF");
            TestIntervalOps(empty, unit, "FFFF");
            TestIntervalOps(unit, half, "TTTT");
            TestIntervalOps(unit, unit, "TFTT");
            TestIntervalOps(unit, empty, "TTFF");
            TestIntervalOps(unit, negunit, "FFTF");
            TestIntervalOps(unit, new R1Interval(0, 0.5), "TFTT");
            TestIntervalOps(half, new R1Interval(0, 0.5), "FFTF");

            // AddPoint()
            R1Interval r = empty;

            r = R1Interval.AddPoint(r, 5);
            Assert.Equal(5, r.Lo);
            Assert.Equal(5, r.Hi);
            r = R1Interval.AddPoint(r, -1);
            Assert.Equal(-1, r.Lo);
            Assert.Equal(5, r.Hi);
            r = R1Interval.AddPoint(r, 0);
            Assert.Equal(-1, r.Lo);
            Assert.Equal(5, r.Hi);

            // Project()
            Assert.Equal(0.3, new R1Interval(0.1, 0.4).Project(0.3));
            Assert.Equal(0.1, new R1Interval(0.1, 0.4).Project(-7.0));
            Assert.Equal(0.4, new R1Interval(0.1, 0.4).Project(0.6));

            // FromPointPair()
            Assert.Equal(new R1Interval(4, 4), R1Interval.FromPointPair(4, 4));
            Assert.Equal(new R1Interval(-2, -1), R1Interval.FromPointPair(-1, -2));
            Assert.Equal(new R1Interval(-5, 3), R1Interval.FromPointPair(-5, 3));

            // Expanded()
            Assert.Equal(empty, empty.Expanded(0.45));
            Assert.Equal(new R1Interval(-0.5, 1.5), unit.Expanded(0.5));
            Assert.Equal(new R1Interval(0.5, 0.5), unit.Expanded(-0.5));
            Assert.True(unit.Expanded(-0.51).IsEmpty());
            Assert.True(unit.Expanded(-0.51).Expanded(0.51).IsEmpty());

            // Union(), Intersection()
            Assert.Equal(new R1Interval(99, 100), new R1Interval(99, 100).Union(empty));
            Assert.Equal(new R1Interval(99, 100), empty.Union(new R1Interval(99, 100)));
            Assert.True(new R1Interval(5, 3).Union(new R1Interval(0, -2)).IsEmpty());
            Assert.True(new R1Interval(0, -2).Union(new R1Interval(5, 3)).IsEmpty());
            Assert.Equal(unit, unit.Union(unit));
            Assert.Equal(new R1Interval(-1, 1), unit.Union(negunit));
            Assert.Equal(new R1Interval(-1, 1), negunit.Union(unit));
            Assert.Equal(unit, half.Union(unit));
            Assert.Equal(half, unit.Intersection(half));
            Assert.Equal(new R1Interval(0, 0), unit.Intersection(negunit));
            Assert.True(negunit.Intersection(half).IsEmpty());
            Assert.True(unit.Intersection(empty).IsEmpty());
            Assert.True(empty.Intersection(unit).IsEmpty());
        }
コード例 #11
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;
    }