Fix distance calculation consistency. (#6315)

Consolidate great circle distance calculations to use cheap ruler library.
This commit is contained in:
Siarhei Fedartsou
2022-08-19 23:31:40 +02:00
committed by GitHub
parent 8f0cd5cf7b
commit aadc088084
84 changed files with 780 additions and 683 deletions
+21 -79
View File
@@ -72,12 +72,10 @@ std::uint64_t squaredEuclideanDistance(const Coordinate lhs, const Coordinate rh
return result;
}
// Uses method described here:
// https://www.gpo.gov/fdsys/pkg/CFR-2005-title47-vol4/pdf/CFR-2005-title47-vol4-sec73-208.pdf
// should be within 0.1% or so of Vincenty method (assuming 19 buckets are enough)
// Should be more faster and more precise than Haversine
double fccApproximateDistance(const Coordinate coordinate_1, const Coordinate coordinate_2)
double greatCircleDistance(const Coordinate coordinate_1, const Coordinate coordinate_2)
{
// Should be within 0.1% or so of Vincenty method (assuming 19 buckets are enough)
// Should be more faster and more precise than Haversine
const auto lon1 = static_cast<double>(util::toFloating(coordinate_1.lon));
const auto lat1 = static_cast<double>(util::toFloating(coordinate_1.lat));
const auto lon2 = static_cast<double>(util::toFloating(coordinate_2.lon));
@@ -86,56 +84,6 @@ double fccApproximateDistance(const Coordinate coordinate_1, const Coordinate co
.distance({lon1, lat1}, {lon2, lat2});
}
double haversineDistance(const Coordinate coordinate_1, const Coordinate coordinate_2)
{
auto lon1 = static_cast<int>(coordinate_1.lon);
auto lat1 = static_cast<int>(coordinate_1.lat);
auto lon2 = static_cast<int>(coordinate_2.lon);
auto lat2 = static_cast<int>(coordinate_2.lat);
BOOST_ASSERT(lon1 != std::numeric_limits<int>::min());
BOOST_ASSERT(lat1 != std::numeric_limits<int>::min());
BOOST_ASSERT(lon2 != std::numeric_limits<int>::min());
BOOST_ASSERT(lat2 != std::numeric_limits<int>::min());
const double lt1 = lat1 / COORDINATE_PRECISION;
const double ln1 = lon1 / COORDINATE_PRECISION;
const double lt2 = lat2 / COORDINATE_PRECISION;
const double ln2 = lon2 / COORDINATE_PRECISION;
const double dlat1 = lt1 * detail::DEGREE_TO_RAD;
const double dlong1 = ln1 * detail::DEGREE_TO_RAD;
const double dlat2 = lt2 * detail::DEGREE_TO_RAD;
const double dlong2 = ln2 * detail::DEGREE_TO_RAD;
const double dlong = dlong1 - dlong2;
const double dlat = dlat1 - dlat2;
const double aharv = std::pow(std::sin(dlat / 2.0), 2.0) +
std::cos(dlat1) * std::cos(dlat2) * std::pow(std::sin(dlong / 2.), 2);
const double charv = 2. * std::atan2(std::sqrt(aharv), std::sqrt(1.0 - aharv));
return detail::EARTH_RADIUS * charv;
}
double greatCircleDistance(const Coordinate coordinate_1, const Coordinate coordinate_2)
{
auto lon1 = static_cast<int>(coordinate_1.lon);
auto lat1 = static_cast<int>(coordinate_1.lat);
auto lon2 = static_cast<int>(coordinate_2.lon);
auto lat2 = static_cast<int>(coordinate_2.lat);
BOOST_ASSERT(lat1 != std::numeric_limits<int>::min());
BOOST_ASSERT(lon1 != std::numeric_limits<int>::min());
BOOST_ASSERT(lat2 != std::numeric_limits<int>::min());
BOOST_ASSERT(lon2 != std::numeric_limits<int>::min());
const double float_lat1 = (lat1 / COORDINATE_PRECISION) * detail::DEGREE_TO_RAD;
const double float_lon1 = (lon1 / COORDINATE_PRECISION) * detail::DEGREE_TO_RAD;
const double float_lat2 = (lat2 / COORDINATE_PRECISION) * detail::DEGREE_TO_RAD;
const double float_lon2 = (lon2 / COORDINATE_PRECISION) * detail::DEGREE_TO_RAD;
const double x_value = (float_lon2 - float_lon1) * std::cos((float_lat1 + float_lat2) / 2.0);
const double y_value = float_lat2 - float_lat1;
return std::hypot(x_value, y_value) * detail::EARTH_RADIUS;
}
double perpendicularDistance(const Coordinate segment_source,
const Coordinate segment_target,
const Coordinate query_location,
@@ -153,7 +101,7 @@ double perpendicularDistance(const Coordinate segment_source,
web_mercator::fromWGS84(query_location));
nearest_location = web_mercator::toWGS84(projected_nearest);
const double approximate_distance = fccApproximateDistance(query_location, nearest_location);
const double approximate_distance = greatCircleDistance(query_location, nearest_location);
BOOST_ASSERT(0.0 <= approximate_distance);
return approximate_distance;
}
@@ -179,30 +127,24 @@ Coordinate centroid(const Coordinate lhs, const Coordinate rhs)
return centroid;
}
double bearing(const Coordinate first_coordinate, const Coordinate second_coordinate)
double bearing(const Coordinate coordinate_1, const Coordinate coordinate_2)
{
const double lon_diff =
static_cast<double>(toFloating(second_coordinate.lon - first_coordinate.lon));
const double lon_delta = detail::degToRad(lon_diff);
const double lat1 = detail::degToRad(static_cast<double>(toFloating(first_coordinate.lat)));
const double lat2 = detail::degToRad(static_cast<double>(toFloating(second_coordinate.lat)));
const double y = std::sin(lon_delta) * std::cos(lat2);
const double x =
std::cos(lat1) * std::sin(lat2) - std::sin(lat1) * std::cos(lat2) * std::cos(lon_delta);
double result = detail::radToDeg(std::atan2(y, x));
while (result < 0.0)
const auto lon1 = static_cast<double>(util::toFloating(coordinate_1.lon));
const auto lat1 = static_cast<double>(util::toFloating(coordinate_1.lat));
const auto lon2 = static_cast<double>(util::toFloating(coordinate_2.lon));
const auto lat2 = static_cast<double>(util::toFloating(coordinate_2.lat));
const auto &ruler = cheap_ruler_container.getRuler(coordinate_1.lat, coordinate_2.lat);
auto result = ruler.bearing({lon1, lat1}, {lon2, lat2});
if (result < 0.0)
{
result += 360.0;
}
BOOST_ASSERT(0 <= result && result <= 360);
while (result >= 360.0)
{
result -= 360.0;
}
// If someone gives us two identical coordinates, then the concept of a bearing
// makes no sense. However, because it sometimes happens, we'll at least
// return a consistent value of 0 so that the behaviour isn't random.
BOOST_ASSERT(first_coordinate != second_coordinate || result == 0.);
BOOST_ASSERT(coordinate_1 != coordinate_2 || result == 0.);
return result;
}
@@ -322,7 +264,7 @@ double circleRadius(const Coordinate C1, const Coordinate C2, const Coordinate C
// a circle by three points requires thee distinct points
auto center = circleCenter(C1, C2, C3);
if (center)
return haversineDistance(C1, *center);
return greatCircleDistance(C1, *center);
else
return std::numeric_limits<double>::infinity();
}
@@ -372,8 +314,8 @@ double findClosestDistance(const Coordinate coordinate,
const Coordinate segment_begin,
const Coordinate segment_end)
{
return haversineDistance(coordinate,
projectPointOnSegment(segment_begin, segment_end, coordinate).second);
return greatCircleDistance(
coordinate, projectPointOnSegment(segment_begin, segment_end, coordinate).second);
}
// find the closes distance between two sets of coordinates
@@ -437,7 +379,7 @@ Coordinate difference(const Coordinate lhs, const Coordinate rhs)
double computeArea(const std::vector<Coordinate> &polygon)
{
using util::coordinate_calculation::haversineDistance;
using util::coordinate_calculation::greatCircleDistance;
if (polygon.empty())
return 0.;
@@ -458,15 +400,15 @@ double computeArea(const std::vector<Coordinate> &polygon)
double area = 0.;
auto first = polygon.begin();
auto previous_base = util::Coordinate{first->lon, ref_latitude};
auto previous_y = haversineDistance(previous_base, *first);
auto previous_y = greatCircleDistance(previous_base, *first);
for (++first; first != polygon.end(); ++first)
{
BOOST_ASSERT(first->lat >= ref_latitude);
const auto current_base = util::Coordinate{first->lon, ref_latitude};
const auto current_y = haversineDistance(current_base, *first);
const auto current_y = greatCircleDistance(current_base, *first);
const auto chunk_area =
haversineDistance(previous_base, current_base) * (previous_y + current_y);
greatCircleDistance(previous_base, current_base) * (previous_y + current_y);
area += (current_base.lon >= previous_base.lon) ? chunk_area : -chunk_area;