diff options
author | Hari Khalsa <hkhalsa@10gen.com> | 2012-10-24 17:40:42 -0400 |
---|---|---|
committer | Hari Khalsa <hkhalsa@10gen.com> | 2012-10-29 09:28:18 -0400 |
commit | 325b42be26c6dda8965f2d6b9b7b64b381f5bd61 (patch) | |
tree | db4db08805b0b1722e10cba723f5ea2dd9e88ca7 /src/third_party/s2/s2latlngrect_test.cc | |
parent | 1bd2fc12ef9de5b705dc1d127d02c397441c77e8 (diff) | |
download | mongo-325b42be26c6dda8965f2d6b9b7b64b381f5bd61.tar.gz |
SERVER-2874 Import S2 the flags library it depends on, add GeoJSON parsing
SERVER-2874 make the S2 stuff compile on mac/windows
Diffstat (limited to 'src/third_party/s2/s2latlngrect_test.cc')
-rw-r--r-- | src/third_party/s2/s2latlngrect_test.cc | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/src/third_party/s2/s2latlngrect_test.cc b/src/third_party/s2/s2latlngrect_test.cc new file mode 100644 index 00000000000..33408944a00 --- /dev/null +++ b/src/third_party/s2/s2latlngrect_test.cc @@ -0,0 +1,720 @@ +// Copyright 2005 Google Inc. All Rights Reserved. +// +// Most of the S2LatLngRect methods have trivial implementations that +// use the R1Interval and S1Interval classes, so most of the testing +// is done in those unit tests. + +#include "s2latlngrect.h" +#include "util/coding/coder.h" +#include "s2edgeutil.h" +#include "s2cap.h" +#include "s2cell.h" +#include "s2testing.h" +#include "testing/base/public/gunit.h" + +static S2LatLngRect RectFromDegrees(double lat_lo, double lng_lo, + double lat_hi, double lng_hi) { + // Convenience method to construct a rectangle. This method is + // intentionally *not* in the S2LatLngRect interface because the + // argument order is ambiguous, but hopefully it's not too confusing + // within the context of this unit test. + + return S2LatLngRect(S2LatLng::FromDegrees(lat_lo, lng_lo).Normalized(), + S2LatLng::FromDegrees(lat_hi, lng_hi).Normalized()); +} + +TEST(S2LatLngRect, EmptyAndFull) { + // Test basic properties of empty and full rectangles. + S2LatLngRect empty = S2LatLngRect::Empty(); + S2LatLngRect full = S2LatLngRect::Full(); + EXPECT_TRUE(empty.is_valid()); + EXPECT_TRUE(empty.is_empty()); + EXPECT_FALSE(empty.is_point()); + EXPECT_TRUE(full.is_valid()); + EXPECT_TRUE(full.is_full()); + EXPECT_FALSE(full.is_point()); + // Check that the default S2LatLngRect is identical to Empty(). + S2LatLngRect default_empty; + EXPECT_TRUE(default_empty.is_valid()); + EXPECT_TRUE(default_empty.is_empty()); + EXPECT_EQ(empty.lat().bounds(), default_empty.lat().bounds()); + EXPECT_EQ(empty.lng().bounds(), default_empty.lng().bounds()); +} + +TEST(S2LatLngRect, Accessors) { + // Check various accessor methods. + S2LatLngRect d1 = RectFromDegrees(-90, 0, -45, 180); + EXPECT_DOUBLE_EQ(d1.lat_lo().degrees(), -90); + EXPECT_DOUBLE_EQ(d1.lat_hi().degrees(), -45); + EXPECT_DOUBLE_EQ(d1.lng_lo().degrees(), 0); + EXPECT_DOUBLE_EQ(d1.lng_hi().degrees(), 180); + EXPECT_EQ(d1.lat(), R1Interval(-M_PI_2, -M_PI_4)); + EXPECT_EQ(d1.lng(), S1Interval(0, M_PI)); +} + +TEST(S2LatLngRect, FromCenterSize) { + EXPECT_TRUE(S2LatLngRect::FromCenterSize(S2LatLng::FromDegrees(80, 170), + S2LatLng::FromDegrees(40, 60)). + ApproxEquals(RectFromDegrees(60, 140, 90, -160))); + EXPECT_TRUE(S2LatLngRect::FromCenterSize(S2LatLng::FromDegrees(10, 40), + S2LatLng::FromDegrees(210, 400)). + is_full()); + EXPECT_TRUE(S2LatLngRect::FromCenterSize(S2LatLng::FromDegrees(-90, 180), + S2LatLng::FromDegrees(20, 50)). + ApproxEquals(RectFromDegrees(-90, 155, -80, -155))); +} + +TEST(S2LatLngRect, FromPoint) { + S2LatLng p = S2LatLng::FromDegrees(23, 47); + EXPECT_EQ(S2LatLngRect::FromPoint(p), S2LatLngRect(p, p)); + EXPECT_TRUE(S2LatLngRect::FromPoint(p).is_point()); +} + +TEST(S2LatLngRect, FromPointPair) { + EXPECT_EQ(S2LatLngRect::FromPointPair(S2LatLng::FromDegrees(-35, -140), + S2LatLng::FromDegrees(15, 155)), + RectFromDegrees(-35, 155, 15, -140)); + EXPECT_EQ(S2LatLngRect::FromPointPair(S2LatLng::FromDegrees(25, -70), + S2LatLng::FromDegrees(-90, 80)), + RectFromDegrees(-90, -70, 25, 80)); +} + +TEST(S2LatLngRect, GetCenterSize) { + S2LatLngRect r1(R1Interval(0, M_PI_2), S1Interval(-M_PI, 0)); + EXPECT_EQ(r1.GetCenter(), S2LatLng::FromRadians(M_PI_4, -M_PI_2)); + EXPECT_EQ(r1.GetSize(), S2LatLng::FromRadians(M_PI_2, M_PI)); + EXPECT_LT(S2LatLngRect::Empty().GetSize().lat().radians(), 0); + EXPECT_LT(S2LatLngRect::Empty().GetSize().lng().radians(), 0); +} + +TEST(S2LatLngRect, GetVertex) { + S2LatLngRect r1(R1Interval(0, M_PI_2), S1Interval(-M_PI, 0)); + EXPECT_EQ(r1.GetVertex(0), S2LatLng::FromRadians(0, M_PI)); + EXPECT_EQ(r1.GetVertex(1), S2LatLng::FromRadians(0, 0)); + EXPECT_EQ(r1.GetVertex(2), S2LatLng::FromRadians(M_PI_2, 0)); + EXPECT_EQ(r1.GetVertex(3), S2LatLng::FromRadians(M_PI_2, M_PI)); + + // Make sure that GetVertex() returns vertices in CCW order. + for (int i = 0; i < 4; ++i) { + double lat = M_PI_4 * (i - 2); + double lng = M_PI_2 * (i - 2) + 0.2; + S2LatLngRect r(R1Interval(lat, lat + M_PI_4), + S1Interval(drem(lng, 2*M_PI), drem(lng + M_PI_2, 2*M_PI))); + for (int k = 0; k < 4; ++k) { + EXPECT_TRUE(S2::SimpleCCW(r.GetVertex((k - 1) & 3).ToPoint(), + r.GetVertex(k).ToPoint(), + r.GetVertex((k + 1) & 3).ToPoint())); + } + } +} + +TEST(S2LatLngRect, Contains) { + // Contains(S2LatLng), InteriorContains(S2LatLng), VirtualContainsPoint() + S2LatLng eq_m180 = S2LatLng::FromRadians(0, -M_PI); + S2LatLng north_pole = S2LatLng::FromRadians(M_PI_2, 0); + S2LatLngRect r1(eq_m180, north_pole); + + EXPECT_TRUE(r1.Contains(S2LatLng::FromDegrees(30, -45))); + EXPECT_TRUE(r1.InteriorContains(S2LatLng::FromDegrees(30, -45))); + EXPECT_FALSE(r1.Contains(S2LatLng::FromDegrees(30, 45))); + EXPECT_FALSE(r1.InteriorContains(S2LatLng::FromDegrees(30, 45))); + EXPECT_TRUE(r1.Contains(eq_m180)); + EXPECT_FALSE(r1.InteriorContains(eq_m180)); + EXPECT_TRUE(r1.Contains(north_pole)); + EXPECT_FALSE(r1.InteriorContains(north_pole)); + EXPECT_TRUE(r1.Contains(S2Point(0.5, -0.3, 0.1))); + EXPECT_TRUE(r1.VirtualContainsPoint(S2Point(0.5, -0.3, 0.1))); + EXPECT_FALSE(r1.Contains(S2Point(0.5, 0.2, 0.1))); + EXPECT_FALSE(r1.VirtualContainsPoint(S2Point(0.5, 0.2, 0.1))); +} + +static void TestIntervalOps(S2LatLngRect const& x, S2LatLngRect const& y, + const char* expected_relation, + S2LatLngRect const& expected_union, + S2LatLngRect const& expected_intersection) { + // Test all of the interval operations on the given pair of intervals. + // "expected_relation" is a sequence of "T" and "F" characters corresponding + // to the expected results of Contains(), InteriorContains(), Intersects(), + // and InteriorIntersects() respectively. + + EXPECT_EQ(x.Contains(y), expected_relation[0] == 'T'); + EXPECT_EQ(x.InteriorContains(y), expected_relation[1] == 'T'); + EXPECT_EQ(x.Intersects(y), expected_relation[2] == 'T'); + EXPECT_EQ(x.InteriorIntersects(y), expected_relation[3] == 'T'); + + EXPECT_EQ(x.Contains(y), x.Union(y) == x); + EXPECT_EQ(x.Intersects(y), !x.Intersection(y).is_empty()); + + EXPECT_EQ(x.Union(y), expected_union); + EXPECT_EQ(x.Intersection(y), expected_intersection); + + if (y.GetSize() == S2LatLng::FromRadians(0, 0)) { + S2LatLngRect r = x; + r.AddPoint(y.lo()); + EXPECT_EQ(r, expected_union); + } +} + +TEST(S2LatLngRect, IntervalOps) { + // Contains(S2LatLngRect), InteriorContains(S2LatLngRect), + // Intersects(), InteriorIntersects(), Union(), Intersection(). + // + // Much more testing of these methods is done in s1interval_unittest + // and r1interval_unittest. + + // Rectangle "r1" covers one-quarter of the sphere. + S2LatLngRect r1 = RectFromDegrees(0, -180, 90, 0); + + // Test operations where one rectangle consists of a single point. + S2LatLngRect r1_mid = RectFromDegrees(45, -90, 45, -90); + TestIntervalOps(r1, r1_mid, "TTTT", r1, r1_mid); + + S2LatLngRect req_m180 = RectFromDegrees(0, -180, 0, -180); + TestIntervalOps(r1, req_m180, "TFTF", r1, req_m180); + + S2LatLngRect rnorth_pole = RectFromDegrees(90, 0, 90, 0); + TestIntervalOps(r1, rnorth_pole, "TFTF", r1, rnorth_pole); + + TestIntervalOps(r1, RectFromDegrees(-10, -1, 1, 20), "FFTT", + RectFromDegrees(-10, 180, 90, 20), + RectFromDegrees(0, -1, 1, 0)); + TestIntervalOps(r1, RectFromDegrees(-10, -1, 0, 20), "FFTF", + RectFromDegrees(-10, 180, 90, 20), + RectFromDegrees(0, -1, 0, 0)); + TestIntervalOps(r1, RectFromDegrees(-10, 0, 1, 20), "FFTF", + RectFromDegrees(-10, 180, 90, 20), + RectFromDegrees(0, 0, 1, 0)); + + TestIntervalOps(RectFromDegrees(-15, -160, -15, -150), + RectFromDegrees(20, 145, 25, 155), "FFFF", + RectFromDegrees(-15, 145, 25, -150), + S2LatLngRect::Empty()); + TestIntervalOps(RectFromDegrees(70, -10, 90, -140), + RectFromDegrees(60, 175, 80, 5), "FFTT", + RectFromDegrees(60, -180, 90, 180), + RectFromDegrees(70, 175, 80, 5)); + + // Check that the intersection of two rectangles that overlap in latitude + // but not longitude is valid, and vice versa. + TestIntervalOps(RectFromDegrees(12, 30, 60, 60), + RectFromDegrees(0, 0, 30, 18), "FFFF", + RectFromDegrees(0, 0, 60, 60), S2LatLngRect::Empty()); + TestIntervalOps(RectFromDegrees(0, 0, 18, 42), + RectFromDegrees(30, 12, 42, 60), "FFFF", + RectFromDegrees(0, 0, 42, 60), S2LatLngRect::Empty()); +} + +TEST(S2LatLngRect, AddPoint) { + S2LatLngRect p = S2LatLngRect::Empty(); + p.AddPoint(S2LatLng::FromDegrees(0, 0)); + EXPECT_TRUE(p.is_point()); + p.AddPoint(S2LatLng::FromRadians(0, -M_PI_2)); + EXPECT_FALSE(p.is_point()); + p.AddPoint(S2LatLng::FromRadians(M_PI_4, -M_PI)); + p.AddPoint(S2Point(0, 0, 1)); + EXPECT_EQ(p, RectFromDegrees(0, -180, 90, 0)); +} + +TEST(S2LatLngRect, Expanded) { + EXPECT_TRUE(RectFromDegrees(70, 150, 80, 170). + Expanded(S2LatLng::FromDegrees(20, 30)). + ApproxEquals(RectFromDegrees(50, 120, 90, -160))); + EXPECT_TRUE(S2LatLngRect::Empty().Expanded(S2LatLng::FromDegrees(20, 30)). + is_empty()); + EXPECT_TRUE(S2LatLngRect::Full().Expanded(S2LatLng::FromDegrees(20, 30)). + is_full()); + EXPECT_TRUE(RectFromDegrees(-90, 170, 10, 20). + Expanded(S2LatLng::FromDegrees(30, 80)). + ApproxEquals(RectFromDegrees(-90, -180, 40, 180))); +} + +TEST(S2LatLngRect, ConvolveWithCap) { + EXPECT_TRUE(RectFromDegrees(0, 170, 0, -170). + ConvolveWithCap(S1Angle::Degrees(15)).ApproxEquals( + RectFromDegrees(-15, 155, 15, -155))); + EXPECT_TRUE(RectFromDegrees(60, 150, 80, 10). + ConvolveWithCap(S1Angle::Degrees(15)).ApproxEquals( + RectFromDegrees(45, -180, 90, 180))); +} + +TEST(S2LatLngRect, GetCapBound) { + // Bounding cap at center is smaller: + EXPECT_TRUE(RectFromDegrees(-45, -45, 45, 45).GetCapBound(). + ApproxEquals(S2Cap::FromAxisHeight(S2Point(1, 0, 0), 0.5))); + + // Bounding cap at north pole is smaller: + EXPECT_TRUE(RectFromDegrees(88, -80, 89, 80).GetCapBound(). + ApproxEquals(S2Cap::FromAxisAngle(S2Point(0, 0, 1), + S1Angle::Degrees(2)))); + + // Longitude span > 180 degrees: + EXPECT_TRUE(RectFromDegrees(-30, -150, -10, 50).GetCapBound(). + ApproxEquals(S2Cap::FromAxisAngle(S2Point(0, 0, -1), + S1Angle::Degrees(80)))); +} + +static void TestCellOps(S2LatLngRect const& r, S2Cell const& cell, + int level) { + // Test the relationship between the given rectangle and cell: + // 0 == no intersection, 1 == MayIntersect, 2 == Intersects, + // 3 == Vertex Containment, 4 == Contains + + bool vertex_contained = false; + for (int i = 0; i < 4; ++i) { + if (r.Contains(cell.GetVertexRaw(i)) || + (!r.is_empty() && cell.Contains(r.GetVertex(i).ToPoint()))) + vertex_contained = true; + } + EXPECT_EQ(r.MayIntersect(cell), level >= 1); + EXPECT_EQ(r.Intersects(cell), level >= 2); + EXPECT_EQ(vertex_contained, level >= 3); + EXPECT_EQ(r.Contains(cell), level >= 4); +} + +TEST(S2LatLngRect, CellOps) { + // Contains(S2Cell), MayIntersect(S2Cell), Intersects(S2Cell) + + // Special cases. + TestCellOps(S2LatLngRect::Empty(), S2Cell::FromFacePosLevel(3, 0, 0), 0); + TestCellOps(S2LatLngRect::Full(), S2Cell::FromFacePosLevel(2, 0, 0), 4); + TestCellOps(S2LatLngRect::Full(), S2Cell::FromFacePosLevel(5, 0, 25), 4); + + // This rectangle includes the first quadrant of face 0. It's expanded + // slightly because cell bounding rectangles are slightly conservative. + S2LatLngRect r4 = RectFromDegrees(-45.1, -45.1, 0.1, 0.1); + TestCellOps(r4, S2Cell::FromFacePosLevel(0, 0, 0), 3); + TestCellOps(r4, S2Cell::FromFacePosLevel(0, 0, 1), 4); + TestCellOps(r4, S2Cell::FromFacePosLevel(1, 0, 1), 0); + + // This rectangle intersects the first quadrant of face 0. + S2LatLngRect r5 = RectFromDegrees(-10, -45, 10, 0); + TestCellOps(r5, S2Cell::FromFacePosLevel(0, 0, 0), 3); + TestCellOps(r5, S2Cell::FromFacePosLevel(0, 0, 1), 3); + TestCellOps(r5, S2Cell::FromFacePosLevel(1, 0, 1), 0); + + // Rectangle consisting of a single point. + TestCellOps(RectFromDegrees(4, 4, 4, 4), + S2Cell::FromFacePosLevel(0, 0, 0), 3); + + // Rectangles that intersect the bounding rectangle of a face + // but not the face itself. + TestCellOps(RectFromDegrees(41, -87, 42, -79), + S2Cell::FromFacePosLevel(2, 0, 0), 1); + TestCellOps(RectFromDegrees(-41, 160, -40, -160), + S2Cell::FromFacePosLevel(5, 0, 0), 1); + + // This is the leaf cell at the top right hand corner of face 0. + // It has two angles of 60 degrees and two of 120 degrees. + S2Cell cell0tr(S2Point(1 + 1e-12, 1, 1)); + S2LatLngRect bound0tr = cell0tr.GetRectBound(); + S2LatLng v0(cell0tr.GetVertexRaw(0)); + TestCellOps(RectFromDegrees(v0.lat().degrees() - 1e-8, + v0.lng().degrees() - 1e-8, + v0.lat().degrees() - 2e-10, + v0.lng().degrees() + 1e-10), + cell0tr, 1); + + // Rectangles that intersect a face but where no vertex of one region + // is contained by the other region. The first one passes through + // a corner of one of the face cells. + TestCellOps(RectFromDegrees(-37, -70, -36, -20), + S2Cell::FromFacePosLevel(5, 0, 0), 2); + + // These two intersect like a diamond and a square. + S2Cell cell202 = S2Cell::FromFacePosLevel(2, 0, 2); + S2LatLngRect bound202 = cell202.GetRectBound(); + TestCellOps(RectFromDegrees(bound202.lo().lat().degrees() + 3, + bound202.lo().lng().degrees() + 3, + bound202.hi().lat().degrees() - 3, + bound202.hi().lng().degrees() - 3), + cell202, 2); +} + +TEST(S2LatLngRect, EncodeDecode) { + S2LatLngRect r = RectFromDegrees(-20, -80, 10, 20); + Encoder encoder; + r.Encode(&encoder); + Decoder decoder(encoder.base(), encoder.length()); + S2LatLngRect decoded_rect = S2LatLngRect::Empty(); + EXPECT_TRUE(decoded_rect.Decode(&decoder)); + EXPECT_EQ(r, decoded_rect); +} + +TEST(S2LatLngRect, Area) { + EXPECT_EQ(S2LatLngRect::Empty().Area(), 0.0); + EXPECT_DOUBLE_EQ(S2LatLngRect::Full().Area(), 4 * M_PI); + EXPECT_DOUBLE_EQ(RectFromDegrees(0, 0, 90, 90).Area(), M_PI / 2); +} + +// Returns the minimum distance from X to the latitude line segment defined by +// the given latitude and longitude interval. +S1Angle GetDistance(const S2LatLng& x, + const S1Angle& lat, + const S1Interval& interval) { + EXPECT_TRUE(x.is_valid()); + EXPECT_TRUE(interval.is_valid()); + + // Is X inside the longitude interval? + if (interval.Contains(x.lng().radians())) + return (x.lat() - lat).abs(); + + // Return the distance to the closer endpoint. + return min(x.GetDistance(S2LatLng(lat, S1Angle::Radians(interval.lo()))), + x.GetDistance(S2LatLng(lat, S1Angle::Radians(interval.hi())))); +} + +static S1Angle BruteForceDistance(const S2LatLngRect& a, + const S2LatLngRect& b) { + if (a.Intersects(b)) + return S1Angle::Radians(0); + + // Compare every point in 'a' against every latitude edge and longitude edge + // in 'b', and vice-versa, for a total of 16 point-vs-latitude-edge tests and + // 16 point-vs-longitude-edge tests. + S2LatLng pnt_a[4], pnt_b[4]; + pnt_a[0] = S2LatLng(a.lat_lo(), a.lng_lo()); + pnt_a[1] = S2LatLng(a.lat_lo(), a.lng_hi()); + pnt_a[2] = S2LatLng(a.lat_hi(), a.lng_hi()); + pnt_a[3] = S2LatLng(a.lat_hi(), a.lng_lo()); + pnt_b[0] = S2LatLng(b.lat_lo(), b.lng_lo()); + pnt_b[1] = S2LatLng(b.lat_lo(), b.lng_hi()); + pnt_b[2] = S2LatLng(b.lat_hi(), b.lng_hi()); + pnt_b[3] = S2LatLng(b.lat_hi(), b.lng_lo()); + + // Make arrays containing the lo/hi latitudes and the lo/hi longitude edges. + S1Angle lat_a[2] = { a.lat_lo(), a.lat_hi() }; + S1Angle lat_b[2] = { b.lat_lo(), b.lat_hi() }; + S2Point lng_edge_a[2][2] = { { pnt_a[0].ToPoint(), pnt_a[3].ToPoint() }, + { pnt_a[1].ToPoint(), pnt_a[2].ToPoint() } }; + S2Point lng_edge_b[2][2] = { { pnt_b[0].ToPoint(), pnt_b[3].ToPoint() }, + { pnt_b[1].ToPoint(), pnt_b[2].ToPoint() } }; + + S1Angle min_distance = S1Angle::Degrees(180.0); + for (int i = 0; i < 4; ++i) { + // For each point in a and b. + const S2LatLng& current_a = pnt_a[i]; + const S2LatLng& current_b = pnt_b[i]; + + for (int j = 0; j < 2; ++j) { + // Get distances to latitude and longitude edges. + S1Angle a_to_lat = GetDistance(current_a, lat_b[j], b.lng()); + S1Angle b_to_lat = GetDistance(current_b, lat_a[j], a.lng()); + S1Angle a_to_lng = S2EdgeUtil::GetDistance( + current_a.ToPoint(), lng_edge_b[j][0], lng_edge_b[j][1]); + S1Angle b_to_lng = S2EdgeUtil::GetDistance( + current_b.ToPoint(), lng_edge_a[j][0], lng_edge_a[j][1]); + + min_distance = min(min_distance, + min(a_to_lat, min(b_to_lat, min(a_to_lng, b_to_lng)))); + } + } + return min_distance; +} + +static S1Angle BruteForceRectPointDistance(const S2LatLngRect& a, + const S2LatLng& b) { + if (a.Contains(b)) { + return S1Angle::Radians(0); + } + + S1Angle b_to_lo_lat = GetDistance(b, a.lat_lo(), a.lng()); + S1Angle b_to_hi_lat = GetDistance(b, a.lat_hi(), a.lng()); + S1Angle b_to_lo_lng = S2EdgeUtil::GetDistance( + b.ToPoint(), + S2LatLng(a.lat_lo(), a.lng_lo()).ToPoint(), + S2LatLng(a.lat_hi(), a.lng_lo()).ToPoint()); + S1Angle b_to_hi_lng = S2EdgeUtil::GetDistance( + b.ToPoint(), + S2LatLng(a.lat_lo(), a.lng_hi()).ToPoint(), + S2LatLng(a.lat_hi(), a.lng_hi()).ToPoint()); + return min(b_to_lo_lat, min(b_to_hi_lat, min(b_to_lo_lng, b_to_hi_lng))); +} + +// 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. +static void VerifyGetDistance(const S2LatLngRect& a, const S2LatLngRect& b) { + S1Angle distance1 = BruteForceDistance(a, b); + S1Angle distance2 = a.GetDistance(b); + EXPECT_NEAR(distance1.radians() - distance2.radians(), 0, 1e-10) + << a << ":" << b; +} + +static S2LatLngRect PointRectFromDegrees(double lat, double lng) { + return S2LatLngRect::FromPoint( + S2LatLng::FromDegrees(lat, lng).Normalized()); +} + +// 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. +static void VerifyGetRectPointDistance( + const S2LatLngRect& a, const S2LatLng& p) { + S1Angle distance1 = BruteForceRectPointDistance(a, p.Normalized()); + S1Angle distance2 = a.GetDistance(p.Normalized()); + EXPECT_NEAR(fabs(distance1.radians() - distance2.radians()), 0, 1e-10) + << a << ":" << p; +} + +TEST(S2LatLngRect, GetDistanceOverlapping) { + // Check pairs of rectangles that overlap: (should all return 0): + S2LatLngRect a = RectFromDegrees(0, 0, 2, 2); + S2LatLngRect b = PointRectFromDegrees(0, 0); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(a)); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(b)); + EXPECT_EQ(S1Angle::Radians(0), b.GetDistance(b)); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(S2LatLng::FromDegrees(0, 0))); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(RectFromDegrees(0, 1, 2, 3))); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(RectFromDegrees(0, 2, 2, 4))); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(RectFromDegrees(1, 0, 3, 2))); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(RectFromDegrees(2, 0, 4, 2))); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(RectFromDegrees(1, 1, 3, 3))); + EXPECT_EQ(S1Angle::Radians(0), a.GetDistance(RectFromDegrees(2, 2, 4, 4))); +} + +TEST(S2LatLngRect, GetDistanceRectVsPoint) { + // Rect that spans 180. + S2LatLngRect a = RectFromDegrees(-1, -1, 2, 1); + VerifyGetDistance(a, PointRectFromDegrees(-2, -1)); + VerifyGetDistance(a, PointRectFromDegrees(1, 2)); + + VerifyGetDistance(PointRectFromDegrees(-2, -1), a); + VerifyGetDistance(PointRectFromDegrees(1, 2), a); + + VerifyGetRectPointDistance(a, S2LatLng::FromDegrees(-2, -1)); + VerifyGetRectPointDistance(a, S2LatLng::FromDegrees(1, 2)); + + // Tests near the north pole. + S2LatLngRect b = RectFromDegrees(86, 0, 88, 2); + VerifyGetDistance(b, PointRectFromDegrees(87, 3)); + VerifyGetDistance(b, PointRectFromDegrees(87, -1)); + VerifyGetDistance(b, PointRectFromDegrees(89, 1)); + VerifyGetDistance(b, PointRectFromDegrees(89, 181)); + VerifyGetDistance(b, PointRectFromDegrees(85, 1)); + VerifyGetDistance(b, PointRectFromDegrees(85, 181)); + VerifyGetDistance(b, PointRectFromDegrees(90, 0)); + + VerifyGetDistance(PointRectFromDegrees(87, 3), b); + VerifyGetDistance(PointRectFromDegrees(87, -1), b); + VerifyGetDistance(PointRectFromDegrees(89, 1), b); + VerifyGetDistance(PointRectFromDegrees(89, 181), b); + VerifyGetDistance(PointRectFromDegrees(85, 1), b); + VerifyGetDistance(PointRectFromDegrees(85, 181), b); + VerifyGetDistance(PointRectFromDegrees(90, 0), b); + + VerifyGetRectPointDistance(b, S2LatLng::FromDegrees(87, 3)); + VerifyGetRectPointDistance(b, S2LatLng::FromDegrees(87, -1)); + VerifyGetRectPointDistance(b, S2LatLng::FromDegrees(89, 1)); + VerifyGetRectPointDistance(b, S2LatLng::FromDegrees(89, 181)); + VerifyGetRectPointDistance(b, S2LatLng::FromDegrees(85, 1)); + VerifyGetRectPointDistance(b, S2LatLng::FromDegrees(85, 181)); + VerifyGetRectPointDistance(b, S2LatLng::FromDegrees(90, 0)); + + // Rect that touches the north pole. + S2LatLngRect c = RectFromDegrees(88, 0, 90, 2); + VerifyGetDistance(c, PointRectFromDegrees(89, 3)); + VerifyGetDistance(c, PointRectFromDegrees(89, 90)); + VerifyGetDistance(c, PointRectFromDegrees(89, 181)); + VerifyGetDistance(PointRectFromDegrees(89, 3), c); + VerifyGetDistance(PointRectFromDegrees(89, 90), c); + VerifyGetDistance(PointRectFromDegrees(89, 181), c); +} + +TEST(S2LatLngRect, GetDistanceRectVsRect) { + // Rect that spans 180. + S2LatLngRect a = RectFromDegrees(-1, -1, 2, 1); + VerifyGetDistance(a, RectFromDegrees(0, 2, 1, 3)); + VerifyGetDistance(a, RectFromDegrees(-2, -3, -1, -2)); + + // Tests near the south pole. + S2LatLngRect b = RectFromDegrees(-87, 0, -85, 3); + VerifyGetDistance(b, RectFromDegrees(-89, 1, -88, 2)); + VerifyGetDistance(b, RectFromDegrees(-84, 1, -83, 2)); + VerifyGetDistance(b, RectFromDegrees(-88, 90, -86, 91)); + VerifyGetDistance(b, RectFromDegrees(-84, -91, -83, -90)); + VerifyGetDistance(b, RectFromDegrees(-90, 181, -89, 182)); + VerifyGetDistance(b, RectFromDegrees(-84, 181, -83, 182)); +} + +TEST(S2LatLngRect, GetDistanceRandomPairs) { + // Test random pairs. + for (int i = 0; i < 10000; ++i) { + S2LatLngRect a = + S2LatLngRect::FromPointPair(S2LatLng(S2Testing::RandomPoint()), + S2LatLng(S2Testing::RandomPoint())); + S2LatLngRect b = + S2LatLngRect::FromPointPair(S2LatLng(S2Testing::RandomPoint()), + S2LatLng(S2Testing::RandomPoint())); + VerifyGetDistance(a, b); + + + S2LatLng c(S2Testing::RandomPoint()); + VerifyGetRectPointDistance(a, c); + VerifyGetRectPointDistance(b, c); + } +} + +// This function assumes that GetDirectedHausdorffDistance() always returns +// a distance from some point in a to b. So the function mainly tests whether +// the returned distance is large enough, and only does a weak test on whether +// it is small enough. +static void VerifyGetDirectedHausdorffDistance(const S2LatLngRect& a, + const S2LatLngRect& b) { + S1Angle hausdorff_distance = a.GetDirectedHausdorffDistance(b); + + static const double kResolution = 0.1; + // Record the max sample distance as well as the sample point realizing the + // max for easier debugging. + S1Angle max_distance; + double lat_max, lng_max; + + int sample_size_on_lat = + static_cast<int>(a.lat().GetLength() / kResolution) + 1; + int sample_size_on_lng = + static_cast<int>(a.lng().GetLength() / kResolution) + 1; + double delta_on_lat = a.lat().GetLength() / sample_size_on_lat; + double delta_on_lng = a.lng().GetLength() / sample_size_on_lng; + + double lng = a.lng().lo(); + for (int i = 0; i <= sample_size_on_lng; ++i, lng += delta_on_lng) { + double lat = a.lat().lo(); + for (int j = 0; j <= sample_size_on_lat; ++j, lat += delta_on_lat) { + S2LatLng latlng = S2LatLng::FromRadians(lat, lng).Normalized(); + S1Angle distance_to_b = b.GetDistance(latlng); + + if (distance_to_b >= max_distance) { + max_distance = distance_to_b; + lat_max = lat; + lng_max = lng; + } + } + } + + EXPECT_LE(max_distance.radians(), hausdorff_distance.radians() + 1e-10) + << a << ":" << b; + EXPECT_GE(max_distance.radians(), hausdorff_distance.radians() - kResolution) + << a << ":" << b; +} + + +TEST(S2LatLngRect, GetDirectedHausdorffDistanceRandomPairs) { + // Test random pairs. + for (int i = 0; i < 5000; ++i) { + S2LatLngRect a = + S2LatLngRect::FromPointPair(S2LatLng(S2Testing::RandomPoint()), + S2LatLng(S2Testing::RandomPoint())); + S2LatLngRect b = + S2LatLngRect::FromPointPair(S2LatLng(S2Testing::RandomPoint()), + S2LatLng(S2Testing::RandomPoint())); + // a and b are *minimum* bounding rectangles of two random points, in + // particular, their Voronoi diagrams are always of the same topology. We + // take the "complements" of a and b for more thorough testing. + S2LatLngRect a2(a.lat(), a.lng().Complement()); + S2LatLngRect b2(b.lat(), b.lng().Complement()); + + VerifyGetDirectedHausdorffDistance(a, b); + VerifyGetDirectedHausdorffDistance(b, a); + + VerifyGetDirectedHausdorffDistance(a, b2); + VerifyGetDirectedHausdorffDistance(b2, a); + + VerifyGetDirectedHausdorffDistance(a2, b); + VerifyGetDirectedHausdorffDistance(b, a2); + + VerifyGetDirectedHausdorffDistance(a2, b2); + VerifyGetDirectedHausdorffDistance(b2, a2); + } +} + +TEST(S2LatLngRect, GetDirectedHausdorffDistanceContained) { + // Caller rect is contained in callee rect. Should return 0. + S2LatLngRect a = RectFromDegrees(-10, 20, -5, 90); + EXPECT_EQ(S1Angle::Radians(0), + a.GetDirectedHausdorffDistance(RectFromDegrees(-10, 20, -5, 90))); + EXPECT_EQ(S1Angle::Radians(0), + a.GetDirectedHausdorffDistance(RectFromDegrees(-10, 19, -5, 91))); + EXPECT_EQ(S1Angle::Radians(0), + a.GetDirectedHausdorffDistance(RectFromDegrees(-11, 20, -4, 90))); + EXPECT_EQ(S1Angle::Radians(0), + a.GetDirectedHausdorffDistance(RectFromDegrees(-11, 19, -4, 91))); +} + +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); + EXPECT_DOUBLE_EQ(a1.GetDirectedHausdorffDistance(b).radians(), + a1.GetDistance(b).radians()); + EXPECT_DOUBLE_EQ(a2.GetDirectedHausdorffDistance(b).radians(), + a2.GetDistance(b).radians()); + + b = RectFromDegrees(4, -10, 80, 10); + EXPECT_DOUBLE_EQ(a1.GetDirectedHausdorffDistance(b).radians(), + a1.GetDistance(b).radians()); + EXPECT_DOUBLE_EQ(a2.GetDirectedHausdorffDistance(b).radians(), + a2.GetDistance(b).radians()); + + b = RectFromDegrees(70, 170, 80, -170); + EXPECT_DOUBLE_EQ(a1.GetDirectedHausdorffDistance(b).radians(), + a1.GetDistance(b).radians()); + EXPECT_DOUBLE_EQ(a2.GetDirectedHausdorffDistance(b).radians(), + a2.GetDistance(b).radians()); +} + +TEST(S2LatLngRect, GetDirectedHausdorffDistanceRectToPoint) { + S2LatLngRect a = RectFromDegrees(1, -8, 10, 20); + VerifyGetDirectedHausdorffDistance(a, PointRectFromDegrees(5, 8)); + VerifyGetDirectedHausdorffDistance(a, PointRectFromDegrees(-6, -100)); + // south pole + VerifyGetDirectedHausdorffDistance(a, PointRectFromDegrees(-90, -20)); + // north pole + VerifyGetDirectedHausdorffDistance(a, PointRectFromDegrees(90, 0)); +} + +TEST(S2LatLngRect, GetDirectedHausdorffDistanceRectToRectNearPole) { + // Tests near south pole. + S2LatLngRect a = RectFromDegrees(-87, 0, -85, 3); + VerifyGetDirectedHausdorffDistance(a, RectFromDegrees(-89, 1, -88, 2)); + VerifyGetDirectedHausdorffDistance(a, RectFromDegrees(-84, 1, -83, 2)); + VerifyGetDirectedHausdorffDistance(a, RectFromDegrees(-88, 90, -86, 91)); + VerifyGetDirectedHausdorffDistance(a, RectFromDegrees(-84, -91, -83, -90)); + VerifyGetDirectedHausdorffDistance(a, RectFromDegrees(-90, 181, -89, 182)); + VerifyGetDirectedHausdorffDistance(a, RectFromDegrees(-84, 181, -83, 182)); +} + +TEST(S2LatLngRect, GetDirectedHausdorffDistanceRectToRectDegenerateCases) { + // Rectangles that contain poles. + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(0, 10, 90, 20), RectFromDegrees(-4, -10, 4, 0)); + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(-4, -10, 4, 0), RectFromDegrees(0, 10, 90, 20)); + + // Two rectangles share same or complement longitudinal intervals. + S2LatLngRect a = RectFromDegrees(-50, -10, 50, 10); + S2LatLngRect b = RectFromDegrees(30, -10, 60, 10); + VerifyGetDirectedHausdorffDistance(a, b); + S2LatLngRect c(a.lat(), a.lng().Complement()); + VerifyGetDirectedHausdorffDistance(c, b); + + // rectangle a touches b_opposite_lng. + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(10, 170, 30, 180), RectFromDegrees(-50, -10, 50, 10)); + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(10, -180, 30, -170), RectFromDegrees(-50, -10, 50, 10)); + + // rectangle b's Voronoi diagram is degenerate (lng interval spans 180 + // degrees), and a touches the degenerate Voronoi vertex. + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(-30, 170, 30, 180), RectFromDegrees(-10, -90, 10, 90)); + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(-30, -180, 30, -170), RectFromDegrees(-10, -90, 10, 90)); + + // rectangle a touches a voronoi vertex of rectangle b. + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(-20, 105, 20, 110), RectFromDegrees(-30, 5, 30, 15)); + VerifyGetDirectedHausdorffDistance( + RectFromDegrees(-20, 95, 20, 105), RectFromDegrees(-30, 5, 30, 15)); +} |