|
| 1 | +#include "meili/geometry_helpers.h" |
| 2 | +#include "midgard/encoded.h" |
| 3 | +#include "midgard/polyline2.h" |
| 4 | +#include <gtest/gtest.h> |
| 5 | + |
| 6 | +// Foundational geometric unit tests. |
| 7 | + |
| 8 | +using namespace valhalla; |
| 9 | +using namespace valhalla::midgard; |
| 10 | + |
| 11 | +namespace { |
| 12 | + |
| 13 | +//=========================================================================== |
| 14 | +// Take a given real-world road shape. Create a second shape that is the |
| 15 | +// reverse shape-point order of the first. |
| 16 | +// Take a handful of "gps points" and project them onto both shapes. |
| 17 | +// Assert that: |
| 18 | +// 0) for percentage_along's that are small, that they're not so small |
| 19 | +// that 1.0-percentage_along==1.0. See this line of code in |
| 20 | +// candidate_search.cc, WithinSquaredDistance(): |
| 21 | +// const double dist = edge->forward() ? offset : 1.0 - offset; |
| 22 | +// 1) the fwd/rev projections project to the same location |
| 23 | +// 2) that the fwd_percentage_along + rev_percentage_along == 1.0 (within tol) |
| 24 | +//=========================================================================== |
| 25 | +TEST(geometry, fwd_rev_projection_tols) { |
| 26 | + // Some real world encoded shape data. |
| 27 | + const std::string fwd_enc_shape = |
| 28 | + {-24, -109, -78, 36, -90, -4, -46, 12, 33, 118, 71, 16, 117, 47, 41, 91, |
| 29 | + 113, 44, 45, 112, -125, 1, 80, -67, 1, 54, -1, 1, 117, -31, 2, -43, |
| 30 | + 1, -33, 1, 21, -93, 2, 15, -25, 1, 47, -97, 1, 86, -123, 1, -64, |
| 31 | + 1, 51, -110, 2, 48, -90, 1, 29, -84, 1, 67, -94, 2, 84, -10, 1, |
| 32 | + -86, 1, 82, -106, 2, 70, -32, 1, 64, -96, 3, -20, 1, -126, 2, 112, |
| 33 | + -56, 3, 26, -104, 3, 15, -30, 3, 101, -48, 3, -87, 1, -28, 3, -107, |
| 34 | + 1, -62, 1, 113, 118, -81, 1, 110, 27, 56, -106, 1, 71, -78, 1, -67, |
| 35 | + 1, -122, 2, -7, 1, -40, 1, -81, 1, -86, 1, 17, 108, 26, -84, 1, |
| 36 | + -100, 1, 54, -62, 1, -111, 2, -120, 3, -127, 2, -16, 1, 127, -62, 1, |
| 37 | + -43, 1, -32, 1, -67, 2, 38, -49, 1, -100, 1, 64, 14, -74, 1, 105, |
| 38 | + -100, 2, -125, 1, -44, 3, -81, 1, -126, 3, -93, 1, -64, 1, -67, 2, |
| 39 | + -82, 3, -85, 1, -36, 1, -75, 1, 48, -67, 1, -128, 1, -111, 1, -72, |
| 40 | + 2, -59, 2, -8, 3, -113, 3, -40, 3, -85, 2, -126, 2, -43, 2, -68, |
| 41 | + 2, -17, 1, -46, 1, -35, 2, -4, 2, 42, -106, 1, -96, 1, 48, -82, |
| 42 | + 7, -38, 4, -102, 2, -90, 1, -74, 3, -36, 1, -16, 1, 47, -38, 2, |
| 43 | + -105, 2, -70, 1, -37, 1, -34, 2, -85, 2, -44, 1, 117, -106, 2, 91, |
| 44 | + -42, 2, -68, 1, -18, 2, -126, 1, -62, 7, -62, 2, -4, 2, -106, 1, |
| 45 | + -2, 3, -118, 1, -78, 1, -118, 1}; |
| 46 | + |
| 47 | + std::vector<PointLL> fwd_shape_points = decode7<std::vector<PointLL>>(fwd_enc_shape); |
| 48 | + std::vector<PointLL> rev_shape_points(fwd_shape_points); |
| 49 | + std::reverse(rev_shape_points.begin(), rev_shape_points.end()); |
| 50 | + std::string rev_enc_shape = midgard::encode7(rev_shape_points); |
| 51 | + |
| 52 | + // This first check ensures that the length of a real-world series of segments |
| 53 | + // is equal when measured forwards and backwards. This is reasonable to expect. |
| 54 | + // The possible issues that might come into play is if our underlying Distance() |
| 55 | + // computation is 1) too approximate, and/or 2) employs some sort of curved |
| 56 | + // earth bias based on the first point of each segment. |
| 57 | + double fwd_shape_length = midgard::length(fwd_shape_points); |
| 58 | + double rev_shape_length = midgard::length(rev_shape_points); |
| 59 | + EXPECT_TRUE(std::fabs(fwd_shape_length - rev_shape_length) < 1e-5); |
| 60 | + |
| 61 | + // Project these gps points onto the fwd-shape and the rev-shape. |
| 62 | + // See that they project to the same point and that their projection's |
| 63 | + // distance-along percentages sum to 1.0. |
| 64 | + const PointLL gps_points[] = {{13.262609100000001, 38.159699600000003}, |
| 65 | + {13.262637200000002, 38.159659599999998}, |
| 66 | + {13.262741800000005, 38.159575799999997}, |
| 67 | + {13.262665099999999, 38.159486100000001}, |
| 68 | + {13.262594400000001, 38.159471100000003}, |
| 69 | + {13.262595700000003, 38.159450400000005}}; |
| 70 | + |
| 71 | + size_t i = 0; |
| 72 | + for (const auto& gps_point : gps_points) { |
| 73 | + // meili::helpers::Project() consumes the incoming shape so we have to |
| 74 | + // create it with each iteration. |
| 75 | + midgard::Shape7Decoder<midgard::PointLL> fwd_shape(fwd_enc_shape.c_str(), fwd_enc_shape.size()); |
| 76 | + |
| 77 | + double fwd_sq_distance = -1.0; |
| 78 | + size_t fwd_segment; |
| 79 | + double fwd_percentage_along = -1.0; |
| 80 | + PointLL fwd_proj_point; |
| 81 | + std::tie(fwd_proj_point, fwd_sq_distance, fwd_segment, fwd_percentage_along) = |
| 82 | + valhalla::meili::helpers::Project(gps_point, fwd_shape); |
| 83 | + |
| 84 | + // make sure the fwd_percentage_along is big enough that it doesn't |
| 85 | + // disappear when subtracted from 1.0. |
| 86 | + if (fwd_percentage_along > 0.0) { |
| 87 | + double reverse_pct_along = 1.0 - fwd_percentage_along; |
| 88 | + ASSERT_NE(reverse_pct_along, 1.0); |
| 89 | + } |
| 90 | + |
| 91 | + // meili::helpers::Project() consumes the incoming shape so we have to |
| 92 | + // create it with each iteration. |
| 93 | + midgard::Shape7Decoder<midgard::PointLL> rev_shape(rev_enc_shape.c_str(), rev_enc_shape.size()); |
| 94 | + |
| 95 | + double rev_sq_distance = -1.0; |
| 96 | + size_t rev_segment; |
| 97 | + double rev_percentage_along = -1.0; |
| 98 | + PointLL rev_proj_point; |
| 99 | + std::tie(rev_proj_point, rev_sq_distance, rev_segment, rev_percentage_along) = |
| 100 | + valhalla::meili::helpers::Project(gps_point, rev_shape); |
| 101 | + |
| 102 | + // make sure the rev_percentage_along is big enough that it doesn't |
| 103 | + // disappear when subtracted from 1.0. |
| 104 | + if (rev_percentage_along > 0.0) { |
| 105 | + double forward_pct_along = 1.0 - rev_percentage_along; |
| 106 | + ASSERT_NE(forward_pct_along, 1.0); |
| 107 | + } |
| 108 | + |
| 109 | + // The first gps point is a special case! Without the fix, fwd_percentage_along |
| 110 | + // is ~1e-9; the fix is to snap it to 0.f. |
| 111 | + if (i++ == 0) { |
| 112 | + ASSERT_EQ(fwd_percentage_along, 0.0); |
| 113 | + ASSERT_EQ(rev_percentage_along, 1.0); |
| 114 | + } |
| 115 | + |
| 116 | + // 6 decimal digits is roughly 0.1 m, a reasonable expectation |
| 117 | + constexpr double tenth_of_meter_in_deg = 0.000001; |
| 118 | + ASSERT_TRUE(std::fabs(fwd_proj_point.lat() - rev_proj_point.lat()) < tenth_of_meter_in_deg); |
| 119 | + ASSERT_TRUE(std::fabs(fwd_proj_point.lng() - rev_proj_point.lng()) < tenth_of_meter_in_deg); |
| 120 | + |
| 121 | + // wherever we project, the fwd+rev percentages must add up to 1.0 (within tol) |
| 122 | + double total_percent = fwd_percentage_along + rev_percentage_along; |
| 123 | + ASSERT_TRUE(std::fabs(total_percent - 1.0) < 0.0001); |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +} // anonymous namespace |
0 commit comments