public void Test_RectBounder_MaxLatitudeSimple() { // Check cases where the min/max latitude is attained at a vertex. var kCubeLat = Math.Asin(1 / Math.Sqrt(3)); // 35.26 degrees Assert.True(GetEdgeBound(1, 1, 1, 1, -1, -1).ApproxEquals( // NOLINT new S2LatLngRect(new R1Interval(-kCubeLat, kCubeLat), new S1Interval(-S2.M_PI_4, S2.M_PI_4)), kRectError)); Assert.True(GetEdgeBound(1, -1, 1, 1, 1, -1).ApproxEquals( // NOLINT new S2LatLngRect(new R1Interval(-kCubeLat, kCubeLat), new S1Interval(-S2.M_PI_4, S2.M_PI_4)), kRectError)); // Check cases where the min/max latitude occurs in the edge interior. // These tests expect the result to be pretty close to the middle of the // allowable error range (i.e., by adding 0.5 * kRectError). // Max latitude, CW edge Assert2.DoubleEqual(S2.M_PI_4 + 0.5 * kRectError.LatRadians, GetEdgeBound(1, 1, 1, 1, -1, 1).Lat.Hi); // Max latitude, CCW edge Assert2.DoubleEqual(S2.M_PI_4 + 0.5 * kRectError.LatRadians, GetEdgeBound(1, -1, 1, 1, 1, 1).Lat.Hi); // NOLINT // Min latitude, CW edge Assert2.DoubleEqual(-S2.M_PI_4 - 0.5 * kRectError.LatRadians, GetEdgeBound(1, -1, -1, -1, -1, -1).Lat.Lo); // NOLINT // Min latitude, CCW edge Assert2.DoubleEqual(-S2.M_PI_4 - 0.5 * kRectError.LatRadians, GetEdgeBound(-1, 1, -1, -1, -1, -1).Lat.Lo); // NOLINT // Check cases where the edge passes through one of the poles. Assert.Equal(S2.M_PI_2, GetEdgeBound(.3, .4, 1, -.3, -.4, 1).Lat.Hi); // NOLINT Assert.Equal(-S2.M_PI_2, GetEdgeBound(.3, .4, -1, -.3, -.4, -1).Lat.Lo); // NOLINT }
public void Test_S2LatLngRect_GetDirectHausdorffDistancePointToRect() { // The Hausdorff distance from a point to a rect should be the same as its // distance to the rect. S2LatLngRect a1 = PointRectFromDegrees(5, 8); S2LatLngRect a2 = PointRectFromDegrees(90, 10); // north pole S2LatLngRect b = RectFromDegrees(-85, -50, -80, 10); Assert2.DoubleEqual(a1.GetDirectedHausdorffDistance(b).Radians, a1.GetDistance(b).Radians); Assert2.DoubleEqual(a2.GetDirectedHausdorffDistance(b).Radians, a2.GetDistance(b).Radians); b = RectFromDegrees(4, -10, 80, 10); Assert2.DoubleEqual(a1.GetDirectedHausdorffDistance(b).Radians, a1.GetDistance(b).Radians); Assert2.DoubleEqual(a2.GetDirectedHausdorffDistance(b).Radians, a2.GetDistance(b).Radians); b = RectFromDegrees(70, 170, 80, -170); Assert2.DoubleEqual(a1.GetDirectedHausdorffDistance(b).Radians, a1.GetDistance(b).Radians); Assert2.DoubleEqual(a2.GetDirectedHausdorffDistance(b).Radians, a2.GetDistance(b).Radians); }
public void Test_S1ChordAngle_Arithmetic() { S1ChordAngle zero = S1ChordAngle.Zero; S1ChordAngle degree30 = S1ChordAngle.FromDegrees(30); //0.26794919243112264 S1ChordAngle degree60 = S1ChordAngle.FromDegrees(60); //0.99999999999999978 S1ChordAngle degree90 = S1ChordAngle.FromDegrees(90); //1.9999999999999996 //2.0000000000000004 S1ChordAngle degree120 = S1ChordAngle.FromDegrees(120); //2.9999999999999996 S1ChordAngle degree180 = S1ChordAngle.Straight; Assert.Equal(0, (zero + zero).Degrees()); Assert.Equal(0, (zero - zero).Degrees()); Assert.Equal(0, (degree60 - degree60).Degrees()); Assert.Equal(0, (degree180 - degree180).Degrees()); Assert.Equal(0, (zero - degree60).Degrees()); Assert.Equal(0, (degree30 - degree90).Degrees()); Assert2.DoubleEqual(60, (degree60 + zero).Degrees()); Assert2.DoubleEqual(60, (degree60 - zero).Degrees()); Assert2.DoubleEqual(60, (zero + degree60).Degrees()); Assert2.DoubleEqual(90, (degree30 + degree60).Degrees()); Assert2.DoubleEqual(90, (degree60 + degree30).Degrees()); Assert2.DoubleEqual(60, (degree90 - degree30).Degrees()); Assert2.DoubleEqual(30, (degree90 - degree60).Degrees(), 5); Assert.Equal(180, (degree180 + zero).Degrees()); Assert.Equal(180, (degree180 - zero).Degrees()); Assert.Equal(180, (degree90 + degree90).Degrees()); Assert.Equal(180, (degree120 + degree90).Degrees()); Assert.Equal(180, (degree120 + degree120).Degrees()); Assert.Equal(180, (degree30 + degree180).Degrees()); Assert.Equal(180, (degree180 + degree180).Degrees()); }
public void Test_S1Angle_Trigonometry() { // Spot check a few angles to ensure that the correct function is called. Assert2.DoubleEqual(1, S1Angle.FromDegrees(0).Cos()); Assert2.DoubleEqual(1, S1Angle.FromDegrees(90).Sin()); Assert2.DoubleEqual(1, S1Angle.FromDegrees(45).Tan()); }
public void Test_GetPerimeter_MoreThanTwoPi() { // Make sure that GetPerimeter doesn't use S1ChordAngle, which can only // represent distances up to 2*Pi. var loop = ParsePointsOrDie("0:0, 0:90, 0:180, 90:0, 0:-90").ToArray(); Assert2.DoubleEqual(5 * S2.M_PI_2, S2.GetPerimeter(loop).Radians); }
public void Test_S1ChordAngle_FromLength2() { Assert.Equal(0, S1ChordAngle.FromLength2(0).Degrees()); Assert2.DoubleEqual(60, S1ChordAngle.FromLength2(1).Degrees()); Assert2.DoubleEqual(90, S1ChordAngle.FromLength2(2).Degrees()); Assert.Equal(180, S1ChordAngle.FromLength2(4).Degrees()); Assert.Equal(180, S1ChordAngle.FromLength2(5).Degrees()); }
public void Test_S1Angle_NormalizeCorrectlyCanonicalizesAngles() { Assert2.DoubleEqual(0.0, S1Angle.FromDegrees(360.0).Normalize().GetDegrees()); Assert2.DoubleEqual(-90.0, S1Angle.FromDegrees(-90.0).Normalize().GetDegrees()); Assert2.DoubleEqual(180.0, S1Angle.FromDegrees(-180.0).Normalize().GetDegrees()); Assert2.DoubleEqual(180.0, S1Angle.FromDegrees(180.0).Normalize().GetDegrees()); Assert2.DoubleEqual(180.0, S1Angle.FromDegrees(540.0).Normalize().GetDegrees()); Assert2.DoubleEqual(90.0, S1Angle.FromDegrees(-270.0).Normalize().GetDegrees()); }
public void Test_LoopTestBase_GetCurvature() { Assert.Equal(-S2.M_2_PI, S2.GetCurvature(full_)); Assert.Equal(S2.M_2_PI, S2.GetCurvature(v_loop_)); CheckCurvatureInvariants(v_loop_); // This curvature should be computed exactly. Assert.Equal(0, S2.GetCurvature(north_hemi3_)); CheckCurvatureInvariants(north_hemi3_); Assert2.Near(0, S2.GetCurvature(west_hemi_), 1e-15); CheckCurvatureInvariants(west_hemi_); // We don't have an easy way to estimate the curvature of these loops, but // we can still check that the expected invariants hold. CheckCurvatureInvariants(candy_cane_); CheckCurvatureInvariants(three_leaf_clover_); Assert2.DoubleEqual(S2.M_2_PI, S2.GetCurvature(line_triangle_)); CheckCurvatureInvariants(line_triangle_); Assert2.DoubleEqual(S2.M_2_PI, S2.GetCurvature(skinny_chevron_)); CheckCurvatureInvariants(skinny_chevron_); // Build a narrow spiral loop starting at the north pole. This is designed // to test that the error in GetCurvature is linear in the number of // vertices even when the partial sum of the curvatures gets very large. // The spiral consists of two "arms" defining opposite sides of the loop. // This is a pathological loop that contains many long parallel edges. int kArmPoints = 10000; // Number of vertices in each "arm" double kArmRadius = 0.01; // Radius of spiral. S2PointLoopSpan spiral = new(2 * kArmPoints); spiral[kArmPoints] = new S2Point(0, 0, 1); for (int i = 0; i < kArmPoints; ++i) { double angle = (S2.M_2_PI / 3) * i; double x = Math.Cos(angle); double y = Math.Sin(angle); double r1 = i * kArmRadius / kArmPoints; double r2 = (i + 1.5) * kArmRadius / kArmPoints; spiral[kArmPoints - i - 1] = new S2Point(r1 * x, r1 * y, 1).Normalize(); spiral[kArmPoints + i] = new S2Point(r2 * x, r2 * y, 1).Normalize(); } // Check that GetCurvature() is consistent with GetArea() to within the // error bound of the former. We actually use a tiny fraction of the // worst-case error bound, since the worst case only happens when all the // roundoff errors happen in the same direction and this test is not // designed to achieve that. The error in GetArea() can be ignored for the // purposes of this test since it is generally much smaller. Assert2.Near( S2.M_2_PI - S2.GetArea(spiral), S2.GetCurvature(spiral), 0.01 * S2.GetCurvatureMaxError(spiral)); }
public void Test_S1ChordAngle_ToFromS1Angle() { Assert.Equal(0, new S1ChordAngle(S1Angle.Zero).Radians()); Assert.Equal(4, new S1ChordAngle(S1Angle.FromRadians(Math.PI)).Length2); Assert.Equal(Math.PI, new S1ChordAngle(S1Angle.FromRadians(Math.PI)).Radians()); Assert.Equal(S1Angle.Infinity, new S1ChordAngle(S1Angle.Infinity).ToAngle()); Assert.True(new S1ChordAngle(S1Angle.FromRadians(-1)).Radians() < 0); Assert2.DoubleEqual(1.0, new S1ChordAngle(S1Angle.FromRadians(1.0)).Radians()); }
public void Test_S1IntervalTestBase_GetLength() { Assert.Equal(quad12.GetLength(), Math.PI); Assert.Equal(0, pi.GetLength()); Assert.Equal(0, mipi.GetLength()); Assert2.DoubleEqual(quad123.GetLength(), 1.5 * Math.PI); Assert.Equal(Math.Abs(quad23.GetLength()), Math.PI); Assert.Equal(full.GetLength(), S2.M_2_PI); Assert.True(empty.GetLength() < 0); }
public void Test_S1IntervalTestBase_GetCenter() { Assert.Equal(quad12.GetCenter(), S2.M_PI_2); Assert2.DoubleEqual(new S1Interval(3.1, 2.9).GetCenter(), 3.0 - Math.PI); Assert2.DoubleEqual(new S1Interval(-2.9, -3.1).GetCenter(), Math.PI - 3.0); Assert2.DoubleEqual(new S1Interval(2.1, -2.1).GetCenter(), Math.PI); Assert.Equal(pi.GetCenter(), Math.PI); Assert.Equal(mipi.GetCenter(), Math.PI); Assert.Equal(Math.Abs(quad23.GetCenter()), Math.PI); Assert2.DoubleEqual(quad123.GetCenter(), 0.75 * Math.PI); }
public void Test_S2LatLngRect_Accessors() { // Check various accessor methods. S2LatLngRect d1 = RectFromDegrees(-90, 0, -45, 180); Assert2.DoubleEqual(d1.LatLo().GetDegrees(), -90); Assert2.DoubleEqual(d1.LatHi().GetDegrees(), -45); Assert2.DoubleEqual(d1.LngLo().GetDegrees(), 0); Assert2.DoubleEqual(d1.LngHi().GetDegrees(), 180); Assert.Equal(d1.Lat, new(-S2.M_PI_2, -S2.M_PI_4)); Assert.Equal(d1.Lng, new(0, Math.PI)); }
public void Test_EdgeTrueCentroid_SemiEquator() { // Test the centroid of polyline ABC that follows the equator and consists // of two 90 degree edges (i.e., C = -A). The centroid (multiplied by // length) should point toward B and have a norm of 2.0. (The centroid // itself has a norm of 2/Pi, and the total edge length is Pi.) S2Point a = new(0, -1, 0), b = new(1, 0, 0), c = new(0, 1, 0); S2Point centroid = S2Centroid.TrueCentroid(a, b) + S2Centroid.TrueCentroid(b, c); Assert.True(S2.ApproxEquals(b, centroid.Normalize())); Assert2.DoubleEqual(2.0, centroid.Norm()); }
public void Test_S1ChordAngle_TwoPointConstructor() { for (int iter = 0; iter < 100; ++iter) { S2Testing.GetRandomFrame(out var x, out var y, out var z); Assert.Equal(S1Angle.Zero, new S1ChordAngle(z, z).ToAngle()); Assert2.Near(Math.PI, new S1ChordAngle(-z, z).Radians(), 1e-7); Assert2.DoubleEqual(S2.M_PI_2, new S1ChordAngle(x, z).Radians()); S2Point w = (y + z).Normalize(); Assert2.DoubleEqual(S2.M_PI_4, new S1ChordAngle(w, z).Radians()); } }
public void Test_S1Angle_E5E6E7Representations() { // Check that E5/E6/E7 representations work as expected. Assert2.DoubleEqual(S1Angle.FromDegrees(-45).Radians, S1Angle.FromE5(-4500000).Radians); Assert2.DoubleEqual(S1Angle.FromDegrees(-60).Radians, S1Angle.FromE6(-60000000).Radians); Assert2.DoubleEqual(S1Angle.FromDegrees(75).Radians, S1Angle.FromE7(750000000).Radians); Assert.Equal(-17256123, S1Angle.FromDegrees(-172.56123).E5()); Assert.Equal(12345678, S1Angle.FromDegrees(12.345678).E6()); Assert.Equal(-123456789, S1Angle.FromDegrees(-12.3456789).E7()); }
public void Test_S2Cell_TestFaces() { var edge_counts = new Dictionary <S2Point, int>(); var vertex_counts = new Dictionary <S2Point, int>(); for (int face = 0; face < 6; ++face) { S2CellId id = S2CellId.FromFace(face); S2Cell cell = new(id); Assert.Equal(id, cell.Id); Assert.Equal(face, cell.Face); Assert.Equal(0, cell.Level); // Top-level faces have alternating orientations to get RHS coordinates. Assert.Equal(face & S2.kSwapMask, cell.Orientation); Assert.False(cell.IsLeaf()); for (int k = 0; k < 4; ++k) { var key = cell.EdgeRaw(k); if (edge_counts.ContainsKey(key)) { edge_counts[key] += 1; } else { edge_counts[key] = 1; } var key2 = cell.VertexRaw(k); if (vertex_counts.ContainsKey(key2)) { vertex_counts[key2] += 1; } else { vertex_counts[key2] = 1; } Assert2.DoubleEqual(0.0, cell.VertexRaw(k).DotProd(key)); Assert2.DoubleEqual(0.0, cell.VertexRaw(k + 1).DotProd(key)); Assert2.DoubleEqual(1.0, cell.VertexRaw(k).CrossProd(cell.VertexRaw(k + 1)) .Normalize().DotProd(cell.Edge(k))); } } // Check that edges have multiplicity 2 and vertices have multiplicity 3. foreach (var p in edge_counts) { Assert.Equal(2, p.Value); } foreach (var p in vertex_counts) { Assert.Equal(3, p.Value); } }
public void Test_S2LatLng_TestBasic() { S2LatLng ll_rad = S2LatLng.FromRadians(S2.M_PI_4, S2.M_PI_2); Assert.Equal(S2.M_PI_4, ll_rad.LatRadians); Assert.Equal(S2.M_PI_2, ll_rad.LngRadians); Assert.True(ll_rad.IsValid()); S2LatLng ll_deg = S2LatLng.FromDegrees(45, 90); Assert.Equal(ll_rad, ll_deg); Assert.True(ll_deg.IsValid()); Assert.False(S2LatLng.FromDegrees(-91, 0).IsValid()); Assert.False(S2LatLng.FromDegrees(0, 181).IsValid()); S2LatLng bad = S2LatLng.FromDegrees(120, 200); Assert.False(bad.IsValid()); S2LatLng better = bad.Normalized(); Assert.True(better.IsValid()); Assert.Equal(S1Angle.FromDegrees(90), better.Lat()); Assert2.DoubleEqual(S1Angle.FromDegrees(-160).Radians, better.LngRadians); bad = S2LatLng.FromDegrees(-100, -360); Assert.False(bad.IsValid()); better = bad.Normalized(); Assert.True(better.IsValid()); Assert.Equal(S1Angle.FromDegrees(-90), better.Lat()); Assert2.DoubleEqual(0.0, better.LngRadians); Assert.True((S2LatLng.FromDegrees(10, 20) + S2LatLng.FromDegrees(20, 30)). ApproxEquals(S2LatLng.FromDegrees(30, 50))); Assert.True((S2LatLng.FromDegrees(10, 20) - S2LatLng.FromDegrees(20, 30)). ApproxEquals(S2LatLng.FromDegrees(-10, -10))); Assert.True((0.5 * S2LatLng.FromDegrees(10, 20)). ApproxEquals(S2LatLng.FromDegrees(5, 10))); // Check that Invalid() returns an invalid point. S2LatLng invalid = S2LatLng.Invalid; Assert.False(invalid.IsValid()); // Check that the default constructor sets latitude and longitude to 0. S2LatLng default_ll = S2LatLng.Center; Assert.True(default_ll.IsValid()); Assert.Equal(0, default_ll.LatRadians); Assert.Equal(0, default_ll.LngRadians); }
public void Test_S2LatLng_TestConversion() { // Test special cases: poles, "date line" Assert2.DoubleEqual(90.0, new S2LatLng(S2LatLng.FromDegrees(90.0, 65.0).ToPoint()).Lat().GetDegrees()); Assert.Equal(-S2.M_PI_2, new S2LatLng(S2LatLng.FromRadians(-S2.M_PI_2, 1).ToPoint()).LatRadians); Assert2.DoubleEqual(180.0, Math.Abs(new S2LatLng(S2LatLng.FromDegrees(12.2, 180.0).ToPoint()).Lng().GetDegrees())); Assert.Equal(Math.PI, Math.Abs(new S2LatLng(S2LatLng.FromRadians(0.1, -Math.PI).ToPoint()).LngRadians)); // Test a bunch of random points. for (int i = 0; i < 100000; ++i) { S2Point p = S2Testing.RandomPoint(); Assert.True(S2.ApproxEquals(p, new S2LatLng(p).ToPoint())); } }
public void Test_S1Angle_E6E7RepresentationsUnsigned() { // Check that unsigned E6/E7 representations work as expected. Assert2.DoubleEqual( S1Angle.FromDegrees(60).Radians, S1Angle.FromUnsignedE6((UInt32)60000000).Radians); Assert2.DoubleEqual( S1Angle.FromDegrees(-60).Radians, S1Angle.FromUnsignedE6(0xFFFFFFFFFC6C7900U).Radians); // (UInt32)-60000000 Assert2.DoubleEqual( S1Angle.FromDegrees(75).Radians, S1Angle.FromUnsignedE7((UInt32)750000000).Radians); Assert2.DoubleEqual( S1Angle.FromDegrees(-75).Radians, S1Angle.FromUnsignedE7(0xFFFFFFFFD34BE880U).Radians); // (UInt32)-750000000 }
public void Test_LoopTestBase_GetAreaAndCentroid() { Assert.Equal(S2.M_4_PI, S2.GetArea(full_)); Assert.Equal(S2Point.Empty, S2.GetCentroid(full_)); Assert2.DoubleEqual(S2.GetArea(north_hemi_), S2.M_2_PI); Assert2.Near(S2.M_2_PI, S2.GetArea(east_hemi_), 1e-12); // Construct spherical caps of random height, and approximate their boundary // with closely spaces vertices. Then check that the area and centroid are // correct. for (int iter = 0; iter < 50; ++iter) { // Choose a coordinate frame for the spherical cap. S2Testing.GetRandomFrame(out var x, out var y, out var z); // 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 Math.Abs(atan(tan(phi) / cos(dtheta/2)) - phi) <= kMaxDist. const double kMaxDist = 1e-6; double height = 2 * S2Testing.Random.RandDouble(); double phi = Math.Asin(1 - height); double max_dtheta = 2 * Math.Acos(Math.Tan(Math.Abs(phi)) / Math.Tan(Math.Abs(phi) + kMaxDist)); max_dtheta = Math.Min(Math.PI, max_dtheta); // At least 3 vertices. S2PointLoopSpan loop = new(); for (double theta = 0; theta < S2.M_2_PI; theta += S2Testing.Random.RandDouble() * max_dtheta) { loop.Add(Math.Cos(theta) * Math.Cos(phi) * x + Math.Sin(theta) * Math.Cos(phi) * y + Math.Sin(phi) * z); } double area = S2.GetArea(loop); S2Point centroid = S2.GetCentroid(loop); double expected_area = S2.M_2_PI * height; Assert.True(Math.Abs(area - expected_area) <= S2.M_2_PI * kMaxDist); S2Point expected_centroid = expected_area * (1 - 0.5 * height) * z; Assert.True((centroid - expected_centroid).Norm() <= 2 * kMaxDist); } }
public void Test_UVNorms() { // Check that GetUNorm and GetVNorm compute right-handed normals for // an edge in the increasing U or V direction. for (int face = 0; face < 6; ++face) { for (double x = -1; x <= 1; x += 1 / 1024.0) { Assert2.DoubleEqual(S2.FaceUVtoXYZ(face, x, -1) .CrossProd(S2.FaceUVtoXYZ(face, x, 1)) .Angle(S2.GetUNorm(face, x)), 0); Assert2.DoubleEqual(S2.FaceUVtoXYZ(face, -1, x) .CrossProd(S2.FaceUVtoXYZ(face, 1, x)) .Angle(S2.GetVNorm(face, x)), 0); } } }
public void Test_S2_Frames() { var z = new S2Point(0.2, 0.5, -3.3).Normalize(); var m = S2.GetFrame(z); Assert.True(S2.ApproxEquals(m.Col(2), z)); Assert.True(m.Col(0).IsUnitLength()); Assert.True(m.Col(1).IsUnitLength()); Assert2.DoubleEqual(m.Det(), 1); Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(0)), new(1, 0, 0))); Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(1)), new(0, 1, 0))); Assert.True(S2.ApproxEquals(S2.ToFrame(m, m.Col(2)), new(0, 0, 1))); Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(1, 0, 0)), m.Col(0))); Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(0, 1, 0)), m.Col(1))); Assert.True(S2.ApproxEquals(S2.FromFrame(m, new(0, 0, 1)), m.Col(2))); }
public void Test_S1Angle_ArithmeticOperationsOnAngles() { Assert2.DoubleEqual(0.3, S1Angle.FromRadians(-0.3).Abs()); Assert2.DoubleEqual(-0.1, (-S1Angle.FromRadians(0.1)).Radians); Assert2.DoubleEqual(0.4, (S1Angle.FromRadians(0.1) + S1Angle.FromRadians(0.3)).Radians); Assert2.DoubleEqual(-0.2, (S1Angle.FromRadians(0.1) - S1Angle.FromRadians(0.3)).Radians); Assert2.DoubleEqual(0.6, (2 * S1Angle.FromRadians(0.3)).Radians); Assert2.DoubleEqual(0.6, (S1Angle.FromRadians(0.3) * 2).Radians); Assert2.DoubleEqual(0.15, (S1Angle.FromRadians(0.3) / 2).Radians); Assert2.DoubleEqual(0.5, (S1Angle.FromRadians(0.3) / S1Angle.FromRadians(0.6))); S1Angle tmp = S1Angle.FromRadians(1.0); tmp += S1Angle.FromRadians(0.5); Assert2.DoubleEqual(1.5, tmp.Radians); tmp -= S1Angle.FromRadians(1.0); Assert2.DoubleEqual(0.5, tmp.Radians); tmp *= 5; Assert2.DoubleEqual(2.5, tmp.Radians); tmp /= 2; Assert2.DoubleEqual(1.25, tmp.Radians); }
public void Test_S1ChordAngle_Right() { Assert2.DoubleEqual(90, S1ChordAngle.Right.Degrees()); }
public void Test_S2Cap_Basic() { // Test basic properties of empty and full caps. S2Cap empty = S2Cap.Empty; S2Cap full = S2Cap.Full; Assert.True(empty.IsValid()); Assert.True(empty.IsEmpty()); Assert.True(empty.Complement().IsFull()); Assert.True(full.IsValid()); Assert.True(full.IsFull()); Assert.True(full.Complement().IsEmpty()); Assert.Equal(2, full.Height()); Assert2.DoubleEqual(180.0, full.Radius.Degrees()); // Test ==/!=. Assert.Equal(full, full); Assert.Equal(empty, empty); Assert.NotEqual(full, empty); // Test the S1Angle constructor using out-of-range arguments. Assert.True(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(-20)).IsEmpty()); Assert.True(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(5)).IsFull()); Assert.True(new S2Cap(new S2Point(1, 0, 0), S1Angle.Infinity).IsFull()); // Check that the default S2Cap is identical to Empty(). var default_empty = S2Cap.Empty; Assert.True(default_empty.IsValid()); Assert.True(default_empty.IsEmpty()); Assert.Equal(empty.Center, default_empty.Center); Assert.Equal(empty.Height(), default_empty.Height()); // Containment and intersection of empty and full caps. Assert.True(empty.Contains(empty)); Assert.True(full.Contains(empty)); Assert.True(full.Contains(full)); Assert.False(empty.InteriorIntersects(empty)); Assert.True(full.InteriorIntersects(full)); Assert.False(full.InteriorIntersects(empty)); // Singleton cap containing the x-axis. S2Cap xaxis = S2Cap.FromPoint(new S2Point(1, 0, 0)); Assert.True(xaxis.Contains(new S2Point(1, 0, 0))); Assert.False(xaxis.Contains(new S2Point(1, 1e-20, 0))); Assert.Equal(0, xaxis.Radius.Radians()); // Singleton cap containing the y-axis. S2Cap yaxis = S2Cap.FromPoint(new S2Point(0, 1, 0)); Assert.False(yaxis.Contains(xaxis.Center)); Assert.Equal(0, xaxis.Height()); // Check that the complement of a singleton cap is the full cap. S2Cap xcomp = xaxis.Complement(); Assert.True(xcomp.IsValid()); Assert.True(xcomp.IsFull()); Assert.True(xcomp.Contains(xaxis.Center)); // Check that the complement of the complement is *not* the original. Assert.True(xcomp.Complement().IsValid()); Assert.True(xcomp.Complement().IsEmpty()); Assert.False(xcomp.Complement().Contains(xaxis.Center)); // Check that very small caps can be represented accurately. // Here "kTinyRad" is small enough that unit vectors perturbed by this // amount along a tangent do not need to be renormalized. S2Cap tiny = new(new S2Point(1, 2, 3).Normalize(), S1Angle.FromRadians(kTinyRad)); var tangent = tiny.Center.CrossProd(new S2Point(3, 2, 1)).Normalize(); Assert.True(tiny.Contains(tiny.Center + 0.99 * kTinyRad * tangent)); Assert.False(tiny.Contains(tiny.Center + 1.01 * kTinyRad * tangent)); // Basic tests on a hemispherical cap. S2Cap hemi = S2Cap.FromCenterHeight(new S2Point(1, 0, 1).Normalize(), 1); Assert.Equal(-hemi.Center, hemi.Complement().Center); Assert.Equal(1, hemi.Complement().Height()); Assert.True(hemi.Contains(new S2Point(1, 0, 0))); Assert.False(hemi.Complement().Contains(new S2Point(1, 0, 0))); Assert.True(hemi.Contains(new S2Point(1, 0, -(1 - kEps)).Normalize())); Assert.False(hemi.InteriorContains(new S2Point(1, 0, -(1 + kEps)).Normalize())); // A concave cap. Note that the error bounds for point containment tests // increase with the cap angle, so we need to use a larger error bound // here. (It would be painful to do this everywhere, but this at least // gives an example of how to compute the maximum error.) S2Point center = GetLatLngPoint(80, 10); S1ChordAngle radius = new(S1Angle.FromDegrees(150)); double max_error = radius.GetS2PointConstructorMaxError() + radius.S1AngleConstructorMaxError + 3 * S2.DoubleEpsilon; // GetLatLngPoint() error S2Cap concave = new(center, radius); S2Cap concave_min = new(center, radius.PlusError(-max_error)); S2Cap concave_max = new(center, radius.PlusError(max_error)); Assert.True(concave_max.Contains(GetLatLngPoint(-70, 10))); Assert.False(concave_min.Contains(GetLatLngPoint(-70, 10))); Assert.True(concave_max.Contains(GetLatLngPoint(-50, -170))); Assert.False(concave_min.Contains(GetLatLngPoint(-50, -170))); // Cap containment tests. Assert.False(empty.Contains(xaxis)); Assert.False(empty.InteriorIntersects(xaxis)); Assert.True(full.Contains(xaxis)); Assert.True(full.InteriorIntersects(xaxis)); Assert.False(xaxis.Contains(full)); Assert.False(xaxis.InteriorIntersects(full)); Assert.True(xaxis.Contains(xaxis)); Assert.False(xaxis.InteriorIntersects(xaxis)); Assert.True(xaxis.Contains(empty)); Assert.False(xaxis.InteriorIntersects(empty)); Assert.True(hemi.Contains(tiny)); Assert.True(hemi.Contains(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.M_PI_4 - kEps)))); Assert.False(hemi.Contains(new S2Cap(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.M_PI_4 + kEps)))); Assert.True(concave.Contains(hemi)); Assert.True(concave.InteriorIntersects(hemi.Complement())); Assert.False(concave.Contains(S2Cap.FromCenterHeight(-concave.Center, 0.1))); }
public void Test_GetPerimeter_Octant() { var loop = ParsePointsOrDie("0:0, 0:90, 90:0"); Assert2.DoubleEqual(3 * S2.M_PI_2, S2.GetPerimeter(loop.ToArray()).Radians); }
public void Test_S1Angle_ConstructorsThatMeasureAngles() { Assert2.DoubleEqual(S2.M_PI_2, new S1Angle(new S2Point(1, 0, 0), new S2Point(0, 0, 2)).Radians); Assert2.DoubleEqual(0.0, new S1Angle(new S2Point(1, 0, 0), new S2Point(1, 0, 0)).Radians); Assert2.Near(50.0, new S1Angle(S2LatLng.FromDegrees(20, 20), S2LatLng.FromDegrees(70, 20)).GetDegrees(), 1e-13); }
public void Test_S2LatLngRect_Area() { Assert.Equal(0.0, S2LatLngRect.Empty.Area()); Assert2.DoubleEqual(S2.M_4_PI, S2LatLngRect.Full.Area()); Assert2.DoubleEqual(S2.M_PI_2, RectFromDegrees(0, 0, 90, 90).Area()); }
private void TestFaceClipping(S2Point a_raw, S2Point b_raw) { S2Point a = a_raw.Normalize(); S2Point b = b_raw.Normalize(); // First we test GetFaceSegments. FaceSegmentVector segments = new(); GetFaceSegments(a, b, segments); int n = segments.Count; Assert.True(n >= 1); var msg = new StringBuilder($"\nA={a_raw}\nB={b_raw}\nN={S2.RobustCrossProd(a, b)}\nSegments:\n"); int i1 = 0; foreach (var s in segments) { msg.AppendLine($"{i1++}: face={s.face}, a={s.a}, b={s.b}"); } _logger.WriteLine(msg.ToString()); R2Rect biunit = new(new R1Interval(-1, 1), new R1Interval(-1, 1)); var kErrorRadians = kFaceClipErrorRadians; // The first and last vertices should approximately equal A and B. Assert.True(a.Angle(S2.FaceUVtoXYZ(segments[0].face, segments[0].a)) <= kErrorRadians); Assert.True(b.Angle(S2.FaceUVtoXYZ(segments[n - 1].face, segments[n - 1].b)) <= kErrorRadians); S2Point norm = S2.RobustCrossProd(a, b).Normalize(); S2Point a_tangent = norm.CrossProd(a); S2Point b_tangent = b.CrossProd(norm); for (int i = 0; i < n; ++i) { // Vertices may not protrude outside the biunit square. Assert.True(biunit.Contains(segments[i].a)); Assert.True(biunit.Contains(segments[i].b)); if (i == 0) { continue; } // The two representations of each interior vertex (on adjacent faces) // must correspond to exactly the same S2Point. Assert.NotEqual(segments[i - 1].face, segments[i].face); Assert.Equal(S2.FaceUVtoXYZ(segments[i - 1].face, segments[i - 1].b), S2.FaceUVtoXYZ(segments[i].face, segments[i].a)); // Interior vertices should be in the plane containing A and B, and should // be contained in the wedge of angles between A and B (i.e., the dot // products with a_tangent and b_tangent should be non-negative). S2Point p = S2.FaceUVtoXYZ(segments[i].face, segments[i].a).Normalize(); Assert.True(Math.Abs(p.DotProd(norm)) <= kErrorRadians); Assert.True(p.DotProd(a_tangent) >= -kErrorRadians); Assert.True(p.DotProd(b_tangent) >= -kErrorRadians); } // Now we test ClipToPaddedFace (sometimes with a padding of zero). We do // this by defining an (x,y) coordinate system for the plane containing AB, // and converting points along the great circle AB to angles in the range // [-Pi, Pi]. We then accumulate the angle intervals spanned by each // clipped edge; the union over all 6 faces should approximately equal the // interval covered by the original edge. double padding = S2Testing.Random.OneIn(10) ? 0.0 : 1e-10 * Math.Pow(1e-5, S2Testing.Random.RandDouble()); S2Point x_axis = a, y_axis = a_tangent; S1Interval expected_angles = new(0, a.Angle(b)); S1Interval max_angles = expected_angles.Expanded(kErrorRadians); S1Interval actual_angles = new(); for (int face = 0; face < 6; ++face) { if (ClipToPaddedFace(a, b, face, padding, out var a_uv, out var b_uv)) { S2Point a_clip = S2.FaceUVtoXYZ(face, a_uv).Normalize(); S2Point b_clip = S2.FaceUVtoXYZ(face, b_uv).Normalize(); Assert.True(Math.Abs(a_clip.DotProd(norm)) <= kErrorRadians); Assert.True(Math.Abs(b_clip.DotProd(norm)) <= kErrorRadians); if (a_clip.Angle(a) > kErrorRadians) { Assert2.DoubleEqual(1 + padding, Math.Max(Math.Abs(a_uv[0]), Math.Abs(a_uv[1]))); } if (b_clip.Angle(b) > kErrorRadians) { Assert2.DoubleEqual(1 + padding, Math.Max(Math.Abs(b_uv[0]), Math.Abs(b_uv[1]))); } double a_angle = Math.Atan2(a_clip.DotProd(y_axis), a_clip.DotProd(x_axis)); double b_angle = Math.Atan2(b_clip.DotProd(y_axis), b_clip.DotProd(x_axis)); // Rounding errors may cause b_angle to be slightly less than a_angle. // We handle this by constructing the interval with FromPointPair(), // which is okay since the interval length is much less than Math.PI. S1Interval face_angles = S1Interval.FromPointPair(a_angle, b_angle); Assert.True(max_angles.Contains(face_angles)); actual_angles = actual_angles.Union(face_angles); } } Assert.True(actual_angles.Expanded(kErrorRadians).Contains(expected_angles)); }