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); }
// 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); }
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); }
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); } }
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); } }
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); }
// 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)); } }
// 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); } }
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" }); }
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); }
// 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]); } }
// 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; }
// 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; }
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); }
// 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; }
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)); }
// 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; }
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); }