private bool loopsEqual(S2Loop a, S2Loop b, double maxError) { // Return true if two loops have the same cyclic vertex sequence. if (a.NumVertices != b.NumVertices) { return(false); } for (var offset = 0; offset < a.NumVertices; ++offset) { if (S2.ApproxEquals(a.Vertex(offset), b.Vertex(0), maxError)) { var success = true; for (var i = 0; i < a.NumVertices; ++i) { if (!S2.ApproxEquals(a.Vertex(i + offset), b.Vertex(i), maxError)) { success = false; break; } } if (success) { return(true); } // Otherwise continue looping. There may be more than one candidate // starting offset since vertices are only matched approximately. } } return(false); }
private void assertPointApproximatelyEquals( S2Loop s2Loop, int vertexIndex, double lat, double lng, double error) { var latLng = new S2LatLng(s2Loop.Vertex(vertexIndex)); assertDoubleNear(latLng.LatDegrees, lat, error); assertDoubleNear(latLng.LngDegrees, lng, error); }
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 }))); }
private S2Loop rotate(S2Loop loop) { var vertices = new List<S2Point>(); for (var i = 1; i <= loop.NumVertices; ++i) { vertices.Add(loop.Vertex(i)); } return new S2Loop(vertices); }
public void Test_S2ConvexHullQuery_EmptyLoop() { S2ConvexHullQuery query = new(); S2Loop empty = (S2Loop.kEmpty); query.AddLoop(empty); var result = (query.GetConvexHull()); Assert.True(result.IsEmpty()); }
private S2Loop rotate(S2Loop loop) { var vertices = new List <S2Point>(); for (var i = 1; i <= loop.NumVertices; ++i) { vertices.Add(loop.Vertex(i)); } return(new S2Loop(vertices)); }
public void Test_S2ConvexHullQuery_FullLoop() { S2ConvexHullQuery query = new(); S2Loop full = (S2Loop.kFull); query.AddLoop(full); var result = (query.GetConvexHull()); Assert.True(result.IsFull()); }
private static bool LoopHasVertex(S2Loop loop, S2Point p) { for (int i = 0; i < loop.NumVertices; ++i) { if (loop.Vertex(i) == p) { return(true); } } return(false); }
private bool findLoop(S2Loop loop, List <S2Loop> candidates, double maxError) { for (var i = 0; i < candidates.Count; ++i) { if (loopsEqual(loop, candidates[i], maxError)) { return(true); } } return(false); }
public void Test_MakeLoop_ValidInput() { Assert.True(MakeLoop("-20:150, -20:151, -19:150", out var loop)); var expected = new S2Loop(new[] { S2LatLng.FromDegrees(-20, 150).ToPoint(), S2LatLng.FromDegrees(-20, 151).ToPoint(), S2LatLng.FromDegrees(-19, 150).ToPoint(), }); Assert.True(loop.BoundaryApproxEquals(expected)); }
public void Test_ContainsBruteForce_ConsistentWithS2Loop() { // Checks that ContainsBruteForce agrees with S2Loop.Contains(). var loop = S2Loop.MakeRegularLoop(MakePointOrDie("89:-179"), S1Angle.FromDegrees(10), 100); S2Loop.Shape shape = new(loop); for (int i = 0; i < loop.NumVertices; ++i) { Assert.Equal(loop.Contains(loop.Vertex(i)), shape.ContainsBruteForce(loop.Vertex(i))); } }
protected override void SetUp() { base.SetUp(); southHemi = new S2Loop(northHemi); southHemi.Invert(); eastHemi = new S2Loop(westHemi); eastHemi.Invert(); farHemi = new S2Loop(nearHemi); farHemi.Invert(); }
public void testContains() { assertTrue(candyCane.Contains(S2LatLng.FromDegrees(5, 71).ToPoint())); for (var i = 0; i < 4; ++i) { assertTrue(northHemi.Contains(new S2Point(0, 0, 1))); assertTrue(!northHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!southHemi.Contains(new S2Point(0, 0, 1))); assertTrue(southHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!westHemi.Contains(new S2Point(0, 1, 0))); assertTrue(westHemi.Contains(new S2Point(0, -1, 0))); assertTrue(eastHemi.Contains(new S2Point(0, 1, 0))); assertTrue(!eastHemi.Contains(new S2Point(0, -1, 0))); northHemi = rotate(northHemi); southHemi = rotate(southHemi); eastHemi = rotate(eastHemi); westHemi = rotate(westHemi); } // This code checks each cell vertex is contained by exactly one of // the adjacent cells. for (var level = 0; level < 3; ++level) { var loops = new List <S2Loop>(); var loopVertices = new List <S2Point>(); ISet <S2Point> points = new HashSet <S2Point>(); for (var id = S2CellId.Begin(level); !id.Equals(S2CellId.End(level)); id = id.Next) { var cell = new S2Cell(id); points.Add(cell.Center); for (var k = 0; k < 4; ++k) { loopVertices.Add(cell.GetVertex(k)); points.Add(cell.GetVertex(k)); } loops.Add(new S2Loop(loopVertices)); loopVertices.Clear(); } foreach (var point in points) { var count = 0; for (var j = 0; j < loops.Count; ++j) { if (loops[j].Contains(point)) { ++count; } } assertEquals(count, 1); } } }
private void assertRelation( S2Loop a, S2Loop b, int containsOrCrosses, bool intersects, bool nestable) { assertEquals(a.Contains(b), containsOrCrosses == 1); assertEquals(a.Intersects(b), intersects); if (nestable) { assertEquals(a.ContainsNested(b), a.Contains(b)); } if (containsOrCrosses >= -1) { assertEquals(a.ContainsOrCrosses(b), containsOrCrosses); } }
// Initializes an S2LaxLoopShape from the given S2Loop, by copying its data. // // REQUIRES: !loop.IsFull // [Use S2LaxPolygonShape if you need to represent a full loop.] public void Init(S2Loop loop) { System.Diagnostics.Debug.Assert(!loop.IsFull()); // Full loops not supported; use S2LaxPolygonShape if (loop.IsEmpty()) { NumVertices = 0; vertices_ = null; } else { NumVertices = loop.NumVertices; vertices_ = loop.CloneVertices(); } }
// Add a loop to the input geometry. public void AddLoop(S2Loop loop) { bound_ = bound_.Union(loop.GetRectBound()); if (loop.IsEmptyOrFull()) { // The empty and full loops consist of a single fake "vertex" that should // not be added to our point collection. return; } for (int i = 0; i < loop.NumVertices; ++i) { points_.Add(loop.Vertex(i)); } }
private static void CompareS2LoopToShape(S2Loop loop, S2Shape shape) { MutableS2ShapeIndex index = new(); index.Add(shape); S2Cap cap = loop.GetCapBound(); var query = index.MakeS2ContainsPointQuery(); for (int iter = 0; iter < 100; ++iter) { S2Point point = S2Testing.SamplePoint(cap); Assert.Equal(loop.Contains(point), query.ShapeContains(index.Shape(0), point)); } }
public void Test_S2ClosestEdgeQuery_EmptyTargetOptimized() { // Ensure that the optimized algorithm handles empty targets when a distance // limit is specified. MutableS2ShapeIndex index = new(); index.Add(new S2Polygon.OwningShape(new S2Polygon( S2Loop.MakeRegularLoop(new S2Point(1, 0, 0), S1Angle.FromRadians(0.1), 1000)))); S2ClosestEdgeQuery query = new(index); query.Options_.MaxDistance = new(S1Angle.FromRadians(1e-5)); MutableS2ShapeIndex target_index = new(); S2ClosestEdgeQuery.ShapeIndexTarget target = new(target_index); Assert.Empty(query.FindClosestEdges(target)); }
public static string ToDebugString(this S2Loop loop) { if (loop.IsEmpty()) { return("empty"); } else if (loop.IsFull()) { return("full"); } if (loop.NumVertices > 0) { return(AppendVertices(loop.CloneVertices(), loop.NumVertices)); } return(""); }
private static void TestNorthPoleLoop(S1Angle radius, int num_vertices) { // If the radius is very close to 90, then it's hard to predict whether the // result will be the full loop or not. Assert.True(Math.Abs(radius.Radians - S2.M_PI_2) >= S2.DoubleError); S2ConvexHullQuery query = new(); var loop = S2Loop.MakeRegularLoop(new S2Point(0, 0, 1), radius, num_vertices); query.AddLoop(loop); var result = query.GetConvexHull(); if (radius > S1Angle.FromRadians(S2.M_PI_2)) { Assert.True(result.IsFull()); } else { Assert.True(result.BoundaryEquals(loop)); } }
public void Test_S2PolygonLayer_DuplicateInputEdges() { // Check that S2PolygonLayer can assemble polygons even when there are // duplicate edges (after sibling pairs are removed), and then report the // duplicate edges as an error. S2Builder builder = new(new Options()); S2Polygon output = new(); S2PolygonLayer.Options options = new(); options.Validate = (true); builder.StartLayer(new S2PolygonLayer(output, options)); builder.AddPolyline(MakePolylineOrDie( "0:0, 0:2, 2:2, 1:1, 0:2, 2:2, 2:0, 0:0")); Assert.False(builder.Build(out var error)); Assert.Equal(S2ErrorCode.POLYGON_LOOPS_SHARE_EDGE, error.Code); Assert.Equal(2, output.NumLoops()); S2Loop loop0 = MakeLoopOrDie("0:0, 0:2, 2:2, 2:0"); S2Loop loop1 = (MakeLoopOrDie("0:2, 2:2, 1:1")); Assert.True(loop0 == output.Loop(0)); Assert.True(loop1 == output.Loop(1)); }
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); } }
// As above, but does not Debug.Assert-fail on invalid input. Returns true if // conversion is successful. public static bool MakeLoop(string str, out S2Loop?loop, S2Debug override_ = S2Debug.ALLOW) { if (str == "empty") { loop = S2Loop.kEmpty; return(true); } if (str == "full") { loop = S2Loop.kFull; return(true); } loop = null; var vertices = new List <S2Point>(); if (!ParsePoints(str, vertices)) { return(false); } loop = new S2Loop(vertices, override_); return(true); }
private bool findLoop(S2Loop loop, List<S2Loop> candidates, double maxError) { for (var i = 0; i < candidates.Count; ++i) { if (loopsEqual(loop, candidates[i], maxError)) { return true; } } return false; }
private bool loopsEqual(S2Loop a, S2Loop b, double maxError) { // Return true if two loops have the same cyclic vertex sequence. if (a.NumVertices != b.NumVertices) { return false; } for (var offset = 0; offset < a.NumVertices; ++offset) { if (S2.ApproxEquals(a.Vertex(offset), b.Vertex(0), maxError)) { var success = true; for (var i = 0; i < a.NumVertices; ++i) { if (!S2.ApproxEquals(a.Vertex(i + offset), b.Vertex(i), maxError)) { success = false; break; } } if (success) { return true; } // Otherwise continue looping. There may be more than one candidate // starting offset since vertices are only matched approximately. } } return false; }
public S2LaxClosedPolylineShape(S2Loop loop) : base(loop) { }
// Constructs an S2LaxLoopShape from the given S2Loop, by copying its data. public S2LaxLoopShape(S2Loop loop) { Init(loop); }
public void testContains() { assertTrue(candyCane.Contains(S2LatLng.FromDegrees(5, 71).ToPoint())); for (var i = 0; i < 4; ++i) { assertTrue(northHemi.Contains(new S2Point(0, 0, 1))); assertTrue(!northHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!southHemi.Contains(new S2Point(0, 0, 1))); assertTrue(southHemi.Contains(new S2Point(0, 0, -1))); assertTrue(!westHemi.Contains(new S2Point(0, 1, 0))); assertTrue(westHemi.Contains(new S2Point(0, -1, 0))); assertTrue(eastHemi.Contains(new S2Point(0, 1, 0))); assertTrue(!eastHemi.Contains(new S2Point(0, -1, 0))); northHemi = rotate(northHemi); southHemi = rotate(southHemi); eastHemi = rotate(eastHemi); westHemi = rotate(westHemi); } // This code checks each cell vertex is contained by exactly one of // the adjacent cells. for (var level = 0; level < 3; ++level) { var loops = new List<S2Loop>(); var loopVertices = new List<S2Point>(); ISet<S2Point> points = new HashSet<S2Point>(); for (var id = S2CellId.Begin(level); !id.Equals(S2CellId.End(level)); id = id.Next) { var cell = new S2Cell(id); points.Add(cell.Center); for (var k = 0; k < 4; ++k) { loopVertices.Add(cell.GetVertex(k)); points.Add(cell.GetVertex(k)); } loops.Add(new S2Loop(loopVertices)); loopVertices.Clear(); } foreach (var point in points) { var count = 0; for (var j = 0; j < loops.Count; ++j) { if (loops[j].Contains(point)) { ++count; } } assertEquals(count, 1); } } }
public void testAreaCentroid() { assertDoubleNear(northHemi.Area, 2*S2.Pi); assertDoubleNear(eastHemi.Area, 2*S2.Pi); // Construct spherical caps of random height, and approximate their boundary // with closely spaces vertices. Then check that the area and centroid are // correct. for (var i = 0; i < 100; ++i) { // Choose a coordinate frame for the spherical cap. var x = randomPoint(); var y = S2Point.Normalize(S2Point.CrossProd(x, randomPoint())); var z = S2Point.Normalize(S2Point.CrossProd(x, y)); // Given two points at latitude phi and whose longitudes differ by dtheta, // the geodesic between the two points has a maximum latitude of // atan(Tan(phi) / Cos(dtheta/2)). This can be derived by positioning // the two points at (-dtheta/2, phi) and (dtheta/2, phi). // // We want to position the vertices close enough together so that their // maximum distance from the boundary of the spherical cap is kMaxDist. // Thus we want fabs(atan(Tan(phi) / Cos(dtheta/2)) - phi) <= kMaxDist. var kMaxDist = 1e-6; var height = 2*rand.NextDouble(); var phi = Math.Asin(1 - height); var maxDtheta = 2*Math.Acos(Math.Tan(Math.Abs(phi))/Math.Tan(Math.Abs(phi) + kMaxDist)); maxDtheta = Math.Min(S2.Pi, maxDtheta); // At least 3 vertices. var vertices = new List<S2Point>(); for (double theta = 0; theta < 2*S2.Pi; theta += rand.NextDouble()*maxDtheta) { var xCosThetaCosPhi = x * (Math.Cos(theta)*Math.Cos(phi)); var ySinThetaCosPhi = y * (Math.Sin(theta)*Math.Cos(phi)); var zSinPhi = z * Math.Sin(phi); var sum = xCosThetaCosPhi + ySinThetaCosPhi + zSinPhi; vertices.Add(sum); } var loop = new S2Loop(vertices); var areaCentroid = loop.AreaAndCentroid; var area = loop.Area; var centroid = loop.Centroid; var expectedArea = 2*S2.Pi*height; assertTrue(areaCentroid.Area == area); assertTrue(centroid.Equals(areaCentroid.Centroid)); assertTrue(Math.Abs(area - expectedArea) <= 2*S2.Pi*kMaxDist); // high probability assertTrue(Math.Abs(area - expectedArea) >= 0.01*kMaxDist); var expectedCentroid = z*expectedArea*(1 - 0.5*height); assertTrue((centroid.Value - expectedCentroid).Norm <= 2*kMaxDist); } }
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)); } }
public void Test_UninitializedLoop() { var loop = new S2Loop(Array.Empty <S2Point>()); Assert.Equal("", loop.ToDebugString()); }
public void testAreaCentroid() { assertDoubleNear(northHemi.Area, 2 * S2.Pi); assertDoubleNear(eastHemi.Area, 2 * S2.Pi); // Construct spherical caps of random height, and approximate their boundary // with closely spaces vertices. Then check that the area and centroid are // correct. for (var i = 0; i < 100; ++i) { // Choose a coordinate frame for the spherical cap. var x = randomPoint(); var y = S2Point.Normalize(S2Point.CrossProd(x, randomPoint())); var z = S2Point.Normalize(S2Point.CrossProd(x, y)); // Given two points at latitude phi and whose longitudes differ by dtheta, // the geodesic between the two points has a maximum latitude of // atan(Tan(phi) / Cos(dtheta/2)). This can be derived by positioning // the two points at (-dtheta/2, phi) and (dtheta/2, phi). // // We want to position the vertices close enough together so that their // maximum distance from the boundary of the spherical cap is kMaxDist. // Thus we want fabs(atan(Tan(phi) / Cos(dtheta/2)) - phi) <= kMaxDist. var kMaxDist = 1e-6; var height = 2 * rand.NextDouble(); var phi = Math.Asin(1 - height); var maxDtheta = 2 * Math.Acos(Math.Tan(Math.Abs(phi)) / Math.Tan(Math.Abs(phi) + kMaxDist)); maxDtheta = Math.Min(S2.Pi, maxDtheta); // At least 3 vertices. var vertices = new List <S2Point>(); for (double theta = 0; theta < 2 * S2.Pi; theta += rand.NextDouble() * maxDtheta) { var xCosThetaCosPhi = x * (Math.Cos(theta) * Math.Cos(phi)); var ySinThetaCosPhi = y * (Math.Sin(theta) * Math.Cos(phi)); var zSinPhi = z * Math.Sin(phi); var sum = xCosThetaCosPhi + ySinThetaCosPhi + zSinPhi; vertices.Add(sum); } var loop = new S2Loop(vertices); var areaCentroid = loop.AreaAndCentroid; var area = loop.Area; var centroid = loop.Centroid; var expectedArea = 2 * S2.Pi * height; assertTrue(areaCentroid.Area == area); assertTrue(centroid.Equals(areaCentroid.Centroid)); assertTrue(Math.Abs(area - expectedArea) <= 2 * S2.Pi * kMaxDist); // high probability assertTrue(Math.Abs(area - expectedArea) >= 0.01 * kMaxDist); var expectedCentroid = z * expectedArea * (1 - 0.5 * height); assertTrue((centroid.Value - expectedCentroid).Norm <= 2 * kMaxDist); } }
public void AddEdges(S2Cap index_cap, int num_edges, MutableS2ShapeIndex index) { index.Add(new S2Loop.Shape(S2Loop.MakeRegularLoop( index_cap.Center, index_cap.RadiusAngle(), num_edges))); }