From 5c91f77425fc6929c53b61ef06c4692792cb7fef Mon Sep 17 00:00:00 2001 From: Arne Juul Date: Fri, 17 Jul 2020 08:32:59 +0000 Subject: unit test all constructors and full API for GeoLocation --- .../tests/common/location/geo_location_test.cpp | 276 ++++++++++++++++++++- .../src/vespa/searchlib/common/geo_location.h | 2 + 2 files changed, 269 insertions(+), 9 deletions(-) diff --git a/searchlib/src/tests/common/location/geo_location_test.cpp b/searchlib/src/tests/common/location/geo_location_test.cpp index 8093ea61697..31b844d0fc8 100644 --- a/searchlib/src/tests/common/location/geo_location_test.cpp +++ b/searchlib/src/tests/common/location/geo_location_test.cpp @@ -9,6 +9,15 @@ using search::common::GeoLocation; using search::common::GeoLocationParser; +using Box = search::common::GeoLocation::Box; +using Point = search::common::GeoLocation::Point; +using Range = search::common::GeoLocation::Range; +using Aspect = search::common::GeoLocation::Aspect; + +constexpr int32_t plus_inf = std::numeric_limits::max(); +constexpr int32_t minus_inf = std::numeric_limits::min(); +constexpr uint32_t u32_inf = std::numeric_limits::max(); + bool is_parseable(const char *str) { GeoLocationParser parser; return parser.parseOldFormat(str); @@ -20,7 +29,7 @@ GeoLocation parse(const char *str) { return parser.getGeoLocation(); } -TEST(GeoLocationTest, malformed_bounding_boxes_are_not_parseable) { +TEST(GeoLocationParserTest, malformed_bounding_boxes_are_not_parseable) { EXPECT_TRUE(is_parseable("[2,10,20,30,40]")); EXPECT_FALSE(is_parseable("[2,10,20,30,40][2,10,20,30,40]")); EXPECT_FALSE(is_parseable("[1,10,20,30,40]")); @@ -31,7 +40,7 @@ TEST(GeoLocationTest, malformed_bounding_boxes_are_not_parseable) { EXPECT_FALSE(is_parseable("[10,20,30,40]")); } -TEST(GeoLocationTest, malformed_circles_are_not_parseable) { +TEST(GeoLocationParserTest, malformed_circles_are_not_parseable) { EXPECT_TRUE(is_parseable("(2,10,20,5,0,0,0)")); EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0)(2,10,20,5,0,0,0)")); EXPECT_FALSE(is_parseable("(1,10,20,5,0,0,0)")); @@ -43,7 +52,7 @@ TEST(GeoLocationTest, malformed_circles_are_not_parseable) { EXPECT_FALSE(is_parseable("(10,20,5)")); } -TEST(GeoLocationTest, bounding_boxes_can_be_parsed) { +TEST(GeoLocationParserTest, bounding_boxes_can_be_parsed) { auto loc = parse("[2,10,20,30,40]"); EXPECT_EQ(false, loc.has_point); EXPECT_EQ(true, loc.bounding_box.active()); @@ -57,7 +66,7 @@ TEST(GeoLocationTest, bounding_boxes_can_be_parsed) { EXPECT_EQ(40, loc.bounding_box.y.high); } -TEST(GeoLocationTest, circles_can_be_parsed) { +TEST(GeoLocationParserTest, circles_can_be_parsed) { auto loc = parse("(2,10,20,5,0,0,0)"); EXPECT_EQ(true, loc.has_point); EXPECT_EQ(true, loc.bounding_box.active()); @@ -71,7 +80,7 @@ TEST(GeoLocationTest, circles_can_be_parsed) { EXPECT_EQ(25, loc.bounding_box.y.high); } -TEST(GeoLocationTest, circles_can_have_aspect_ratio) { +TEST(GeoLocationParserTest, circles_can_have_aspect_ratio) { auto loc = parse("(2,10,20,5,0,0,0,2147483648)"); EXPECT_EQ(true, loc.has_point); EXPECT_EQ(true, loc.bounding_box.active()); @@ -85,7 +94,7 @@ TEST(GeoLocationTest, circles_can_have_aspect_ratio) { EXPECT_EQ(25, loc.bounding_box.y.high); } -TEST(GeoLocationTest, bounding_box_can_be_specified_after_circle) { +TEST(GeoLocationParserTest, bounding_box_can_be_specified_after_circle) { auto loc = parse("(2,10,20,5,0,0,0)[2,10,20,30,40]"); EXPECT_EQ(true, loc.has_point); EXPECT_EQ(true, loc.bounding_box.active()); @@ -99,7 +108,7 @@ TEST(GeoLocationTest, bounding_box_can_be_specified_after_circle) { EXPECT_EQ(25, loc.bounding_box.y.high); } -TEST(GeoLocationTest, circles_can_be_specified_after_bounding_box) { +TEST(GeoLocationParserTest, circles_can_be_specified_after_bounding_box) { auto loc = parse("[2,10,20,30,40](2,10,20,5,0,0,0)"); EXPECT_EQ(true, loc.has_point); EXPECT_EQ(true, loc.bounding_box.active()); @@ -113,13 +122,13 @@ TEST(GeoLocationTest, circles_can_be_specified_after_bounding_box) { EXPECT_EQ(25, loc.bounding_box.y.high); } -TEST(GeoLocationTest, santa_search_gives_non_wrapped_bounding_box) { +TEST(GeoLocationParserTest, santa_search_gives_non_wrapped_bounding_box) { auto loc = parse("(2,122163600,89998536,290112,4,2000,0,109704)"); EXPECT_GE(loc.bounding_box.x.high, loc.bounding_box.x.low); EXPECT_GE(loc.bounding_box.y.high, loc.bounding_box.y.low); } -TEST(GeoLocationTest, near_boundary_search_gives_non_wrapped_bounding_box) { +TEST(GeoLocationParserTest, near_boundary_search_gives_non_wrapped_bounding_box) { auto loc1 = parse("(2,2000000000,2000000000,3000000000,0,1,0)"); EXPECT_GE(loc1.bounding_box.x.high, loc1.bounding_box.x.low); EXPECT_GE(loc1.bounding_box.y.high, loc1.bounding_box.y.low); @@ -133,4 +142,253 @@ TEST(GeoLocationTest, near_boundary_search_gives_non_wrapped_bounding_box) { EXPECT_EQ(std::numeric_limits::min(), loc2.bounding_box.y.low); } +void check_box(const GeoLocation &location, Box expected) +{ + int32_t lx = expected.x.low; + int32_t hx = expected.x.high; + int32_t ly = expected.y.low; + int32_t hy = expected.y.high; + EXPECT_TRUE(location.inside_limit(Point{lx,ly})); + EXPECT_TRUE(location.inside_limit(Point{lx,hy})); + EXPECT_TRUE(location.inside_limit(Point{hx,ly})); + EXPECT_TRUE(location.inside_limit(Point{hx,hy})); + + EXPECT_FALSE(location.inside_limit(Point{lx,ly-1})); + EXPECT_FALSE(location.inside_limit(Point{lx,hy+1})); + EXPECT_FALSE(location.inside_limit(Point{lx-1,ly})); + EXPECT_FALSE(location.inside_limit(Point{lx-1,hy})); + EXPECT_FALSE(location.inside_limit(Point{hx,ly-1})); + EXPECT_FALSE(location.inside_limit(Point{hx,hy+1})); + EXPECT_FALSE(location.inside_limit(Point{hx+1,ly})); + EXPECT_FALSE(location.inside_limit(Point{hx+1,hy})); + + EXPECT_FALSE(location.inside_limit(Point{plus_inf,plus_inf})); + EXPECT_FALSE(location.inside_limit(Point{minus_inf,minus_inf})); +} + +TEST(GeoLocationTest, invalid_location) { + GeoLocation invalid; + EXPECT_FALSE(invalid.valid()); + EXPECT_FALSE(invalid.has_radius()); + EXPECT_FALSE(invalid.can_limit()); + EXPECT_FALSE(invalid.has_point); + EXPECT_FALSE(invalid.bounding_box.active()); + EXPECT_FALSE(invalid.x_aspect.active()); + + EXPECT_EQ(invalid.sq_distance_to(Point{0,0}), 0); + EXPECT_EQ(invalid.sq_distance_to(Point{999999,999999}), 0); + EXPECT_EQ(invalid.sq_distance_to(Point{-999999,-999999}), 0); + EXPECT_EQ(invalid.sq_distance_to(Point{plus_inf,plus_inf}), 0); + EXPECT_EQ(invalid.sq_distance_to(Point{minus_inf,minus_inf}), 0); + + EXPECT_TRUE(invalid.inside_limit(Point{0,0})); + EXPECT_TRUE(invalid.inside_limit(Point{999999,999999})); + EXPECT_TRUE(invalid.inside_limit(Point{-999999,-999999})); + EXPECT_TRUE(invalid.inside_limit(Point{plus_inf,plus_inf})); + EXPECT_TRUE(invalid.inside_limit(Point{minus_inf,minus_inf})); +} + +TEST(GeoLocationTest, point_location) { + GeoLocation location(Point{300,-400}); + EXPECT_TRUE(location.valid()); + EXPECT_FALSE(location.has_radius()); + EXPECT_FALSE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_FALSE(location.bounding_box.active()); + EXPECT_FALSE(location.x_aspect.active()); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{300,-400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{300,400}), 640000); + + EXPECT_TRUE(location.inside_limit(Point{0,0})); + EXPECT_TRUE(location.inside_limit(Point{999999,999999})); + EXPECT_TRUE(location.inside_limit(Point{-999999,-999999})); + EXPECT_TRUE(location.inside_limit(Point{plus_inf,plus_inf})); + EXPECT_TRUE(location.inside_limit(Point{minus_inf,minus_inf})); +} + +TEST(GeoLocationTest, point_and_radius) { + GeoLocation location(Point{300,-400}, 500); + EXPECT_TRUE(location.valid()); + EXPECT_TRUE(location.has_radius()); + EXPECT_TRUE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_TRUE(location.bounding_box.active()); + EXPECT_FALSE(location.x_aspect.active()); + + EXPECT_EQ(location.radius, 500); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{300,-400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{300,400}), 640000); + + EXPECT_TRUE(location.inside_limit(Point{0,0})); + EXPECT_TRUE(location.inside_limit(Point{-200,-400})); + EXPECT_TRUE(location.inside_limit(Point{800,-400})); + EXPECT_TRUE(location.inside_limit(Point{300,-400})); + EXPECT_TRUE(location.inside_limit(Point{300,100})); + EXPECT_TRUE(location.inside_limit(Point{300,-900})); + + check_box(location, Box{Range{0,600},{-800,0}}); +} + +TEST(GeoLocationTest, point_and_aspect) { + GeoLocation location(Point{600,400}, Aspect{0.5}); + // same: GeoLocation location(Point{600,400}, Aspect{1ul << 31}); + EXPECT_TRUE(location.valid()); + EXPECT_FALSE(location.has_radius()); + EXPECT_FALSE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_FALSE(location.bounding_box.active()); + EXPECT_TRUE(location.x_aspect.active()); + EXPECT_EQ(location.x_aspect.multiplier, 1ul << 31); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{1200,800}), 500*500); + + EXPECT_TRUE(location.inside_limit(Point{0,0})); + EXPECT_TRUE(location.inside_limit(Point{999999,999999})); + EXPECT_TRUE(location.inside_limit(Point{-999999,-999999})); + EXPECT_TRUE(location.inside_limit(Point{plus_inf,plus_inf})); + EXPECT_TRUE(location.inside_limit(Point{minus_inf,minus_inf})); +} + +TEST(GeoLocationTest, point_radius_and_aspect) { + GeoLocation location(Point{1200,400}, 500, Aspect{0.25}); + EXPECT_TRUE(location.valid()); + EXPECT_TRUE(location.has_radius()); + EXPECT_TRUE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_TRUE(location.bounding_box.active()); + EXPECT_TRUE(location.x_aspect.active()); + EXPECT_EQ(location.x_aspect.multiplier, 1ul << 30); + + EXPECT_EQ(location.radius, 500); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{1200,400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{1240,400}), 100); + + EXPECT_TRUE(location.inside_limit(Point{1200,400})); + EXPECT_TRUE(location.inside_limit(Point{0,0})); + EXPECT_TRUE(location.inside_limit(Point{2400,0})); + EXPECT_TRUE(location.inside_limit(Point{2400,800})); + EXPECT_TRUE(location.inside_limit(Point{0,800})); + // note: must be 4 outside since 3*0.25 may be truncated to 0 + EXPECT_FALSE(location.inside_limit(Point{-4,0})); + EXPECT_FALSE(location.inside_limit(Point{-4,800})); + EXPECT_FALSE(location.inside_limit(Point{2404,0})); + EXPECT_FALSE(location.inside_limit(Point{2404,800})); + EXPECT_FALSE(location.inside_limit(Point{2400,-1})); + EXPECT_FALSE(location.inside_limit(Point{2400,801})); + EXPECT_FALSE(location.inside_limit(Point{0,-1})); + EXPECT_FALSE(location.inside_limit(Point{0,801})); + EXPECT_FALSE(location.inside_limit(Point{plus_inf,plus_inf})); + EXPECT_FALSE(location.inside_limit(Point{minus_inf,minus_inf})); +} + +TEST(GeoLocationTest, box_location) { + Box mybox{Range{300,350},Range{400,450}}; + GeoLocation location(mybox); + EXPECT_TRUE(location.valid()); + EXPECT_FALSE(location.has_radius()); + EXPECT_TRUE(location.can_limit()); + EXPECT_FALSE(location.has_point); + EXPECT_TRUE(location.bounding_box.active()); + EXPECT_FALSE(location.x_aspect.active()); + + // currently does not measure distance outside box: + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 0); + EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{350,450}), 0); + EXPECT_EQ(location.sq_distance_to(Point{450,550}), 0); + + EXPECT_TRUE(location.inside_limit(Point{333,444})); + EXPECT_FALSE(location.inside_limit(Point{0,0})); + check_box(location, mybox); +} + +TEST(GeoLocationTest, box_and_point) { + Box mybox{Range{287,343},Range{366,401}}; + GeoLocation location(mybox, Point{300,400}); + EXPECT_TRUE(location.valid()); + EXPECT_FALSE(location.has_radius()); + EXPECT_TRUE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_TRUE(location.bounding_box.active()); + EXPECT_FALSE(location.x_aspect.active()); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{300,423}), 23*23); + + check_box(location, mybox); +} + +TEST(GeoLocationTest, box_point_and_aspect) { + Box mybox{Range{-1000,350},Range{-1000,600}}; + GeoLocation location(mybox, Point{600,400}, Aspect{0.5}); + EXPECT_TRUE(location.valid()); + EXPECT_FALSE(location.has_radius()); + EXPECT_TRUE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_TRUE(location.bounding_box.active()); + EXPECT_TRUE(location.x_aspect.active()); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{600,407}), 7*7); + EXPECT_EQ(location.sq_distance_to(Point{614,400}), 7*7); + + check_box(location, mybox); +} + +TEST(GeoLocationTest, box_point_and_radius) { + Box mybox{Range{-1000,350},Range{-1000,600}}; + GeoLocation location(mybox, Point{300,400}, 500); + EXPECT_TRUE(location.valid()); + EXPECT_TRUE(location.has_radius()); + EXPECT_TRUE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_TRUE(location.bounding_box.active()); + EXPECT_FALSE(location.x_aspect.active()); + + EXPECT_EQ(location.radius, 500); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{300,423}), 23*23); + + EXPECT_EQ(location.bounding_box.x.low, -200); + EXPECT_EQ(location.bounding_box.y.low, -100); + EXPECT_EQ(location.bounding_box.x.high, 350); + EXPECT_EQ(location.bounding_box.y.high, 600); +} + +TEST(GeoLocationTest, box_point_radius_and_aspect) { + Box mybox{Range{-1000,650},Range{-1000,700}}; + GeoLocation location(mybox, Point{600,400}, 500, Aspect{0.5}); + EXPECT_TRUE(location.valid()); + EXPECT_TRUE(location.has_radius()); + EXPECT_TRUE(location.can_limit()); + EXPECT_TRUE(location.has_point); + EXPECT_TRUE(location.bounding_box.active()); + EXPECT_TRUE(location.x_aspect.active()); + + EXPECT_EQ(location.radius, 500); + + EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500); + EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0); + EXPECT_EQ(location.sq_distance_to(Point{600,407}), 7*7); + EXPECT_EQ(location.sq_distance_to(Point{614,400}), 7*7); + + EXPECT_GE(location.bounding_box.x.low, -402); + EXPECT_LE(location.bounding_box.x.low, -400); + EXPECT_EQ(location.bounding_box.y.low, -100); + EXPECT_EQ(location.bounding_box.x.high, 650); + EXPECT_EQ(location.bounding_box.y.high, 700); +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/searchlib/src/vespa/searchlib/common/geo_location.h b/searchlib/src/vespa/searchlib/common/geo_location.h index 5d04a09142a..261951caf3e 100644 --- a/searchlib/src/vespa/searchlib/common/geo_location.h +++ b/searchlib/src/vespa/searchlib/common/geo_location.h @@ -28,6 +28,8 @@ struct GeoLocation uint32_t multiplier; Aspect() : multiplier(0) {} Aspect(uint32_t multiplier_in) : multiplier(multiplier_in) {} + // for unit tests: + Aspect(double multiplier_in) : multiplier(multiplier_in*4294967296.0) {} bool active() const { return multiplier != 0; } }; struct Range { -- cgit v1.2.3