private void assertCrossing(S2Point a,
                                    S2Point b,
                                    S2Point c,
                                    S2Point d,
                                    int robust,
                                    bool edgeOrVertex,
                                    bool simple)
        {
            a = S2Point.Normalize(a);
            b = S2Point.Normalize(b);
            c = S2Point.Normalize(c);
            d = S2Point.Normalize(d);

            compareResult(S2EdgeUtil.RobustCrossing(a, b, c, d), robust);
            if (simple)
            {
                assertEquals(robust > 0, S2EdgeUtil.SimpleCrossing(a, b, c, d));
            }
            var crosser = new EdgeCrosser(a, b, c);
            compareResult(crosser.RobustCrossing(d), robust);
            compareResult(crosser.RobustCrossing(c), robust);

            assertEquals(S2EdgeUtil.EdgeOrVertexCrossing(a, b, c, d), edgeOrVertex);
            assertEquals(edgeOrVertex, crosser.EdgeOrVertexCrossing(d));
            assertEquals(edgeOrVertex, crosser.EdgeOrVertexCrossing(c));
        }
 private void assertCrossings(S2Point a,
                              S2Point b,
                              S2Point c,
                              S2Point d,
                              int robust,
                              bool edgeOrVertex,
                              bool simple)
 {
     assertCrossing(a, b, c, d, robust, edgeOrVertex, simple);
     assertCrossing(b, a, c, d, robust, edgeOrVertex, simple);
     assertCrossing(a, b, d, c, robust, edgeOrVertex, simple);
     assertCrossing(b, a, d, c, robust, edgeOrVertex, simple);
     assertCrossing(a, a, c, d, DEGENERATE, false, false);
     assertCrossing(a, b, c, c, DEGENERATE, false, false);
     assertCrossing(a, b, a, b, 0, true, false);
     assertCrossing(c, d, a, b, robust, (edgeOrVertex ^ (robust == 0)), simple);
 }
        public void testCoverage()
        {
            Trace.WriteLine("TestCoverage");
            // Make sure that random points on the sphere can be represented to the
            // expected level of accuracy, which in the worst case is sqrt(2/3) times
            // the maximum arc length between the points on the sphere associated with
            // adjacent values of "i" or "j". (It is sqrt(2/3) rather than 1/2 because
            // the cells at the corners of each face are stretched -- they have 60 and
            // 120 degree angles.)

            var maxDist = 0.5 * S2Projections.MaxDiag.GetValue(S2CellId.MaxLevel);

            for (var i = 0; i < 1000000; ++i)
            {
                // randomPoint();
                var p = new S2Point(0.37861576725894824, 0.2772406863275093, 0.8830558887338725);
                var q = S2CellId.FromPoint(p).ToPointRaw();

                Assert.True(p.Angle(q) <= maxDist);
            }
        }
        private void assertWedge(S2Point a0,
                                 S2Point ab1,
                                 S2Point a2,
                                 S2Point b0,
                                 S2Point b2,
                                 bool contains,
                                 bool intersects,
                                 bool crosses)
        {
            a0  = S2Point.Normalize(a0);
            ab1 = S2Point.Normalize(ab1);
            a2  = S2Point.Normalize(a2);
            b0  = S2Point.Normalize(b0);
            b2  = S2Point.Normalize(b2);

            assertEquals(new WedgeContains().Test(a0, ab1, a2, b0, b2), contains ? 1 : 0);
            assertEquals(new WedgeIntersects().Test(a0, ab1, a2, b0, b2), intersects ? -1 : 0);
            assertEquals(new WedgeContainsOrIntersects().Test(a0, ab1, a2, b0, b2),
                         contains ? 1 : intersects ? -1 : 0);
            assertEquals(new WedgeContainsOrCrosses().Test(a0, ab1, a2, b0, b2),
                         contains ? 1 : crosses ? -1 : 0);
        }
Exemple #5
0
 // As above, but for maximum distances.  If one edge crosses the antipodal
 // reflection of the other, the distance is Pi.
 public static bool UpdateEdgePairMaxDistance(S2Point a0, S2Point a1, S2Point b0, S2Point b1, ref S1ChordAngle max_dist)
 {
     if (max_dist == S1ChordAngle.Straight)
     {
         return(false);
     }
     if (S2.CrossingSign(a0, a1, -b0, -b1) >= 0)
     {
         max_dist = S1ChordAngle.Straight;
         return(true);
     }
     // Otherwise, the maximum distance is achieved at an endpoint of at least
     // one of the two edges.  We use "|" rather than "||" below to ensure that
     // all four possibilities are always checked.
     //
     // The calculation below computes each of the six vertex-vertex distances
     // twice (this could be optimized).
     return(UpdateMaxDistance(a0, b0, b1, ref max_dist) |
            UpdateMaxDistance(a1, b0, b1, ref max_dist) |
            UpdateMaxDistance(b0, a0, a1, ref max_dist) |
            UpdateMaxDistance(b1, a0, a1, ref max_dist));
 }
        /**
   * Like SimpleCrossing, except that points that lie exactly on a line are
   * arbitrarily classified as being on one side or the other (according to the
   * rules of S2.robustCCW). It returns +1 if there is a crossing, -1 if there
   * is no crossing, and 0 if any two vertices from different edges are the
   * same. Returns 0 or -1 if either edge is degenerate. Properties of
   * robustCrossing:
   *
   *  (1) robustCrossing(b,a,c,d) == robustCrossing(a,b,c,d) (2)
   * robustCrossing(c,d,a,b) == robustCrossing(a,b,c,d) (3)
   * robustCrossing(a,b,c,d) == 0 if a==c, a==d, b==c, b==d (3)
   * robustCrossing(a,b,c,d) <= 0 if a==b or c==d
   *
   *  Note that if you want to check an edge against a *chain* of other edges,
   * it is much more efficient to use an EdgeCrosser (above).
   */

        public static int RobustCrossing(S2Point a, S2Point b, S2Point c, S2Point d)
        {
            // For there to be a crossing, the triangles ACB, CBD, BDA, DAC must
            // all have the same orientation (clockwise or counterclockwise).
            //
            // First we compute the orientation of ACB and BDA. We permute the
            // arguments to robustCCW so that we can reuse the cross-product of A and B.
            // Recall that when the arguments to robustCCW are permuted, the sign of the
            // result changes according to the sign of the permutation. Thus ACB and
            // ABC are oppositely oriented, while BDA and ABD are the same.
            var aCrossB = S2Point.CrossProd(a, b);
            var acb = -S2.RobustCcw(a, b, c, aCrossB);
            var bda = S2.RobustCcw(a, b, d, aCrossB);

            // If any two vertices are the same, the result is degenerate.
            if ((bda & acb) == 0)
            {
                return 0;
            }

            // If ABC and BDA have opposite orientations (the most common case),
            // there is no crossing.
            if (bda != acb)
            {
                return -1;
            }

            // Otherwise we compute the orientations of CBD and DAC, and check whether
            // their orientations are compatible with the other two triangles.
            var cCrossD = S2Point.CrossProd(c, d);
            var cbd = -S2.RobustCcw(c, d, b, cCrossD);
            if (cbd != acb)
            {
                return -1;
            }

            var dac = S2.RobustCcw(c, d, a, cCrossD);
            return (dac == acb) ? 1 : -1;
        }
        /**
   * This class allows a vertex chain v0, v1, v2, ... to be efficiently tested
   * for intersection with a given fixed edge AB.
   */

        /**
   * Return true if edge AB crosses CD at a point that is interior to both
   * edges. Properties:
   *
   *  (1) simpleCrossing(b,a,c,d) == simpleCrossing(a,b,c,d) (2)
   * simpleCrossing(c,d,a,b) == simpleCrossing(a,b,c,d)
   */

        public static bool SimpleCrossing(S2Point a, S2Point b, S2Point c, S2Point d)
        {
            // We compute simpleCCW() for triangles ACB, CBD, BDA, and DAC. All
            // of these triangles need to have the same orientation (CW or CCW)
            // for an intersection to exist. Note that this is slightly more
            // restrictive than the corresponding definition for planar edges,
            // since we need to exclude pairs of line segments that would
            // otherwise "intersect" by crossing two antipodal points.

            var ab = S2Point.CrossProd(a, b);
            var acb = -(ab.DotProd(c));
            var bda = ab.DotProd(d);
            if (acb*bda <= 0)
            {
                return false;
            }

            var cd = S2Point.CrossProd(c, d);
            var cbd = -(cd.DotProd(b));
            var dac = cd.DotProd(a);
            return (acb*cbd > 0) && (acb*dac > 0);
        }
Exemple #8
0
        private void addChain(Chain chain,
                              S2Point x,
                              S2Point y,
                              S2Point z,
                              double maxPerturbation,
                              S2PolygonBuilder builder)
        {
            // Transform the given edge chain to the frame (x,y,z), perturb each vertex
            // up to the given distance, and add it to the builder.

            var vertices = new List <S2Point>();

            getVertices(chain.str, x, y, z, maxPerturbation, vertices);
            if (chain.closed)
            {
                vertices.Add(vertices[0]);
            }
            for (var i = 1; i < vertices.Count; ++i)
            {
                builder.AddEdge(vertices[i - 1], vertices[i]);
            }
        }
        public void Test_IntLatLngSnapFunction_SnapPoint()
        {
            for (int iter = 0; iter < 1000; ++iter)
            {
                // Test that IntLatLngSnapFunction does not modify points that were
                // generated using the S2LatLng.From{E5,E6,E7} methods.  This ensures
                // that both functions are using bitwise-compatible conversion methods.
                S2Point  p  = S2Testing.RandomPoint();
                S2LatLng ll = new(p);
                S2Point  p5 = S2LatLng.FromE5(ll.Lat().E5(), ll.Lng().E5()).ToPoint();
                Assert.Equal(p5, new IntLatLngSnapFunction(5).SnapPoint(p5));
                S2Point p6 = S2LatLng.FromE6(ll.Lat().E6(), ll.Lng().E6()).ToPoint();
                Assert.Equal(p6, new IntLatLngSnapFunction(6).SnapPoint(p6));
                S2Point p7 = S2LatLng.FromE7(ll.Lat().E7(), ll.Lng().E7()).ToPoint();
                Assert.Equal(p7, new IntLatLngSnapFunction(7).SnapPoint(p7));

                // Make sure that we're not snapping using some lower exponent.
                S2Point p7not6 = S2LatLng.FromE7(10 * ll.Lat().E6() + 1,
                                                 10 * ll.Lng().E6() + 1).ToPoint();
                Assert.NotEqual(p7not6, new IntLatLngSnapFunction(6).SnapPoint(p7not6));
            }
        }
    private static void TestRoundtripEncoding(CodingHint hint)
    {
        // Ensures that the EncodedS2PointVector can be encoded and decoded without
        // loss.
        const int level       = 3;
        var       pointsArray = new S2Point[kBlockSize];

        pointsArray.Fill(EncodedValueToPoint(0, level));
        var pointsList = pointsArray.ToList();

        pointsList.Add(EncodedValueToPoint(0x78, level));
        pointsList.Add(EncodedValueToPoint(0x7a, level));
        pointsList.Add(EncodedValueToPoint(0x7c, level));
        pointsList.Add(EncodedValueToPoint(0x84, level));
        pointsArray = pointsList.ToArray();

        EncodedS2PointVector a_vector = new();
        Encoder a_encoder             = new();

        EncodedS2PointVector b_vector = new();
        Encoder b_encoder             = new();

        // Encode and decode from a vector<S2Point>.
        {
            EncodedS2PointVector.EncodeS2PointVector(pointsArray, hint, a_encoder);
            var decoder = a_encoder.Decoder();
            a_vector.Init(decoder);
        }
        Assert.Equal(pointsList, a_vector.Decode());

        // Encode and decode from an EncodedS2PointVector.
        {
            a_vector.Encode(b_encoder);
            var decoder = b_encoder.Decoder();
            b_vector.Init(decoder);
        }
        Assert.Equal(pointsList, b_vector.Decode());
    }
        public void testGetDistance()
        {
            // Error margin since we're doing numerical computations
            var epsilon = 1e-15;

            // A rectangle with (lat,lng) vertices (3,1), (3,-1), (-3,-1) and (-3,1)
            var inner = "3:1, 3:-1, -3:-1, -3:1;";
            // A larger rectangle with (lat,lng) vertices (4,2), (4,-2), (-4,-2) and
            // (-4,s)
            var outer = "4:2, 4:-2, -4:-2, -4:2;";


            var rect  = makePolygon(inner);
            var shell = makePolygon(inner + outer);

            // All of the vertices of a polygon should be distance 0
            for (var i = 0; i < shell.NumLoops; i++)
            {
                for (var j = 0; j < shell.Loop(i).NumVertices; j++)
                {
                    assertEquals(0d, shell.GetDistance(shell.Loop(i).Vertex(j)).Radians, epsilon);
                }
            }

            // A non-vertex point on an edge should be distance 0
            assertEquals(0d, rect.GetDistance(
                             S2Point.Normalize(rect.Loop(0).Vertex(0) + rect.Loop(0).Vertex(1))).Radians,
                         epsilon);

            var origin = S2LatLng.FromDegrees(0, 0).ToPoint();

            // rect contains the origin
            assertEquals(0d, rect.GetDistance(origin).Radians, epsilon);

            // shell does NOT contain the origin, since it has a hole. The shortest
            // distance is to (1,0) or (-1,0), and should be 1 degree
            assertEquals(1d, shell.GetDistance(origin).Degrees, epsilon);
        }
Exemple #12
0
 public void Test_S2Cell_GetDistanceToPoint()
 {
     S2Testing.Random.Reset(S2Testing.Random.RandomSeed);
     for (int iter = 0; iter < 1000; ++iter)
     {
         _logger.WriteLine($"Iteration {iter}");
         S2Cell  cell   = new(S2Testing.GetRandomCellId());
         S2Point target = S2Testing.RandomPoint();
         S1Angle expected_to_boundary =
             GetDistanceToPointBruteForce(cell, target).ToAngle();
         S1Angle expected_to_interior =
             cell.Contains(target) ? S1Angle.Zero : expected_to_boundary;
         S1Angle expected_max =
             GetMaxDistanceToPointBruteForce(cell, target).ToAngle();
         S1Angle actual_to_boundary = cell.BoundaryDistance(target).ToAngle();
         S1Angle actual_to_interior = cell.Distance(target).ToAngle();
         S1Angle actual_max         = cell.MaxDistance(target).ToAngle();
         // The error has a peak near Pi/2 for edge distance, and another peak near
         // Pi for vertex distance.
         Assert2.Near(expected_to_boundary.Radians,
                      actual_to_boundary.Radians, 1e-12);
         Assert2.Near(expected_to_interior.Radians,
                      actual_to_interior.Radians, 1e-12);
         Assert2.Near(expected_max.Radians,
                      actual_max.Radians, 1e-12);
         if (expected_to_boundary.Radians <= Math.PI / 3)
         {
             Assert2.Near(expected_to_boundary.Radians,
                          actual_to_boundary.Radians, S2.DoubleError);
             Assert2.Near(expected_to_interior.Radians,
                          actual_to_interior.Radians, S2.DoubleError);
         }
         if (expected_max.Radians <= Math.PI / 3)
         {
             Assert2.Near(expected_max.Radians, actual_max.Radians, S2.DoubleError);
         }
     }
 }
    public void Test_S2ContainsPointQuery_GetContainingShapes()
    {
        // Also tests ShapeContains().
        int                 kNumVerticesPerLoop = 10;
        S1Angle             kMaxLoopRadius      = S2Testing.KmToAngle(10);
        S2Cap               center_cap          = new(S2Testing.RandomPoint(), kMaxLoopRadius);
        MutableS2ShapeIndex index = new();

        for (int i = 0; i < 100; ++i)
        {
            var loop = S2Loop.MakeRegularLoop(
                S2Testing.SamplePoint(center_cap),
                S2Testing.Random.RandDouble() * kMaxLoopRadius, kNumVerticesPerLoop);
            index.Add(new S2Loop.Shape(loop));
        }
        var query = index.MakeS2ContainsPointQuery();

        for (int i = 0; i < 100; ++i)
        {
            S2Point        p        = S2Testing.SamplePoint(center_cap);
            List <S2Shape> expected = new();
            foreach (var shape in index)
            {
                var loop = ((S2Loop.Shape)shape).Loop;
                if (loop.Contains(p))
                {
                    Assert.True(query.ShapeContains(shape, p));
                    expected.Add(shape);
                }
                else
                {
                    Assert.False(query.ShapeContains(shape, p));
                }
            }
            var actual = query.GetContainingShapes(p);
            Assert.Equal(expected, actual);
        }
    }
    public void Test_GetCrossingCandidates_CollinearEdgesOnCellBoundaries()
    {
        int kNumEdgeIntervals = 8;  // 9*8/2 = 36 edges

        for (int level = 0; level <= S2.kMaxCellLevel; ++level)
        {
            S2Cell  cell  = new(S2Testing.GetRandomCellId(level));
            int     j2    = S2Testing.Random.Uniform(4);
            S2Point p1    = cell.VertexRaw(j2);
            S2Point p2    = cell.VertexRaw(j2 + 1);
            S2Point delta = (p2 - p1) / kNumEdgeIntervals;
            List <(S2Point, S2Point)> edges = new();
            for (int i = 0; i <= kNumEdgeIntervals; ++i)
            {
                for (int j = 0; j < i; ++j)
                {
                    edges.Add(((p1 + i * delta).Normalize(),
                               (p1 + j * delta).Normalize()));
                }
            }
            TestAllCrossings(edges);
        }
    }
Exemple #15
0
    public void Test_VisitIntersectingShapes_Polylines()
    {
        MutableS2ShapeIndex index = new();
        S2Cap center_cap          = new(new S2Point(1, 0, 0), S1Angle.FromRadians(0.5));

        for (int i = 0; i < 50; ++i)
        {
            S2Point   center = S2Testing.SamplePoint(center_cap);
            S2Point[] vertices;
            if (S2Testing.Random.OneIn(10))
            {
                vertices = new[] { center, center };  // Try a few degenerate polylines.
            }
            else
            {
                vertices = S2Testing.MakeRegularPoints(
                    center, S1Angle.FromRadians(S2Testing.Random.RandDouble()),
                    S2Testing.Random.Uniform(20) + 3);
            }
            index.Add(new S2LaxPolylineShape(vertices));
        }
        new VisitIntersectingShapesTest(index).Run();
    }
 public void Test_EncodedS2PointVectorTest_ManyDuplicatePointsAtAllLevels()
 {
     // Test encoding 32 copies of the last S2CellId at each level.  This uses
     // between 27 and 38 bytes depending on the level.  (Note that the encoding
     // can use less than 1 byte per point in this situation.)
     for (int level = 0; level <= S2.kMaxCellLevel; ++level)
     {
         _logger.WriteLine("Level = " + level);
         S2CellId id = S2CellId.End(level).Prev();
         // Encoding: header (2 bytes), base ((level + 2) / 4 bytes), block count
         // (1 byte), block offsets (2 bytes), block headers (2 bytes), 32 deltas
         // (16 bytes).  At level 30 the encoding size goes up by 1 byte because
         // we can't encode an 8 byte "base" value, so instead this case uses a
         // base of 7 bytes plus a one-byte offset in each of the 2 blocks.
         int expected_size = 23 + (level + 2) / 4;
         if (level == 30)
         {
             expected_size += 1;
         }
         var points = new S2Point[32].Fill(id.ToPoint());
         TestEncodedS2PointVector(points, CodingHint.COMPACT, expected_size);
     }
 }
Exemple #17
0
    public void Test_S2_AngleMethods()
    {
        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.Angle(p000, pz, p045), S2.M_PI_4);
        Assert2.Near(S2.TurnAngle(p000, pz, p045), -3 * S2.M_PI_4);

        Assert2.Near(S2.Angle(p045, pz, p180), 3 * S2.M_PI_4);
        Assert2.Near(S2.TurnAngle(p045, pz, p180), -S2.M_PI_4);

        Assert2.Near(S2.Angle(p000, pz, p180), Math.PI);
        Assert2.Near(S2.TurnAngle(p000, pz, p180), 0);

        Assert2.Near(S2.Angle(pz, p000, p045), S2.M_PI_2);
        Assert2.Near(S2.TurnAngle(pz, p000, p045), S2.M_PI_2);

        Assert2.Near(S2.Angle(pz, p000, pz), 0);
        Assert2.Near(Math.Abs(S2.TurnAngle(pz, p000, pz)), Math.PI);
    }
Exemple #18
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);
    }
    public void Test_S2_FaceClipping()
    {
        // Start with a few simple cases.
        // An edge that is entirely contained within one cube face:
        TestFaceClippingEdgePair(new S2Point(1, -0.5, -0.5), new S2Point(1, 0.5, 0.5));
        // An edge that crosses one cube edge:
        TestFaceClippingEdgePair(new S2Point(1, 0, 0), new S2Point(0, 1, 0));
        // An edge that crosses two opposite edges of face 0:
        TestFaceClippingEdgePair(new S2Point(0.75, 0, -1), new S2Point(0.75, 0, 1));
        // An edge that crosses two adjacent edges of face 2:
        TestFaceClippingEdgePair(new S2Point(1, 0, 0.75), new S2Point(0, 1, 0.75));
        // An edge that crosses three cube edges (four faces):
        TestFaceClippingEdgePair(new S2Point(1, 0.9, 0.95), new S2Point(-1, 0.95, 0.9));

        // Comprehensively test edges that are difficult to handle, especially those
        // that nearly follow one of the 12 cube edges.
        R2Rect biunit = new(new R1Interval(-1, 1), new R1Interval(-1, 1));
        int    kIters = 1000; // Test passes with 1e6 iterations

        for (int iter = 0; iter < kIters; ++iter)
        {
            _logger.WriteLine($"Iteration {iter}");
            // Choose two adjacent cube corners P and Q.
            int     face = S2Testing.Random.Uniform(6);
            int     i    = S2Testing.Random.Uniform(4);
            int     j    = (i + 1) & 3;
            S2Point p    = S2.FaceUVtoXYZ(face, biunit.GetVertex(i));
            S2Point q    = S2.FaceUVtoXYZ(face, biunit.GetVertex(j));

            // Now choose two points that are nearly on the edge PQ, preferring points
            // that are near cube corners, face midpoints, or edge midpoints.
            S2Point a = PerturbedCornerOrMidpoint(p, q);
            S2Point b = PerturbedCornerOrMidpoint(p, q);
            TestFaceClipping(a, b);
        }
    }
        // 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));
            }
        }
Exemple #21
0
    // Returns true if the given shape contains the given point.  Most clients
    // should not use this method, since its running time is linear in the number
    // of shape edges.  Instead clients should create an S2ShapeIndex and use
    // S2ContainsPointQuery, since this strategy is much more efficient when many
    // points need to be tested.
    //
    // Polygon boundaries are treated as being semi-open (see S2ContainsPointQuery
    // and S2VertexModel for other options).
    //
    // CAVEAT: Typically this method is only used internally.  Its running time is
    //         linear in the number of shape edges.
    public static bool ContainsBruteForce(this S2Shape shape, S2Point point)
    {
        if (shape.Dimension() < 2)
        {
            return(false);
        }

        var ref_point = shape.GetReferencePoint();

        if (ref_point.Point == point)
        {
            return(ref_point.Contained);
        }

        S2CopyingEdgeCrosser crosser = new(ref_point.Point, point);
        bool inside = ref_point.Contained;

        for (int e = 0; e < shape.NumEdges(); ++e)
        {
            var edge = shape.GetEdge(e);
            inside ^= crosser.EdgeOrVertexCrossing(edge.V0, edge.V1);
        }
        return(inside);
    }
    public void Test_RectBounder_MaxLatitudeRandom()
    {
        // Check that the maximum latitude of edges is computed accurately to within
        // 3 * S2Constants.DoubleEpsilon (the expected maximum error).  We concentrate on maximum
        // latitudes near the equator and north pole since these are the extremes.

        const int kIters = 100;

        for (int iter = 0; iter < kIters; ++iter)
        {
            // Construct a right-handed coordinate frame (U,V,W) such that U points
            // slightly above the equator, V points at the equator, and W is slightly
            // offset from the north pole.
            S2Point u = S2Testing.RandomPoint();
            u = new S2Point(u.X, u.Y, S2.DoubleEpsilon * 1e-6 * Math.Pow(1e12, S2Testing.Random.RandDouble()));  // log is uniform
            u = u.Normalize();
            S2Point v = S2.RobustCrossProd(new S2Point(0, 0, 1), u).Normalize();
            S2Point w = S2.RobustCrossProd(u, v).Normalize();

            // Construct a line segment AB that passes through U, and check that the
            // maximum latitude of this segment matches the latitude of U.
            S2Point      a        = (u - S2Testing.Random.RandDouble() * v).Normalize();
            S2Point      b        = (u + S2Testing.Random.RandDouble() * v).Normalize();
            S2LatLngRect ab_bound = GetEdgeBound(a, b);
            Assert2.Near(S2LatLng.Latitude(u).Radians,
                         ab_bound.Lat.Hi, kRectError.LatRadians);

            // Construct a line segment CD that passes through W, and check that the
            // maximum latitude of this segment matches the latitude of W.
            S2Point      c        = (w - S2Testing.Random.RandDouble() * v).Normalize();
            S2Point      d        = (w + S2Testing.Random.RandDouble() * v).Normalize();
            S2LatLngRect cd_bound = GetEdgeBound(c, d);
            Assert2.Near(S2LatLng.Latitude(w).Radians,
                         cd_bound.Lat.Hi, kRectError.LatRadians);
        }
    }
Exemple #23
0
    public void Test_S2RegionTermIndexer_MarkerCharacter()
    {
        S2RegionTermIndexer.Options options = new();
        options.MinLevel = 20;
        options.MaxLevel = 20;

        S2RegionTermIndexer indexer = new(options);
        S2Point             point   = S2LatLng.FromDegrees(10, 20).ToPoint();

        Assert.Equal(indexer.Options_.MarkerCharacter, '$');
        Assert.Equal(indexer.GetQueryTerms(point, ""),
                     new List <string>()
        {
            "11282087039", "$11282087039"
        });

        indexer.Options_.MarkerCharacter = ':';
        Assert.Equal(indexer.Options_.MarkerCharacter, ':');
        Assert.Equal(indexer.GetQueryTerms(point, ""),
                     new List <string>()
        {
            "11282087039", ":11282087039"
        });
    }
Exemple #24
0
        private static S1ChordAngle GetDistanceToEdgeBruteForce(S2Cell cell, S2Point a, S2Point b)
        {
            if (cell.Contains(a) || cell.Contains(b))
            {
                return(S1ChordAngle.Zero);
            }

            S1ChordAngle min_dist = S1ChordAngle.Infinity;

            for (int i = 0; i < 4; ++i)
            {
                S2Point v0 = cell.Vertex(i);
                S2Point v1 = cell.Vertex(i + 1);
                // If the edge crosses through the cell, max distance is 0.
                if (S2.CrossingSign(a, b, v0, v1) >= 0)
                {
                    return(S1ChordAngle.Zero);
                }
                S2.UpdateMinDistance(a, v0, v1, ref min_dist);
                S2.UpdateMinDistance(b, v0, v1, ref min_dist);
                S2.UpdateMinDistance(v0, a, b, ref min_dist);
            }
            return(min_dist);
        }
Exemple #25
0
    // The default implementation of SearchPointPos(S2Point).
    public virtual (int pos, bool found) LocatePoint(S2Point target_point)
    {
        // Let I = cell_map_.lower_bound(T), where T is the leaf cell containing
        // "target_point".  Then if T is contained by an index cell, then the
        // containing cell is either I or I'.  We test for containment by comparing
        // the ranges of leaf cells spanned by T, I, and I'.

        var target = new S2CellId(target_point);

        var(pos, _) = SeekCell(target);
        var cell1 = GetCellId(pos);

        if (cell1 is not null && cell1.Value.RangeMin() <= target)
        {
            return(pos, true);
        }
        var cell2 = GetCellId(pos - 1);

        if (cell2 is not null && cell2.Value.RangeMax() >= target)
        {
            return(pos - 1, true);
        }
        return(pos - 1, false);
    }
 // Generate sub-edges of some given edge (a0,b0).  The length of the sub-edges
 // is distributed exponentially over a large range, and the endpoints may be
 // slightly perturbed to one side of (a0,b0) or the other.
 private static void GetPerturbedSubEdges(S2Point a0, S2Point b0, int count, List <(S2Point, S2Point)> edges)
        private void addChain(Chain chain,
                              S2Point x,
                              S2Point y,
                              S2Point z,
                              double maxPerturbation,
                              S2PolygonBuilder builder)
        {
            // Transform the given edge chain to the frame (x,y,z), perturb each vertex
            // up to the given distance, and add it to the builder.

            var vertices = new List<S2Point>();
            getVertices(chain.str, x, y, z, maxPerturbation, vertices);
            if (chain.closed)
            {
                vertices.Add(vertices[0]);
            }
            for (var i = 1; i < vertices.Count; ++i)
            {
                builder.AddEdge(vertices[i - 1], vertices[i]);
            }
        }
Exemple #28
0
 // Adds the given point to the index.  Invalidates all iterators.
 public void Add(S2Point point, Data data)
 {
     var id = new S2CellId(point);
     var tup = new TreeNode<Data>(id, point, data);
     map_.AddSorted(tup);
 }
        /**
   * Return a vector "c" that is orthogonal to the given unit-length vectors "a"
   * and "b". This function is similar to a.CrossProd(b) except that it does a
   * better job of ensuring orthogonality when "a" is nearly parallel to "b",
   * and it returns a non-zero result even when a == b or a == -b.
   *
   *  It satisfies the following properties (RCP == RobustCrossProd):
   *
   *  (1) RCP(a,b) != 0 for all a, b (2) RCP(b,a) == -RCP(a,b) unless a == b or
   * a == -b (3) RCP(-a,b) == -RCP(a,b) unless a == b or a == -b (4) RCP(a,-b)
   * == -RCP(a,b) unless a == b or a == -b
   */

        public static S2Point RobustCrossProd(S2Point a, S2Point b)
        {
            // The direction of a.CrossProd(b) becomes unstable as (a + b) or (a - b)
            // approaches zero. This leads to situations where a.CrossProd(b) is not
            // very orthogonal to "a" and/or "b". We could fix this using Gram-Schmidt,
            // but we also want b.RobustCrossProd(a) == -b.RobustCrossProd(a).
            //
            // The easiest fix is to just compute the cross product of (b+a) and (b-a).
            // Given that "a" and "b" are unit-length, this has good orthogonality to
            // "a" and "b" even if they differ only in the lowest bit of one component.

            // assert (isUnitLength(a) && isUnitLength(b));
            var x = S2Point.CrossProd(b + a, b - a);
            if (!x.Equals(new S2Point(0, 0, 0)))
            {
                return x;
            }

            // The only result that makes sense mathematically is to return zero, but
            // we find it more convenient to return an arbitrary orthogonal vector.
            return Ortho(a);
        }
 public static int XyzToFace(S2Point p)
 {
     var face = p.LargestAbsComponent;
     if (p[face] < 0)
     {
         face += 3;
     }
     return face;
 }
        /**
   * Returns the true centroid of the spherical triangle ABC multiplied by the
   * signed area of spherical triangle ABC. The reasons for multiplying by the
   * signed area are (1) this is the quantity that needs to be summed to compute
   * the centroid of a union or difference of triangles, and (2) it's actually
   * easier to calculate this way.
   */

        public static S2Point TrueCentroid(S2Point a, S2Point b, S2Point c)
        {
            // I couldn't find any references for computing the true centroid of a
            // spherical triangle... I have a truly marvellous demonstration of this
            // formula which this margin is too narrow to contain :)

            // assert (isUnitLength(a) && isUnitLength(b) && isUnitLength(c));
            var sina = S2Point.CrossProd(b, c).Norm;
            var sinb = S2Point.CrossProd(c, a).Norm;
            var sinc = S2Point.CrossProd(a, b).Norm;
            var ra = (sina == 0) ? 1 : (Math.Asin(sina)/sina);
            var rb = (sinb == 0) ? 1 : (Math.Asin(sinb)/sinb);
            var rc = (sinc == 0) ? 1 : (Math.Asin(sinc)/sinc);

            // Now compute a point M such that M.X = rX * det(ABC) / 2 for X in A,B,C.
            var x = new S2Point(a.X, b.X, c.X);
            var y = new S2Point(a.Y, b.Y, c.Y);
            var z = new S2Point(a.Z, b.Z, c.Z);
            var r = new S2Point(ra, rb, rc);
            return new S2Point(0.5*S2Point.CrossProd(y, z).DotProd(r),
                               0.5*S2Point.CrossProd(z, x).DotProd(r), 0.5*S2Point.CrossProd(x, y).DotProd(r));
        }
        // About centroids:
        // ----------------
        //
        // There are several notions of the "centroid" of a triangle. First, there
        // // is the planar centroid, which is simply the centroid of the ordinary
        // (non-spherical) triangle defined by the three vertices. Second, there is
        // the surface centroid, which is defined as the intersection of the three
        // medians of the spherical triangle. It is possible to show that this
        // point is simply the planar centroid projected to the surface of the
        // sphere. Finally, there is the true centroid (mass centroid), which is
        // defined as the area integral over the spherical triangle of (x,y,z)
        // divided by the triangle area. This is the point that the triangle would
        // rotate around if it was spinning in empty space.
        //
        // The best centroid for most purposes is the true centroid. Unlike the
        // planar and surface centroids, the true centroid behaves linearly as
        // regions are added or subtracted. That is, if you split a triangle into
        // pieces and compute the average of their centroids (weighted by triangle
        // area), the result equals the centroid of the original triangle. This is
        // not true of the other centroids.
        //
        // Also note that the surface centroid may be nowhere near the intuitive
        // "center" of a spherical triangle. For example, consider the triangle
        // with vertices A=(1,eps,0), B=(0,0,1), C=(-1,eps,0) (a quarter-sphere).
        // The surface centroid of this triangle is at S=(0, 2*eps, 1), which is
        // within a distance of 2*eps of the vertex B. Note that the median from A
        // (the segment connecting A to the midpoint of BC) passes through S, since
        // this is the shortest path connecting the two endpoints. On the other
        // hand, the true centroid is at M=(0, 0.5, 0.5), which when projected onto
        // the surface is a much more reasonable interpretation of the "center" of
        // this triangle.

        /**
   * Return the centroid of the planar triangle ABC. This can be normalized to
   * unit length to obtain the "surface centroid" of the corresponding spherical
   * triangle, i.e. the intersection of the three medians. However, note that
   * for large spherical triangles the surface centroid may be nowhere near the
   * intuitive "center" (see example above).
   */

        public static S2Point PlanarCentroid(S2Point a, S2Point b, S2Point c)
        {
            return new S2Point((a.X + b.X + c.X)/3.0, (a.Y + b.Y + c.Y)/3.0, (a.Z + b.Z + c.Z)/3.0);
        }
        /**
   * Like Area(), but returns a positive value for counterclockwise triangles
   * and a negative value otherwise.
   */

        public static double SignedArea(S2Point a, S2Point b, S2Point c)
        {
            return Area(a, b, c)*RobustCcw(a, b, c);
        }
        /**
   * Return the area of the triangle computed using Girard's formula. This is
   * slightly faster than the Area() method above is not accurate for very small
   * triangles.
   */

        public static double GirardArea(S2Point a, S2Point b, S2Point c)
        {
            // This is equivalent to the usual Girard's formula but is slightly
            // more accurate, faster to compute, and handles a == b == c without
            // a special case.

            var ab = S2Point.CrossProd(a, b);
            var bc = S2Point.CrossProd(b, c);
            var ac = S2Point.CrossProd(a, c);
            return Math.Max(0.0, ab.Angle(ac) - ab.Angle(bc) + bc.Angle(ac));
        }
        /**
   * Return the area of triangle ABC. The method used is about twice as
   * expensive as Girard's formula, but it is numerically stable for both large
   * and very small triangles. The points do not need to be normalized. The area
   * is always positive.
   *
   *  The triangle area is undefined if it contains two antipodal points, and
   * becomes numerically unstable as the length of any edge approaches 180
   * degrees.
   */

        internal static double Area(S2Point a, S2Point b, S2Point c)
        {
            // This method is based on l'Huilier's theorem,
            //
            // tan(E/4) = sqrt(tan(s/2) tan((s-a)/2) tan((s-b)/2) tan((s-c)/2))
            //
            // where E is the spherical excess of the triangle (i.e. its area),
            // a, b, c, are the side lengths, and
            // s is the semiperimeter (a + b + c) / 2 .
            //
            // The only significant source of error using l'Huilier's method is the
            // cancellation error of the terms (s-a), (s-b), (s-c). This leads to a
            // *relative* error of about 1e-16 * s / Min(s-a, s-b, s-c). This compares
            // to a relative error of about 1e-15 / E using Girard's formula, where E is
            // the true area of the triangle. Girard's formula can be even worse than
            // this for very small triangles, e.g. a triangle with a true area of 1e-30
            // might evaluate to 1e-5.
            //
            // So, we prefer l'Huilier's formula unless dmin < s * (0.1 * E), where
            // dmin = Min(s-a, s-b, s-c). This basically includes all triangles
            // except for extremely long and skinny ones.
            //
            // Since we don't know E, we would like a conservative upper bound on
            // the triangle area in terms of s and dmin. It's possible to show that
            // E <= k1 * s * sqrt(s * dmin), where k1 = 2*sqrt(3)/Pi (about 1).
            // Using this, it's easy to show that we should always use l'Huilier's
            // method if dmin >= k2 * s^5, where k2 is about 1e-2. Furthermore,
            // if dmin < k2 * s^5, the triangle area is at most k3 * s^4, where
            // k3 is about 0.1. Since the best case error using Girard's formula
            // is about 1e-15, this means that we shouldn't even consider it unless
            // s >= 3e-4 or so.

            // We use volatile doubles to force the compiler to truncate all of these
            // quantities to 64 bits. Otherwise it may compute a value of dmin > 0
            // simply because it chose to spill one of the intermediate values to
            // memory but not one of the others.
            var sa = b.Angle(c);
            var sb = c.Angle(a);
            var sc = a.Angle(b);
            var s = 0.5*(sa + sb + sc);
            if (s >= 3e-4)
            {
                // Consider whether Girard's formula might be more accurate.
                var s2 = s*s;
                var dmin = s - Math.Max(sa, Math.Max(sb, sc));
                if (dmin < 1e-2*s*s2*s2)
                {
                    // This triangle is skinny enough to consider Girard's formula.
                    var area = GirardArea(a, b, c);
                    if (dmin < s*(0.1*area))
                    {
                        return area;
                    }
                }
            }
            // Use l'Huilier's formula.
            return 4
                   *Math.Atan(
                       Math.Sqrt(
                           Math.Max(0.0,
                                    Math.Tan(0.5*s)*Math.Tan(0.5*(s - sa))*Math.Tan(0.5*(s - sb))
                                    *Math.Tan(0.5*(s - sc)))));
        }
        /**
   * Return a unit-length vector that is orthogonal to "a". Satisfies Ortho(-a)
   * = -Ortho(a) for all a.
   */

        public static S2Point Ortho(S2Point a)
        {
            // The current implementation in S2Point has the property we need,
            // i.e. Ortho(-a) = -Ortho(a) for all a.
            return a.Ortho;
        }
 public S2AreaCentroid(double area, S2Point? centroid = null)
 {
     this._area = area;
     this._centroid = centroid;
 }
Exemple #38
0
 // Converts a point on the sphere to a projected 2D point.
 public abstract R2Point Project(S2Point p);
        public void testAngleArea()
        {
            var pz = new S2Point(0, 0, 1);
            var p000 = new S2Point(1, 0, 0);
            var p045 = new S2Point(1, 1, 0);
            var p090 = new S2Point(0, 1, 0);
            var p180 = new S2Point(-1, 0, 0);
            assertDoubleNear(S2.Angle(p000, pz, p045), S2.PiOver4);
            assertDoubleNear(S2.Angle(p045, pz, p180), 3*S2.PiOver4);
            assertDoubleNear(S2.Angle(p000, pz, p180), S2.Pi);
            assertDoubleNear(S2.Angle(pz, p000, pz), 0);
            assertDoubleNear(S2.Angle(pz, p000, p045), S2.PiOver2);

            assertDoubleNear(S2.Area(p000, p090, pz), S2.PiOver2);
            assertDoubleNear(S2.Area(p045, pz, p180), 3*S2.PiOver4);

            // Make sure that area() has good *relative* accuracy even for
            // very small areas.
            var eps = 1e-10;
            var pepsx = new S2Point(eps, 0, 1);
            var pepsy = new S2Point(0, eps, 1);
            var expected1 = 0.5*eps*eps;
            assertDoubleNear(S2.Area(pepsx, pepsy, pz), expected1, 1e-14*expected1);

            // Make sure that it can handle degenerate triangles.
            var pr = new S2Point(0.257, -0.5723, 0.112);
            var pq = new S2Point(-0.747, 0.401, 0.2235);
            assertEquals(S2.Area(pr, pr, pr), 0.0);
            // TODO: The following test is not exact in optimized mode because the
            // compiler chooses to mix 64-bit and 80-bit intermediate results.
            assertDoubleNear(S2.Area(pr, pq, pr), 0);
            assertEquals(S2.Area(p000, p045, p090), 0.0);

            double maxGirard = 0;
            for (var i = 0; i < 10000; ++i)
            {
                var p0 = randomPoint();
                var d1 = randomPoint();
                var d2 = randomPoint();
                var p1 = p0 + (d1 * 1e-15);
                var p2 = p0 + (d2 * 1e-15);
                // 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.
                assertTrue(S2.Area(p0, p1, p2) < 0.7e-30);
                maxGirard = Math.Max(maxGirard, S2.GirardArea(p0, p1, p2));
            }
            Console.WriteLine("Worst case Girard for triangle area 1e-30: " + maxGirard);

            // Try a very long and skinny triangle.
            var p045eps = new S2Point(1, 1, eps);
            var expected2 = 5.8578643762690495119753e-11; // Mathematica.
            assertDoubleNear(S2.Area(p000, p045eps, p090), expected2, 1e-9*expected2);

            // Triangles with near-180 degree edges that sum to a quarter-sphere.
            var eps2 = 1e-10;
            var p000eps2 = new S2Point(1, 0.1*eps2, eps2);
            var quarterArea1 =
                S2.Area(p000eps2, p000, p090) + S2.Area(p000eps2, p090, p180) + S2.Area(p000eps2, p180, pz)
                + S2.Area(p000eps2, pz, p000);
            assertDoubleNear(quarterArea1, S2.Pi);

            // Four other triangles that sum to a quarter-sphere.
            var p045eps2 = new S2Point(1, 1, eps2);
            var quarterArea2 =
                S2.Area(p045eps2, p000, p090) + S2.Area(p045eps2, p090, p180) + S2.Area(p045eps2, p180, pz)
                + S2.Area(p045eps2, pz, p000);
            assertDoubleNear(quarterArea2, S2.Pi);
        }
 private void dumpCrossings(S2Loop loop)
 {
     Console.WriteLine("Ortho(v1): " + S2.Ortho(loop.Vertex(1)));
     Console.WriteLine("Contains(kOrigin): {0}\n", loop.Contains(S2.Origin));
     for (var i = 1; i <= loop.NumVertices; ++i)
     {
         var a = S2.Ortho(loop.Vertex(i));
         var b = loop.Vertex(i - 1);
         var c = loop.Vertex(i + 1);
         var o = loop.Vertex(i);
         Console.WriteLine("Vertex {0}: [%.17g, %.17g, %.17g], "
                           + "%d%dR=%d, %d%d%d=%d, R%d%d=%d, inside: %b\n",
                           i,
                           loop.Vertex(i).X,
                           loop.Vertex(i).Y,
                           loop.Vertex(i).Z,
                           i - 1,
                           i,
                           S2.RobustCcw(b, o, a),
                           i + 1,
                           i,
                           i - 1,
                           S2.RobustCcw(c, o, b),
                           i,
                           i + 1,
                           S2.RobustCcw(a, o, c),
                           S2.OrderedCcw(a, b, c, o));
     }
     for (var i = 0; i < loop.NumVertices + 2; ++i)
     {
         var orig = S2.Origin;
         S2Point dest;
         if (i < loop.NumVertices)
         {
             dest = loop.Vertex(i);
             Console.WriteLine("Origin->{0} crosses:", i);
         }
         else
         {
             dest = new S2Point(0, 0, 1);
             if (i == loop.NumVertices + 1)
             {
                 orig = loop.Vertex(1);
             }
             Console.WriteLine("Case {0}:", i);
         }
         for (var j = 0; j < loop.NumVertices; ++j)
         {
             Console.WriteLine(
                 " " + S2EdgeUtil.EdgeOrVertexCrossing(orig, dest, loop.Vertex(j), loop.Vertex(j + 1)));
         }
         Console.WriteLine();
     }
     for (var i = 0; i <= 2; i += 2)
     {
         Console.WriteLine("Origin->v1 crossing v{0}->v1: ", i);
         var a = S2.Ortho(loop.Vertex(1));
         var b = loop.Vertex(i);
         var c = S2.Origin;
         var o = loop.Vertex(1);
         Console.WriteLine("{0}1R={1}, M1{2}={3}, R1M={4}, crosses: {5}\n",
                           i,
                           S2.RobustCcw(b, o, a),
                           i,
                           S2.RobustCcw(c, o, b),
                           S2.RobustCcw(a, o, c),
                           S2EdgeUtil.EdgeOrVertexCrossing(c, o, b, a));
     }
 }
        /**
   * Return true if the points A, B, C are strictly counterclockwise. Return
   * false if the points are clockwise or colinear (i.e. if they are all
   * contained on some great circle).
   *
   *  Due to numerical errors, situations may arise that are mathematically
   * impossible, e.g. ABC may be considered strictly CCW while BCA is not.
   * However, the implementation guarantees the following:
   *
   *  If SimpleCCW(a,b,c), then !SimpleCCW(c,b,a) for all a,b,c.
   *
   * In other words, ABC and CBA are guaranteed not to be both CCW
   */

        public static bool SimpleCcw(S2Point a, S2Point b, S2Point c)
        {
            // We compute the signed volume of the parallelepiped ABC. The usual
            // formula for this is (AxB).C, but we compute it here using (CxA).B
            // in order to ensure that ABC and CBA are not both CCW. This follows
            // from the following identities (which are true numerically, not just
            // mathematically):
            //
            // (1) x.CrossProd(y) == -(y.CrossProd(x))
            // (2) (-x).DotProd(y) == -(x.DotProd(y))

            return S2Point.CrossProd(c, a).DotProd(b) > 0;
        }
        /**
   * Defines an area or a length cell metric.
   */

        /**
   * Return a unique "origin" on the sphere for operations that need a fixed
   * reference point. It should *not* be a point that is commonly used in edge
   * tests in order to avoid triggering code to handle degenerate cases. (This
   * rules out the north and south poles.)
   */


        /**
   * Return true if the given point is approximately unit length (this is mainly
   * useful for assertions).
   */

        public static bool IsUnitLength(S2Point p)
        {
            return Math.Abs(p.Norm2 - 1) <= 1e-15;
        }
Exemple #43
0
 public S2PointVector3(S2Point a, S2Point b, S2Point c) : this(
         a[0], a[1], a[2],
         b[0], b[1], b[2],
         c[0], c[1], c[2])
 {
 }
 public static R2Vector ValidFaceXyzToUv(int face, S2Point p)
 {
     // assert (p.dotProd(faceUvToXyz(face, 0, 0)) > 0);
     double pu;
     double pv;
     switch (face)
     {
         case 0:
             pu = p.Y/p.X;
             pv = p.Z/p.X;
             break;
         case 1:
             pu = -p.X/p.Y;
             pv = p.Z/p.Y;
             break;
         case 2:
             pu = -p.X/p.Z;
             pv = -p.Y/p.Z;
             break;
         case 3:
             pu = p.Z/p.X;
             pv = p.Y/p.X;
             break;
         case 4:
             pu = p.Z/p.Y;
             pv = -p.X/p.Y;
             break;
         default:
             pu = -p.Y/p.Z;
             pv = -p.X/p.Z;
             break;
     }
     return new R2Vector(pu, pv);
 }
Exemple #45
0
 // If the distance from X to the edge AB is less than "min_dist", this
 // method updates "min_dist" and returns true.  Otherwise it returns false.
 // The case A == B is handled correctly.
 //
 // Use this method when you want to compute many distances and keep track of
 // the minimum.  It is significantly faster than using GetDistance(),
 // because (1) using S1ChordAngle is much faster than S1Angle, and (2) it
 // can save a lot of work by not actually computing the distance when it is
 // obviously larger than the current minimum.
 public static bool UpdateMinDistance(S2Point x, S2Point a, S2Point b, ref S1ChordAngle min_dist)
 {
     return(AlwaysUpdateMinDistance(x, a, b, ref min_dist, false));
 }
        /**
   * A relatively expensive calculation invoked by RobustCCW() if the sign of
   * the determinant is uncertain.
   */

        private static int ExpensiveCcw(S2Point a, S2Point b, S2Point c)
        {
            // Return zero if and only if two points are the same. This ensures (1).
            if (a.Equals(b) || b.Equals(c) || c.Equals(a))
            {
                return 0;
            }

            // Now compute the determinant in a stable way. Since all three points are
            // unit length and we know that the determinant is very close to zero, this
            // means that points are very nearly colinear. Furthermore, the most common
            // situation is where two points are nearly identical or nearly antipodal.
            // To get the best accuracy in this situation, it is important to
            // immediately reduce the magnitude of the arguments by computing either
            // A+B or A-B for each pair of points. Note that even if A and B differ
            // only in their low bits, A-B can be computed very accurately. On the
            // other hand we can't accurately represent an arbitrary linear combination
            // of two vectors as would be required for Gaussian elimination. The code
            // below chooses the vertex opposite the longest edge as the "origin" for
            // the calculation, and computes the different vectors to the other two
            // vertices. This minimizes the sum of the lengths of these vectors.
            //
            // This implementation is very stable numerically, but it still does not
            // return consistent results in all cases. For example, if three points are
            // spaced far apart from each other along a great circle, the sign of the
            // result will basically be random (although it will still satisfy the
            // conditions documented in the header file). The only way to return
            // consistent results in all cases is to compute the result using
            // arbitrary-precision arithmetic. I considered using the Gnu MP library,
            // but this would be very expensive (up to 2000 bits of precision may be
            // needed to store the intermediate results) and seems like overkill for
            // this problem. The MP library is apparently also quite particular about
            // compilers and compilation options and would be a pain to maintain.

            // We want to handle the case of nearby points and nearly antipodal points
            // accurately, so determine whether A+B or A-B is smaller in each case.
            double sab = (a.DotProd(b) > 0) ? -1 : 1;
            double sbc = (b.DotProd(c) > 0) ? -1 : 1;
            double sca = (c.DotProd(a) > 0) ? -1 : 1;
            var vab = a + (b * sab);
            var vbc = b + (c * sbc);
            var vca = c + (a * sca);
            var dab = vab.Norm2;
            var dbc = vbc.Norm2;
            var dca = vca.Norm2;

            // Sort the difference vectors to find the longest edge, and use the
            // opposite vertex as the origin. If two difference vectors are the same
            // length, we break ties deterministically to ensure that the symmetry
            // properties guaranteed in the header file will be true.
            double sign;
            if (dca < dbc || (dca == dbc && a < b))
            {
                if (dab < dbc || (dab == dbc && a < c))
                {
                    // The "sab" factor converts A +/- B into B +/- A.
                    sign = S2Point.CrossProd(vab, vca).DotProd(a)*sab; // BC is longest
                    // edge
                }
                else
                {
                    sign = S2Point.CrossProd(vca, vbc).DotProd(c)*sca; // AB is longest
                    // edge
                }
            }
            else
            {
                if (dab < dca || (dab == dca && b < c))
                {
                    sign = S2Point.CrossProd(vbc, vab).DotProd(b)*sbc; // CA is longest
                    // edge
                }
                else
                {
                    sign = S2Point.CrossProd(vca, vbc).DotProd(c)*sca; // AB is longest
                    // edge
                }
            }
            if (sign > 0)
            {
                return 1;
            }
            if (sign < 0)
            {
                return -1;
            }

            // The points A, B, and C are numerically indistinguishable from coplanar.
            // This may be due to roundoff error, or the points may in fact be exactly
            // coplanar. We handle this situation by perturbing all of the points by a
            // vector (eps, eps**2, eps**3) where "eps" is an infinitesmally small
            // positive number (e.g. 1 divided by a googolplex). The perturbation is
            // done symbolically, i.e. we compute what would happen if the points were
            // perturbed by this amount. It turns out that this is equivalent to
            // checking whether the points are ordered CCW around the origin first in
            // the Y-Z plane, then in the Z-X plane, and then in the X-Y plane.

            var ccw =
                PlanarOrderedCcw(new R2Vector(a.Y, a.Z), new R2Vector(b.Y, b.Z), new R2Vector(c.Y, c.Z));
            if (ccw == 0)
            {
                ccw =
                    PlanarOrderedCcw(new R2Vector(a.Z, a.X), new R2Vector(b.Z, b.X), new R2Vector(c.Z, c.X));
                if (ccw == 0)
                {
                    ccw = PlanarOrderedCcw(
                        new R2Vector(a.X, a.Y), new R2Vector(b.X, b.Y), new R2Vector(c.X, c.Y));
                    // assert (ccw != 0);
                }
            }
            return ccw;
        }
Exemple #47
0
 public override R2Point Project(S2Point p)
 {
     return(FromLatLng(new S2LatLng(p)));
 }
        /**
   * Return true if the edges OA, OB, and OC are encountered in that order while
   * sweeping CCW around the point O. You can think of this as testing whether
   * A <= B <= C with respect to a continuous CCW ordering around O.
   *
   * Properties:
   * <ol>
   *   <li>If orderedCCW(a,b,c,o) && orderedCCW(b,a,c,o), then a == b</li>
   *   <li>If orderedCCW(a,b,c,o) && orderedCCW(a,c,b,o), then b == c</li>
   *   <li>If orderedCCW(a,b,c,o) && orderedCCW(c,b,a,o), then a == b == c</li>
   *   <li>If a == b or b == c, then orderedCCW(a,b,c,o) is true</li>
   *   <li>Otherwise if a == c, then orderedCCW(a,b,c,o) is false</li>
   * </ol>
   */

        public static bool OrderedCcw(S2Point a, S2Point b, S2Point c, S2Point o)
        {
            // The last inequality below is ">" rather than ">=" so that we return true
            // if A == B or B == C, and otherwise false if A == C. Recall that
            // RobustCCW(x,y,z) == -RobustCCW(z,y,x) for all x,y,z.

            var sum = 0;
            if (RobustCcw(b, o, a) >= 0)
            {
                ++sum;
            }
            if (RobustCcw(c, o, b) >= 0)
            {
                ++sum;
            }
            if (RobustCcw(a, o, c) > 0)
            {
                ++sum;
            }
            return sum >= 2;
        }
        public void testRoundingError()
        {
            var a = new S2Point(-0.9190364081111774, 0.17231932652084575, 0.35451111445694833);
            var b = new S2Point(-0.92130667053206, 0.17274500072476123, 0.3483578383756171);
            var c = new S2Point(-0.9257244057938284, 0.17357332608634282, 0.3360158106235289);
            var d = new S2Point(-0.9278712595449962, 0.17397586116468677, 0.32982923679138537);

            assertTrue(S2Loop.IsValidLoop(new List<S2Point>(new[] {a, b, c, d})));
        }
        /**
   * Return the angle at the vertex B in the triangle ABC. The return value is
   * always in the range [0, Pi]. The points do not need to be normalized.
   * Ensures that Angle(a,b,c) == Angle(c,b,a) for all a,b,c.
   *
   *  The angle is undefined if A or C is diametrically opposite from B, and
   * becomes numerically unstable as the length of edge AB or BC approaches 180
   * degrees.
   */

        public static double Angle(S2Point a, S2Point b, S2Point c)
        {
            return S2Point.CrossProd(a, b).Angle(S2Point.CrossProd(c, b));
        }
Exemple #51
0
 // Convenience function for the case when Data is an empty class.
 public void Remove(S2Point point)
 {
     //System.Diagnostics.Debug.Assert(typeof(Data) == void); // Data must be empty
     Remove(point, default);
 }
        /**
   * Return the exterior angle at the vertex B in the triangle ABC. The return
   * value is positive if ABC is counterclockwise and negative otherwise. If you
   * imagine an ant walking from A to B to C, this is the angle that the ant
   * turns at vertex B (positive = left, negative = right). Ensures that
   * TurnAngle(a,b,c) == -TurnAngle(c,b,a) for all a,b,c.
   *
   * @param a
   * @param b
   * @param c
   * @return the exterior angle at the vertex B in the triangle ABC
   */

        public static double TurnAngle(S2Point a, S2Point b, S2Point c)
        {
            // This is a bit less efficient because we compute all 3 cross products, but
            // it ensures that turnAngle(a,b,c) == -turnAngle(c,b,a) for all a,b,c.
            var outAngle = S2Point.CrossProd(b, a).Angle(S2Point.CrossProd(c, b));
            return (RobustCcw(a, b, c) > 0) ? outAngle : -outAngle;
        }
        private void getVertices(String str,
                                 S2Point x,
                                 S2Point y,
                                 S2Point z,
                                 double maxPerturbation,
                                 List<S2Point> vertices)
        {
            // Parse the vertices, perturb them if desired, and transform them into the
            // given frame.
            var line = makePolyline(str);

            for (var i = 0; i < line.NumVertices; ++i)
            {
                var p = line.Vertex(i);
                // (p[0]*x + p[1]*y + p[2]*z).Normalize()
                var axis = S2Point.Normalize(((x * p.X) + (y * p.Y)) + (z * p.Z));
                var cap = S2Cap.FromAxisAngle(axis, S1Angle.FromRadians(maxPerturbation));
                vertices.Add(samplePoint(cap));
            }
        }
        /**
   * Return true if two points are within the given distance of each other
   * (mainly useful for testing).
   */

        public static bool ApproxEquals(S2Point a, S2Point b, double maxError)
        {
            return a.Angle(b) <= maxError;
        }
Exemple #55
0
 public FurthestPointTarget(S2Point point) : base(point)
 {
 }
 public static bool ApproxEquals(S2Point a, S2Point b)
 {
     return ApproxEquals(a, b, 1e-15);
 }
        /**
   * WARNING! This requires arbitrary precision arithmetic to be truly robust.
   * This means that for nearly colinear AB and AC, this function may return the
   * wrong answer.
   *
   * <p>
   * Like SimpleCCW(), but returns +1 if the points are counterclockwise and -1
   * if the points are clockwise. It satisfies the following conditions:
   *
   *  (1) RobustCCW(a,b,c) == 0 if and only if a == b, b == c, or c == a (2)
   * RobustCCW(b,c,a) == RobustCCW(a,b,c) for all a,b,c (3) RobustCCW(c,b,a)
   * ==-RobustCCW(a,b,c) for all a,b,c
   *
   *  In other words:
   *
   *  (1) The result is zero if and only if two points are the same. (2)
   * Rotating the order of the arguments does not affect the result. (3)
   * Exchanging any two arguments inverts the result.
   *
   *  This function is essentially like taking the sign of the determinant of
   * a,b,c, except that it has additional logic to make sure that the above
   * properties hold even when the three points are coplanar, and to deal with
   * the limitations of floating-point arithmetic.
   *
   *  Note: a, b and c are expected to be of unit length. Otherwise, the results
   * are undefined.
   */

        public static int RobustCcw(S2Point a, S2Point b, S2Point c)
        {
            return RobustCcw(a, b, c, S2Point.CrossProd(a, b));
        }
 public static R2Vector? FaceXyzToUv(int face, S2Point p)
 {
     if (face < 3)
     {
         if (p[face] <= 0)
         {
             return null;
         }
     }
     else
     {
         if (p[face - 3] >= 0)
         {
             return null;
         }
     }
     return ValidFaceXyzToUv(face, p);
 }
        public void Test_S2PolylineSimplifier_Precision()
        {
            // This is a rough upper bound on both the error in constructing the disc
            // locations (i.e., S2.InterpolateAtDistance, etc), and also on the
            // padding that S2PolylineSimplifier uses to ensure that its results are
            // conservative (i.e., the error calculated by GetSemiwidth).
            S1Angle kMaxError = S1Angle.FromRadians(25 * S2.DoubleEpsilon);

            // We repeatedly generate a random edge.  We then target several discs that
            // barely overlap the edge, and avoid several discs that barely miss the
            // edge.  About half the time, we choose one disc and make it slightly too
            // large or too small so that targeting fails.
            int kIters = 1000;  // Passes with 1 million iterations.

            for (int iter = 0; iter < kIters; ++iter)
            {
                S2Testing.Random.Reset(iter + 1);  // Easier to reproduce a specific case.
                S2Point src        = S2Testing.RandomPoint();
                var     simplifier = new S2PolylineSimplifier(src);
                S2Point dst        = S2.InterpolateAtDistance(
                    S1Angle.FromRadians(S2Testing.Random.RandDouble()),
                    src, S2Testing.RandomPoint());
                S2Point n = S2.RobustCrossProd(src, dst).Normalize();

                // If bad_disc >= 0, then we make targeting fail for that disc.
                int kNumDiscs = 5;
                int bad_disc  = S2Testing.Random.Uniform(2 * kNumDiscs) - kNumDiscs;
                for (int i = 0; i < kNumDiscs; ++i)
                {
                    // The center of the disc projects to a point that is the given fraction
                    // "f" along the edge (src, dst).  If f < 0, the center is located
                    // behind "src" (in order to test this case).
                    double  f       = S2Testing.Random.UniformDouble(-0.5, 1.0);
                    S2Point a       = ((1 - f) * src + f * dst).Normalize();
                    S1Angle r       = S1Angle.FromRadians(S2Testing.Random.RandDouble());
                    bool    on_left = S2Testing.Random.OneIn(2);
                    S2Point x       = S2.InterpolateAtDistance(r, a, on_left ? n : -n);
                    // If the disc is behind "src", adjust its radius so that it just
                    // touches "src" rather than just touching the line through (src, dst).
                    if (f < 0)
                    {
                        r = new S1Angle(src, x);
                    }
                    // We grow the radius slightly if we want to target the disc and shrink
                    // it otherwise, *unless* we want targeting to fail for this disc, in
                    // which case these actions are reversed.
                    bool avoid       = S2Testing.Random.OneIn(2);
                    bool grow_radius = (avoid == (i == bad_disc));
                    var  radius      = new S1ChordAngle(grow_radius ? r + kMaxError : r - kMaxError);
                    if (avoid)
                    {
                        simplifier.AvoidDisc(x, radius, on_left);
                    }
                    else
                    {
                        simplifier.TargetDisc(x, radius);
                    }
                }
                // The result is true iff all the discraints were satisfiable.
                Assert.Equal(bad_disc < 0, simplifier.Extend(dst));
            }
        }
        /**
   * A more efficient version of RobustCCW that allows the precomputed
   * cross-product of A and B to be specified.
   *
   *  Note: a, b and c are expected to be of unit length. Otherwise, the results
   * are undefined
   */

        public static int RobustCcw(S2Point a, S2Point b, S2Point c, S2Point aCrossB)
        {
            // assert (isUnitLength(a) && isUnitLength(b) && isUnitLength(c));

            // There are 14 multiplications and additions to compute the determinant
            // below. Since all three points are normalized, it is possible to show
            // that the average rounding error per operation does not exceed 2**-54,
            // the maximum rounding error for an operation whose result magnitude is in
            // the range [0.5,1). Therefore, if the absolute value of the determinant
            // is greater than 2*14*(2**-54), the determinant will have the same sign
            // even if the arguments are rotated (which produces a mathematically
            // equivalent result but with potentially different rounding errors).
            const double kMinAbsValue = 1.6e-15; // 2 * 14 * 2**-54

            var det = aCrossB.DotProd(c);

            // Double-check borderline cases in debug mode.
            // assert ((Math.Abs(det) < kMinAbsValue) || (Math.Abs(det) > 1000 * kMinAbsValue)
            //    || (det * expensiveCCW(a, b, c) > 0));

            if (det > kMinAbsValue)
            {
                return 1;
            }

            if (det < -kMinAbsValue)
            {
                return -1;
            }

            return ExpensiveCcw(a, b, c);
        }