public void Test_S2ClosestCellQuery_OptionsNotModified() { // Tests that FindClosestCell(), GetDistance(), and IsDistanceLess() do not // modify query.Options_, even though all of these methods have their own // specific options requirements. S2ClosestCellQuery.Options options = new(); options.MaxResults = (3); options.MaxDistance = (S1ChordAngle.FromDegrees(3)); options.MaxError = (S1ChordAngle.FromDegrees(0.001)); S2CellIndex index = new(); index.Add(new S2CellId(MakePointOrDie("1:1")), 1); index.Add(new S2CellId(MakePointOrDie("1:2")), 2); index.Add(new S2CellId(MakePointOrDie("1:3")), 3); index.Build(); S2ClosestCellQuery query = new(index, options); S2ClosestCellQuery.PointTarget target = new(MakePointOrDie("2:2")); Assert.Equal(2, query.FindClosestCell(target).Label); Assert2.Near(1.0, query.GetDistance(target).Degrees(), 1e-7); Assert.True(query.IsDistanceLess(target, S1ChordAngle.FromDegrees(1.5))); // Verify that none of the options above were modified. Assert.Equal(options.MaxResults, query.Options_.MaxResults); Assert.Equal(options.MaxDistance, query.Options_.MaxDistance); Assert.Equal(options.MaxError, query.Options_.MaxError); }
public void Test_GetApproxArea_LargeShellAndHolePolygon() { // Make sure that GetApproxArea works well for large polygons. Assert2.Near(S2.GetApproxArea(MakeLaxPolygonOrDie( "0:0, 0:90, 90:0; 0:22.5, 90:0, 0:67.5")), S2.M_PI_4, 1e-12); }
// Given a point X and an edge AB, check that the distance from X to AB is // "distance_radians" and the closest point on AB is "expected_closest". private static void CheckDistance(S2Point x, S2Point a, S2Point b, double distance_radians, S2Point expected_closest) { x = x.Normalize(); a = a.Normalize(); b = b.Normalize(); expected_closest = expected_closest.Normalize(); Assert2.Near(distance_radians, S2.GetDistance(x, a, b).Radians, S2.DoubleError); S2Point closest = S2.Project(x, a, b); Assert.True(S2Pred.CompareEdgeDistance( closest, a, b, new S1ChordAngle(S2.kProjectPerpendicularErrorS1Angle)) < 0); // If X is perpendicular to AB then there is nothing further we can expect. if (distance_radians != S2.M_PI_2) { if (expected_closest == new S2Point()) { // This special value says that the result should be A or B. Assert.True(closest == a || closest == b); } else { Assert.True(S2.ApproxEquals(closest, expected_closest)); } } S1ChordAngle min_distance = S1ChordAngle.Zero; Assert.False(S2.UpdateMinDistance(x, a, b, ref min_distance)); min_distance = S1ChordAngle.Infinity; Assert.True(S2.UpdateMinDistance(x, a, b, ref min_distance)); Assert2.Near(distance_radians, min_distance.ToAngle().Radians, S2.DoubleError); }
// This method verifies a.GetDistance(b) by comparing its result against a // brute-force implementation. The correctness of the brute-force version is // much easier to verify by inspection. private static void VerifyGetDistance(S2LatLngRect a, S2LatLngRect b) { S1Angle distance1 = BruteForceDistance(a, b); S1Angle distance2 = a.GetDistance(b); Assert2.Near(distance1.Radians - distance2.Radians, 0, 1e-10); }
// This method verifies a.GetDistance(b), where b is a S2LatLng, by comparing // its result against a.GetDistance(c), c being the point rectangle created // from b. private static void VerifyGetRectPointDistance(S2LatLngRect a, S2LatLng p) { S1Angle distance1 = BruteForceRectPointDistance(a, p.Normalized()); S1Angle distance2 = a.GetDistance(p.Normalized()); Assert2.Near(Math.Abs(distance1.Radians - distance2.Radians), 0, 1e-10); }
public void Test_S1ChordAngle_Trigonometry() { const int kIters = 20; for (int iter = 0; iter <= kIters; ++iter) { double radians = Math.PI * iter / kIters; S1ChordAngle angle = new(S1Angle.FromRadians(radians)); Assert2.Near(Math.Sin(radians), angle.Sin(), S2.DoubleError); Assert2.Near(Math.Cos(radians), angle.Cos(), S2.DoubleError); // Since the tan(x) is unbounded near Pi/4, we map the result back to an // angle before comparing. (The assertion is that the result is equal to // the tangent of a nearby angle.) Assert2.Near(Math.Atan(Math.Tan(radians)), Math.Atan(angle.Tan()), S2.DoubleError); } // Unlike S1Angle, S1ChordAngle can represent 90 and 180 degrees exactly. S1ChordAngle angle90 = S1ChordAngle.FromLength2(2); S1ChordAngle angle180 = S1ChordAngle.FromLength2(4); Assert.Equal(1, angle90.Sin()); Assert.Equal(0, angle90.Cos()); Assert.Equal(double.PositiveInfinity, angle90.Tan()); Assert.Equal(0, angle180.Sin()); Assert.Equal(-1, angle180.Cos()); Assert.Equal(0, angle180.Tan()); }
public void Test_LoopTestBase_GetAreaConsistentWithOrientation() { // Test that GetArea() returns an area near 0 for degenerate loops that // contain almost no points, and an area near 4*Pi for degenerate loops that // contain almost all points. const int kMaxVertices = 6; for (int i = 0; i < 50; ++i) { int num_vertices = 3 + S2Testing.Random.Uniform(kMaxVertices - 3 + 1); // Repeatedly choose N vertices that are exactly on the equator until we // find some that form a valid loop. S2PointLoopSpan loop = new(); do { for (int i2 = 0; i2 < num_vertices; ++i2) { // We limit longitude to the range [0, 90] to ensure that the loop is // degenerate (as opposed to following the entire equator). loop.Add( S2LatLng.FromRadians(0, S2Testing.Random.RandDouble() * S2.M_PI_2).ToPoint()); } } while (!new S2Loop(loop, S2Debug.DISABLE).IsValid()); bool ccw = S2.IsNormalized(loop); // The error bound is sufficient for current tests but not guaranteed. _ = i + ": " + loop.ToDebugString(); Assert2.Near(ccw ? 0 : S2.M_4_PI, S2.GetArea(loop), 1e-14); Assert.Equal(!ccw, new S2Loop(loop).Contains(new S2Point(0, 0, 1))); } }
public void Test_S2LatLng_TestDistance() { Assert.Equal(0.0, S2LatLng.FromDegrees(90, 0).GetDistance(S2LatLng.FromDegrees(90, 0)).Radians); Assert2.Near(77.0, S2LatLng.FromDegrees(-37, 25).GetDistance(S2LatLng.FromDegrees(-66, -155)).GetDegrees(), 1e-13); Assert2.Near(115.0, S2LatLng.FromDegrees(0, 165).GetDistance(S2LatLng.FromDegrees(0, -80)).GetDegrees(), 1e-13); Assert2.Near(180.0, S2LatLng.FromDegrees(47, -127).GetDistance(S2LatLng.FromDegrees(-47, 53)).GetDegrees(), 2e-6); }
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_S2Cap_Union() { // Two caps which have the same center but one has a larger radius. S2Cap a = new(GetLatLngPoint(50.0, 10.0), S1Angle.FromDegrees(0.2)); S2Cap b = new(GetLatLngPoint(50.0, 10.0), S1Angle.FromDegrees(0.3)); Assert.True(b.Contains(a)); Assert.Equal(b, a.Union(b)); // Two caps where one is the full cap. Assert.True(a.Union(S2Cap.Full).IsFull()); // Two caps where one is the empty cap. Assert.Equal(a, a.Union(S2Cap.Empty)); // Two caps which have different centers, one entirely encompasses the other. S2Cap c = new(GetLatLngPoint(51.0, 11.0), S1Angle.FromDegrees(1.5)); Assert.True(c.Contains(a)); Assert.Equal(a.Union(c).Center, c.Center); Assert.Equal(a.Union(c).Radius, c.Radius); // Two entirely disjoint caps. S2Cap d = new(GetLatLngPoint(51.0, 11.0), S1Angle.FromDegrees(0.1)); Assert.False(d.Contains(a)); Assert.False(d.Intersects(a)); Assert.True(a.Union(d).ApproxEquals(d.Union(a))); Assert2.Near(50.4588, new S2LatLng(a.Union(d).Center).Lat().GetDegrees(), 0.001); Assert2.Near(10.4525, new S2LatLng(a.Union(d).Center).Lng().GetDegrees(), 0.001); Assert2.Near(0.7425, a.Union(d).Radius.Degrees(), 0.001); // Two partially overlapping caps. S2Cap e = new(GetLatLngPoint(50.3, 10.3), S1Angle.FromDegrees(0.2)); Assert.False(e.Contains(a)); Assert.True(e.Intersects(a)); Assert.True(a.Union(e).ApproxEquals(e.Union(a))); Assert2.Near(50.1500, new S2LatLng(a.Union(e).Center).Lat().GetDegrees(), 0.001); Assert2.Near(10.1495, new S2LatLng(a.Union(e).Center).Lng().GetDegrees(), 0.001); Assert2.Near(0.3781, a.Union(e).Radius.Degrees(), 0.001); // Two very large caps, whose radius sums to in excess of 180 degrees, and // whose centers are not antipodal. S2Cap f = new(new S2Point(0, 0, 1).Normalize(), S1Angle.FromDegrees(150)); S2Cap g = new(new S2Point(0, 1, 0).Normalize(), S1Angle.FromDegrees(150)); Assert.True(f.Union(g).IsFull()); // Two non-overlapping hemisphere caps with antipodal centers. S2Cap hemi = S2Cap.FromCenterHeight(new S2Point(0, 0, 1).Normalize(), 1); Assert.True(hemi.Union(hemi.Complement()).IsFull()); }
public void Test_S2Cap_GetRectBound() { // Empty and full caps. Assert.True(S2Cap.Empty.GetRectBound().IsEmpty()); Assert.True(S2Cap.Full.GetRectBound().IsFull()); // Maximum allowable error for latitudes and longitudes measured in // degrees. (Assert2.Near isn't sufficient.) // Cap that includes the south pole. S2LatLngRect rect = new S2Cap(GetLatLngPoint(-45, 57), S1Angle.FromDegrees(50)).GetRectBound(); Assert2.Near(rect.LatLo().GetDegrees(), -90, kDegreeEps); Assert2.Near(rect.LatHi().GetDegrees(), 5, kDegreeEps); Assert.True(rect.Lng.IsFull()); // Cap that is tangent to the north pole. rect = new S2Cap(new S2Point(1, 0, 1).Normalize(), S1Angle.FromRadians(S2.M_PI_4 + 1e-16)).GetRectBound(); Assert2.Near(rect.Lat.Lo, 0, kEps); Assert2.Near(rect.Lat.Hi, S2.M_PI_2, kEps); Assert.True(rect.Lng.IsFull()); var p = new S2Point(1, 0, 1).Normalize(); var rb1 = S1Angle.FromDegrees(45 + 5e-15); rect = new S2Cap(p, rb1).GetRectBound(); Assert2.Near(rect.LatLo().GetDegrees(), 0, kDegreeEps); Assert2.Near(rect.LatHi().GetDegrees(), 90, kDegreeEps); Assert.True(rect.Lng.IsFull()); // The eastern hemisphere. var rb2 = S1Angle.FromRadians(S2.M_PI_2 + 2e-16); rect = new S2Cap(new S2Point(0, 1, 0), rb2).GetRectBound(); Assert2.Near(rect.LatLo().GetDegrees(), -90, kDegreeEps); Assert2.Near(rect.LatHi().GetDegrees(), 90, kDegreeEps); Assert.True(rect.Lng.IsFull()); // A cap centered on the equator. rect = new S2Cap(GetLatLngPoint(0, 50), S1Angle.FromDegrees(20)).GetRectBound(); Assert2.Near(rect.LatLo().GetDegrees(), -20, kDegreeEps); Assert2.Near(rect.LatHi().GetDegrees(), 20, kDegreeEps); Assert2.Near(rect.LngLo().GetDegrees(), 30, kDegreeEps); Assert2.Near(rect.LngHi().GetDegrees(), 70, kDegreeEps); // A cap centered on the north pole. rect = new S2Cap(GetLatLngPoint(90, 123), S1Angle.FromDegrees(10)).GetRectBound(); Assert2.Near(rect.LatLo().GetDegrees(), 80, kDegreeEps); Assert2.Near(rect.LatHi().GetDegrees(), 90, kDegreeEps); Assert.True(rect.Lng.IsFull()); }
public void Test_S2_GetPointToRightS1ChordAngle() { S2Point a = S2LatLng.FromDegrees(0, 0).ToPoint(); S2Point b = S2LatLng.FromDegrees(0, 5).ToPoint(); // east S1Angle kDistance = S2Testing.MetersToAngle(10); S2Point c = S2.GetPointToRight(a, b, new S1ChordAngle(kDistance)); Assert2.Near(new S1Angle(a, c).Radians, kDistance.Radians, 1e-15); // CAB must be a right angle with C to the right of AB. Assert2.Near(S2.TurnAngle(c, a, b), -S2.M_PI_2 /*radians*/, 1e-15); }
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()); } }
private static void CheckMaxDistance(S2Point x, S2Point a, S2Point b, double distance_radians) { x = x.Normalize(); a = a.Normalize(); b = b.Normalize(); S1ChordAngle max_distance = S1ChordAngle.Straight; Assert.False(S2.UpdateMaxDistance(x, a, b, ref max_distance)); max_distance = S1ChordAngle.Negative; Assert.True(S2.UpdateMaxDistance(x, a, b, ref max_distance)); Assert2.Near(distance_radians, max_distance.Radians(), S2.DoubleError); }
public void Test_S1IntervalTestBase_GetDirectedHausdorffDistance() { Assert2.Near(0.0, empty.GetDirectedHausdorffDistance(empty)); Assert2.Near(0.0, empty.GetDirectedHausdorffDistance(mid12)); Assert2.Near(Math.PI, mid12.GetDirectedHausdorffDistance(empty)); Assert.Equal(0.0, quad12.GetDirectedHausdorffDistance(quad123)); S1Interval in_ = new(3.0, -3.0); // an interval whose complement center is 0. Assert2.Near(3.0, new S1Interval(-0.1, 0.2).GetDirectedHausdorffDistance(in_)); Assert2.Near(3.0 - 0.1, new S1Interval(0.1, 0.2).GetDirectedHausdorffDistance(in_)); Assert2.Near(3.0 - 0.1, new S1Interval(-0.2, -0.1).GetDirectedHausdorffDistance(in_)); }
// Given two edges a0a1 and b0b1, check that the maximum distance between them // is "distance_radians". Parameters are passed by value so that this // function can normalize them. private static void CheckEdgePairMaxDistance(S2Point a0, S2Point a1, S2Point b0, S2Point b1, double distance_radians) { a0 = a0.Normalize(); a1 = a1.Normalize(); b0 = b0.Normalize(); b1 = b1.Normalize(); S1ChordAngle max_distance = S1ChordAngle.Straight; Assert.False(S2.UpdateEdgePairMaxDistance(a0, a1, b0, b1, ref max_distance)); max_distance = S1ChordAngle.Negative; Assert.True(S2.UpdateEdgePairMaxDistance(a0, a1, b0, b1, ref max_distance)); Assert2.Near(distance_radians, max_distance.Radians(), S2.DoubleError); }
public void Test_S2Cell_GetMaxDistanceToEdge() { // Test an edge for which its antipode crosses the cell. Validates both the // standard and brute force implementations for this case. S2Cell cell = S2Cell.FromFacePosLevel(0, 0, 20); S2Point a = -S2.Interpolate(2.0, cell.Center(), cell.Vertex(0)); S2Point b = -S2.Interpolate(2.0, cell.Center(), cell.Vertex(2)); S1ChordAngle actual = cell.MaxDistance(a, b); S1ChordAngle expected = GetMaxDistanceToEdgeBruteForce(cell, a, b); Assert2.Near(expected.Radians(), S1ChordAngle.Straight.Radians(), S2.DoubleError); Assert2.Near(actual.Radians(), S1ChordAngle.Straight.Radians(), S2.DoubleError); }
public void Test_S2ClosestEdgeQueryBase_MaxDistance() { var index = MakeIndexOrDie("0:0 | 1:0 | 2:0 | 3:0 # #"); FurthestEdgeQuery query = new(index); FurthestEdgeQuery.Options options = new(); options.MaxResults = (1); FurthestPointTarget target = new(MakePointOrDie("4:0")); var results = query.FindClosestEdges(target, options); Assert.Single(results); Assert.Equal(0, results[0].ShapeId); Assert.Equal(0, results[0].EdgeId); Assert2.Near(4, results[0].Distance.ToS1ChordAngle().ToAngle().GetDegrees(), 1e-13); }
public void Test_S2Cell_GetMaxDistanceToCell() { for (int i = 0; i < 1000; i++) { S2Cell cell = new(S2Testing.GetRandomCellId()); S2Cell test_cell = new(S2Testing.GetRandomCellId()); S2CellId antipodal_leaf_id = new(-test_cell.Center()); S2Cell antipodal_test_cell = new(antipodal_leaf_id.Parent(test_cell.Level)); S1ChordAngle dist_from_min = S1ChordAngle.Straight - cell.Distance(antipodal_test_cell); S1ChordAngle dist_from_max = cell.MaxDistance(test_cell); Assert2.Near(dist_from_min.Radians(), dist_from_max.Radians(), 1e-8); } }
public void Test_S1ChordAngle_ArithmeticPrecision() { // Verifies that S1ChordAngle is capable of adding and subtracting angles // extremely accurately up to Pi/2 radians. (Accuracy continues to be good // well beyond this value but degrades as angles approach Pi.) S1ChordAngle kEps = S1ChordAngle.FromRadians(1e-15); S1ChordAngle k90 = S1ChordAngle.Right; S1ChordAngle k90MinusEps = k90 - kEps; S1ChordAngle k90PlusEps = k90 + kEps; double kMaxError = 2 * S2.DoubleEpsilon; Assert2.Near(k90MinusEps.Radians(), S2.M_PI_2 - kEps.Radians(), kMaxError); Assert2.Near(k90PlusEps.Radians(), S2.M_PI_2 + kEps.Radians(), kMaxError); Assert2.Near((k90 - k90MinusEps).Radians(), kEps.Radians(), kMaxError); Assert2.Near((k90PlusEps - k90).Radians(), kEps.Radians(), kMaxError); Assert2.Near((k90MinusEps + kEps).Radians(), S2.M_PI_2, kMaxError); }
private void CheckMinMaxAvg( string label, int level, double count, double abs_error, double min_value, double max_value, double avg_value, S2.Metric min_metric, S2.Metric max_metric, S2.Metric avg_metric) { // All metrics are minimums, maximums, or averages of differential // quantities, and therefore will not be exact for cells at any finite // level. The differential minimum is always a lower bound, and the maximum // is always an upper bound, but these minimums and maximums may not be // achieved for two different reasons. First, the cells at each level are // sampled and we may miss the most extreme examples. Second, the actual // metric for a cell is obtained by integrating the differential quantity, // which is notant across the cell. Therefore cells at low levels // (bigger cells) have smaller variations. // // The "tolerance" below is an attempt to model both of these effects. // At low levels, error is dominated by the variation of differential // quantities across the cells, while at high levels error is dominated by // the effects of random sampling. double tolerance = (max_metric.GetValue(level) - min_metric.GetValue(level)) / Math.Sqrt(Math.Min(count, 0.5 * (1 << level))); if (tolerance == 0) { tolerance = abs_error; } double min_error = min_value - min_metric.GetValue(level); double max_error = max_metric.GetValue(level) - max_value; double avg_error = Math.Abs(avg_metric.GetValue(level) - avg_value); _logger.WriteLine("%-10s (%6.0f samples, tolerance %8.3g) - min %9.4g (%9.3g : %9.3g) " + "max %9.4g (%9.3g : %9.3g), avg %9.4g (%9.3g : %9.3g)", label, count, tolerance, min_value, min_error / min_value, min_error / tolerance, max_value, max_error / max_value, max_error / tolerance, avg_value, avg_error / avg_value, avg_error / tolerance); Assert.True(min_metric.GetValue(level) <= min_value + abs_error); Assert.True(min_metric.GetValue(level) >= min_value - tolerance); Assert.True(max_metric.GetValue(level) <= max_value + tolerance); Assert.True(max_metric.GetValue(level) >= max_value - abs_error); Assert2.Near(avg_metric.GetValue(level), avg_value, 10 * tolerance); }
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_S2_Interpolate() { // Choose test points designed to expose floating-point errors. S2Point p1 = new S2Point(0.1, 1e-30, 0.3).Normalize(); S2Point p2 = new S2Point(-0.7, -0.55, -1e30).Normalize(); // A zero-length edge. TestInterpolate(p1, p1, 0, p1); TestInterpolate(p1, p1, 1, p1); // Start, end, and middle of a medium-length edge. TestInterpolate(p1, p2, 0, p1); TestInterpolate(p1, p2, 1, p2); TestInterpolate(p1, p2, 0.5, 0.5 * (p1 + p2)); // Test that interpolation is done using distances on the sphere rather than // linear distances. TestInterpolate(new S2Point(1, 0, 0), new S2Point(0, 1, 0), 1.0 / 3, new S2Point(Math.Sqrt(3), 1, 0)); TestInterpolate(new S2Point(1, 0, 0), new S2Point(0, 1, 0), 2.0 / 3, new S2Point(1, Math.Sqrt(3), 0)); // Test that interpolation is accurate on a long edge (but not so long that // the definition of the edge itself becomes too unstable). { double kLng = Math.PI - 1e-2; S2Point a = S2LatLng.FromRadians(0, 0).ToPoint(); S2Point b = S2LatLng.FromRadians(0, kLng).ToPoint(); for (double f = 0.4; f > 1e-15; f *= 0.1) { TestInterpolate(a, b, f, S2LatLng.FromRadians(0, f * kLng).ToPoint()); TestInterpolate(a, b, 1 - f, S2LatLng.FromRadians(0, (1 - f) * kLng).ToPoint()); } } // Test that interpolation on a 180 degree edge (antipodal endpoints) yields // a result with the correct distance from each endpoint. for (double t = 0; t <= 1; t += 0.125) { S2Point actual = S2.Interpolate(p1, -p1, t); Assert2.Near(new S1Angle(actual, p1).Radians, t * Math.PI, 3e-15); } }
public void Test_S2LatLngRect_GetCentroid() { // Empty and full rectangles. Assert.Equal(new S2Point(), S2LatLngRect.Empty.Centroid()); Assert.True(S2LatLngRect.Full.Centroid().Norm() <= 1e-15); // Rectangles that cover the full longitude range. for (int i = 0; i < 100; ++i) { double lat1 = S2Testing.Random.UniformDouble(-S2.M_PI_2, S2.M_PI_2); double lat2 = S2Testing.Random.UniformDouble(-S2.M_PI_2, S2.M_PI_2); S2LatLngRect r = new(R1Interval.FromPointPair(lat1, lat2), S1Interval.Full); S2Point centroid = r.Centroid(); Assert2.Near(0.5 * (Math.Sin(lat1) + Math.Sin(lat2)) * r.Area(), centroid.Z, S2.DoubleError); Assert.True(new R2Point(centroid.X, centroid.Y).GetNorm() <= 1e-15); } // Rectangles that cover the full latitude range. for (int i = 0; i < 100; ++i) { double lng1 = S2Testing.Random.UniformDouble(-Math.PI, Math.PI); double lng2 = S2Testing.Random.UniformDouble(-Math.PI, Math.PI); S2LatLngRect r = new(S2LatLngRect.FullLat, S1Interval.FromPointPair(lng1, lng2)); S2Point centroid = r.Centroid(); Assert.True(Math.Abs(centroid.Z) <= 1e-15); Assert2.Near(r.Lng.GetCenter(), new S2LatLng(centroid).LngRadians, S2.DoubleError); double alpha = 0.5 * r.Lng.GetLength(); // TODO(Alas): the next Assert fails sometimes Assert2.Near(0.25 * Math.PI * Math.Sin(alpha) / alpha * r.Area(), new R2Point(centroid.X, centroid.Y).GetNorm(), S2.DoubleError); } // Finally, verify that when a rectangle is recursively split into pieces, // the centroids of the pieces add to give the centroid of their parent. // To make the code simpler we avoid rectangles that cross the 180 degree // line of longitude. TestCentroidSplitting( new S2LatLngRect(S2LatLngRect.FullLat, new S1Interval(-3.14, 3.14)), 10 /*splits_left*/); }
public void Test_S2ClosestPointQueryBase_MaxDistance() { S2PointIndex <int> index = new(); var points = ParsePointsOrDie("0:0, 1:0, 2:0, 3:0"); for (int i = 0; i < points.Count; ++i) { index.Add(points[i], i); } FurthestPointQuery <int> query = new(index); FurthestPointQuery <int> .Options options = new(); options.MaxResults = (1); FurthestPointTarget target = new(MakePointOrDie("4:0")); var results = query.FindClosestPoints(target, options); Assert.Single(results); Assert.Equal(points[0], results[0].Point); Assert.Equal(0, results[0].Data); Assert2.Near(4, results[0].Distance.ToS1ChordAngle().ToAngle().GetDegrees(), 1e-13); }
public void Test_S2ClosestEdgeQuery_OptionsNotModified() { // Tests that FindClosestEdge(), GetDistance(), and IsDistanceLess() do not // modify query.Options_, even though all of these methods have their own // specific options requirements. S2ClosestEdgeQuery.Options options = new(); options.MaxResults = (3); options.MaxDistance = (Distance.FromDegrees(3)); options.MaxError = (Distance.FromDegrees(0.001)); var index = MakeIndexOrDie("1:1 | 1:2 | 1:3 # #"); S2ClosestEdgeQuery query = new(index, options); S2ClosestEdgeQuery.PointTarget target = new(MakePointOrDie("2:2")); Assert.Equal(1, query.FindClosestEdge(target).EdgeId); Assert2.Near(1.0, query.GetDistance(target).Degrees(), S2.DoubleError); Assert.True(query.IsDistanceLess(target, Distance.FromDegrees(1.5))); // Verify that none of the options above were modified. Assert.Equal(options.MaxResults, query.Options_.MaxResults); Assert.Equal(options.MaxDistance, query.Options_.MaxDistance); Assert.Equal(options.MaxError, query.Options_.MaxError); }
public void Test_ST_UV_Conversions() { // Check boundary conditions. for (double s = 0; s <= 1; s += 0.5) { /*volatile*/ double u = S2.STtoUV(s); Assert.Equal(u, 2 * s - 1); } for (double u = -1; u <= 1; ++u) { /*volatile*/ double s = S2.UVtoST(u); Assert.Equal(s, 0.5 * (u + 1)); } // Check that UVtoST and STtoUV are inverses. for (double x = 0; x <= 1; x += 0.0001) { Assert2.Near(S2.UVtoST(S2.STtoUV(x)), x, S2.DoubleError); Assert2.Near(S2.STtoUV(S2.UVtoST(2 * x - 1)), 2 * x - 1, S2.DoubleError); } }
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_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); }
public void Test_S2CellId_Continuity() { // Make sure that sequentially increasing cell ids form a continuous // path over the surface of the sphere, i.e. there are no // discontinuous jumps from one region to another. double max_dist = S2.kMaxEdge.GetValue(kMaxWalkLevel); S2CellId end = S2CellId.End(kMaxWalkLevel); S2CellId id = S2CellId.Begin(kMaxWalkLevel); for (; id != end; id = id.Next()) { Assert.True(id.ToPointRaw().Angle(id.NextWrap().ToPointRaw()) <= max_dist); Assert.Equal(id.NextWrap(), id.AdvanceWrap(1)); Assert.Equal(id, id.NextWrap().AdvanceWrap(-1)); // Check that the ToPointRaw() returns the center of each cell // in (s,t) coordinates. S2.XYZtoFaceUV(id.ToPointRaw(), out var u, out var v); Assert2.Near(Math.IEEERemainder(S2.UVtoST(u), 0.5 * kCellSize), 0.0, S2.DoubleError); Assert2.Near(Math.IEEERemainder(S2.UVtoST(v), 0.5 * kCellSize), 0.0, S2.DoubleError); } }