386 lines
16 KiB
C++
386 lines
16 KiB
C++
#ifndef COORDINATE_CALCULATION
|
|
#define COORDINATE_CALCULATION
|
|
|
|
#include "util/coordinate.hpp"
|
|
|
|
#include <numbers>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <numeric>
|
|
#include <optional>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace osrm::util::coordinate_calculation
|
|
{
|
|
|
|
namespace detail
|
|
{
|
|
const constexpr double DEGREE_TO_RAD = 0.017453292519943295769236907684886;
|
|
const constexpr double RAD_TO_DEGREE = 1. / DEGREE_TO_RAD;
|
|
// earth radius varies between 6,356.750-6,378.135 km (3,949.901-3,963.189mi)
|
|
// The IUGG value for the equatorial radius is 6378.137 km (3963.19 miles)
|
|
const constexpr long double EARTH_RADIUS = 6372797.560856;
|
|
|
|
inline double degToRad(const double degree) { return degree * (std::numbers::pi / 180.0); }
|
|
|
|
inline double radToDeg(const double radian) { return radian * (180.0 * std::numbers::inv_pi); }
|
|
} // namespace detail
|
|
|
|
const constexpr static double METERS_PER_DEGREE_LAT = 110567.0;
|
|
|
|
inline double metersPerLngDegree(const FixedLatitude lat)
|
|
{
|
|
return std::cos(detail::degToRad(static_cast<double>(toFloating(lat)))) * METERS_PER_DEGREE_LAT;
|
|
}
|
|
|
|
//! Takes the squared euclidean distance of the input coordinates. Does not return meters!
|
|
std::uint64_t squaredEuclideanDistance(const Coordinate lhs, const Coordinate rhs);
|
|
|
|
double greatCircleDistance(const Coordinate first_coordinate, const Coordinate second_coordinate);
|
|
|
|
// get the length of a full coordinate vector, using one of our basic functions to compute distances
|
|
template <class BinaryOperation, typename iterator_type>
|
|
double getLength(iterator_type begin, const iterator_type end, BinaryOperation op);
|
|
|
|
// Find the closest distance and location between coordinate and the line connecting source and
|
|
// target:
|
|
// coordinate
|
|
// |
|
|
// |
|
|
// source -------- x -------- target.
|
|
// returns x as well as the distance between source and x as ratio ([0,1])
|
|
inline std::pair<double, FloatCoordinate> projectPointOnSegment(const FloatCoordinate &source,
|
|
const FloatCoordinate &target,
|
|
const FloatCoordinate &coordinate);
|
|
|
|
// find the closest distance between a coordinate and a segment
|
|
// O(1)
|
|
double findClosestDistance(const Coordinate coordinate,
|
|
const Coordinate segment_begin,
|
|
const Coordinate segment_end);
|
|
|
|
// find the closest distance between a coordinate and a set of coordinates
|
|
// O(|coordinates|)
|
|
template <typename iterator_type>
|
|
double findClosestDistance(const Coordinate coordinate,
|
|
const iterator_type begin,
|
|
const iterator_type end);
|
|
|
|
// find the closes distance between two sets of coordinates
|
|
// O(|lhs| * |rhs|)
|
|
template <typename iterator_type>
|
|
double findClosestDistance(const iterator_type lhs_begin,
|
|
const iterator_type lhs_end,
|
|
const iterator_type rhs_begin,
|
|
const iterator_type rhs_end);
|
|
|
|
// checks if two sets of coordinates describe a parallel set of ways
|
|
template <typename iterator_type>
|
|
bool areParallel(const iterator_type lhs_begin,
|
|
const iterator_type lhs_end,
|
|
const iterator_type rhs_begin,
|
|
const iterator_type rhs_end);
|
|
|
|
double perpendicularDistance(const Coordinate segment_source,
|
|
const Coordinate segment_target,
|
|
const Coordinate query_location);
|
|
|
|
double perpendicularDistance(const Coordinate segment_source,
|
|
const Coordinate segment_target,
|
|
const Coordinate query_location,
|
|
Coordinate &nearest_location,
|
|
double &ratio);
|
|
|
|
Coordinate centroid(const Coordinate lhs, const Coordinate rhs);
|
|
|
|
double bearing(const Coordinate first_coordinate, const Coordinate second_coordinate);
|
|
|
|
// Get angle of line segment (A,C)->(C,B)
|
|
double computeAngle(const Coordinate first, const Coordinate second, const Coordinate third);
|
|
|
|
// find the center of a circle through three coordinates
|
|
std::optional<Coordinate> circleCenter(const Coordinate first_coordinate,
|
|
const Coordinate second_coordinate,
|
|
const Coordinate third_coordinate);
|
|
|
|
// find the radius of a circle through three coordinates
|
|
double circleRadius(const Coordinate first_coordinate,
|
|
const Coordinate second_coordinate,
|
|
const Coordinate third_coordinate);
|
|
|
|
// factor in [0,1]. Returns point along the straight line between from and to. 0 returns from, 1
|
|
// returns to
|
|
Coordinate interpolateLinear(double factor, const Coordinate from, const Coordinate to);
|
|
|
|
// compute the signed area of a triangle
|
|
double signedArea(const Coordinate first_coordinate,
|
|
const Coordinate second_coordinate,
|
|
const Coordinate third_coordinate);
|
|
|
|
// check if a set of three coordinates is given in CCW order
|
|
bool isCCW(const Coordinate first_coordinate,
|
|
const Coordinate second_coordinate,
|
|
const Coordinate third_coordinate);
|
|
|
|
template <typename iterator_type>
|
|
std::pair<Coordinate, Coordinate> leastSquareRegression(const iterator_type begin,
|
|
const iterator_type end);
|
|
|
|
// rotates a coordinate around the point (0,0). This function can be used to normalise a few
|
|
// computations around regression vectors
|
|
Coordinate rotateCCWAroundZero(Coordinate coordinate, double angle_in_radians);
|
|
|
|
// compute the difference vector of two coordinates lhs - rhs
|
|
Coordinate difference(const Coordinate lhs, const Coordinate rhs);
|
|
|
|
// TEMPLATE/INLINE DEFINITIONS
|
|
inline std::pair<double, FloatCoordinate> projectPointOnSegment(const FloatCoordinate &source,
|
|
const FloatCoordinate &target,
|
|
const FloatCoordinate &coordinate)
|
|
{
|
|
const FloatCoordinate slope_vector{target.lon - source.lon, target.lat - source.lat};
|
|
const FloatCoordinate rel_coordinate{coordinate.lon - source.lon, coordinate.lat - source.lat};
|
|
// dot product of two un-normed vectors
|
|
const auto unnormed_ratio = static_cast<double>(slope_vector.lon * rel_coordinate.lon) +
|
|
static_cast<double>(slope_vector.lat * rel_coordinate.lat);
|
|
// squared length of the slope vector
|
|
const auto squared_length = static_cast<double>(slope_vector.lon * slope_vector.lon) +
|
|
static_cast<double>(slope_vector.lat * slope_vector.lat);
|
|
|
|
if (squared_length < std::numeric_limits<double>::epsilon())
|
|
{
|
|
return {0, source};
|
|
}
|
|
|
|
const double normed_ratio = unnormed_ratio / squared_length;
|
|
double clamped_ratio = normed_ratio;
|
|
if (clamped_ratio > 1.)
|
|
{
|
|
clamped_ratio = 1.;
|
|
}
|
|
else if (clamped_ratio < 0.)
|
|
{
|
|
clamped_ratio = 0.;
|
|
}
|
|
|
|
return {clamped_ratio,
|
|
{
|
|
FloatLongitude{1.0 - clamped_ratio} * source.lon +
|
|
target.lon * FloatLongitude{clamped_ratio},
|
|
FloatLatitude{1.0 - clamped_ratio} * source.lat +
|
|
target.lat * FloatLatitude{clamped_ratio},
|
|
}};
|
|
}
|
|
|
|
template <class BinaryOperation, typename iterator_type>
|
|
double getLength(iterator_type begin, const iterator_type end, BinaryOperation op)
|
|
{
|
|
double result = 0;
|
|
const auto functor = [&result, op](const Coordinate lhs, const Coordinate rhs)
|
|
{
|
|
result += op(lhs, rhs);
|
|
return false;
|
|
};
|
|
// side-effect find adding up distances
|
|
// Ignore return value, we are only interested in the side-effect
|
|
[[maybe_unused]] auto _ = std::adjacent_find(begin, end, functor);
|
|
|
|
return result;
|
|
}
|
|
|
|
template <typename iterator_type>
|
|
double
|
|
findClosestDistance(const Coordinate coordinate, const iterator_type begin, const iterator_type end)
|
|
{
|
|
double current_min = std::numeric_limits<double>::max();
|
|
|
|
// comparator updating current_min without ever finding an element
|
|
const auto compute_minimum_distance =
|
|
[¤t_min, coordinate](const Coordinate lhs, const Coordinate rhs)
|
|
{
|
|
current_min = std::min(current_min, findClosestDistance(coordinate, lhs, rhs));
|
|
return false;
|
|
};
|
|
|
|
// Ignore return value, we are only interested in the side-effect
|
|
[[maybe_unused]] auto _ = std::adjacent_find(begin, end, compute_minimum_distance);
|
|
return current_min;
|
|
}
|
|
|
|
template <typename iterator_type>
|
|
double findClosestDistance(const iterator_type lhs_begin,
|
|
const iterator_type lhs_end,
|
|
const iterator_type rhs_begin,
|
|
const iterator_type rhs_end)
|
|
{
|
|
double current_min = std::numeric_limits<double>::max();
|
|
|
|
const auto compute_minimum_distance_in_rhs =
|
|
[¤t_min, rhs_begin, rhs_end](const Coordinate coordinate)
|
|
{
|
|
current_min = std::min(current_min, findClosestDistance(coordinate, rhs_begin, rhs_end));
|
|
return false;
|
|
};
|
|
|
|
std::find_if(lhs_begin, lhs_end, compute_minimum_distance_in_rhs);
|
|
return current_min;
|
|
}
|
|
|
|
template <typename iterator_type>
|
|
std::pair<Coordinate, Coordinate> leastSquareRegression(const iterator_type begin,
|
|
const iterator_type end)
|
|
{
|
|
// following the formulas of https://faculty.elgin.edu/dkernler/statistics/ch04/4-2.html
|
|
const auto number_of_coordinates = std::distance(begin, end);
|
|
BOOST_ASSERT(number_of_coordinates >= 2);
|
|
const auto extract_lon = [](const Coordinate coordinate)
|
|
{ return static_cast<double>(toFloating(coordinate.lon)); };
|
|
|
|
const auto extract_lat = [](const Coordinate coordinate)
|
|
{ return static_cast<double>(toFloating(coordinate.lat)); };
|
|
|
|
double min_lon = extract_lon(*begin);
|
|
double max_lon = extract_lon(*begin);
|
|
double min_lat = extract_lat(*begin);
|
|
double max_lat = extract_lat(*begin);
|
|
|
|
for (auto coordinate_iterator = begin; coordinate_iterator != end; ++coordinate_iterator)
|
|
{
|
|
const auto c = *coordinate_iterator;
|
|
const auto lon = extract_lon(c);
|
|
min_lon = std::min(min_lon, lon);
|
|
max_lon = std::max(max_lon, lon);
|
|
const auto lat = extract_lat(c);
|
|
min_lat = std::min(min_lat, lat);
|
|
max_lat = std::max(max_lat, lat);
|
|
}
|
|
// very small difference in longitude -> would result in inaccurate calculation, check if lat is
|
|
// better
|
|
if ((max_lat - min_lat) > 2 * (max_lon - min_lon))
|
|
{
|
|
std::vector<util::Coordinate> rotated_coordinates(number_of_coordinates);
|
|
// rotate all coordinates to the right
|
|
std::transform(begin,
|
|
end,
|
|
rotated_coordinates.begin(),
|
|
[](const auto coordinate)
|
|
{ return rotateCCWAroundZero(coordinate, detail::degToRad(-90)); });
|
|
const auto rotated_regression =
|
|
leastSquareRegression(rotated_coordinates.begin(), rotated_coordinates.end());
|
|
return {rotateCCWAroundZero(rotated_regression.first, detail::degToRad(90)),
|
|
rotateCCWAroundZero(rotated_regression.second, detail::degToRad(90))};
|
|
}
|
|
|
|
const auto make_accumulate = [](const auto extraction_function)
|
|
{
|
|
return [extraction_function](const double sum_so_far, const Coordinate coordinate)
|
|
{ return sum_so_far + extraction_function(coordinate); };
|
|
};
|
|
|
|
const auto accumulated_lon = std::accumulate(begin, end, 0., make_accumulate(extract_lon));
|
|
|
|
const auto accumulated_lat = std::accumulate(begin, end, 0., make_accumulate(extract_lat));
|
|
|
|
const auto mean_lon = accumulated_lon / number_of_coordinates;
|
|
const auto mean_lat = accumulated_lat / number_of_coordinates;
|
|
const auto make_variance = [](const auto mean, const auto extraction_function)
|
|
{
|
|
return [extraction_function, mean](const double sum_so_far, const Coordinate coordinate)
|
|
{
|
|
const auto difference = extraction_function(coordinate) - mean;
|
|
return sum_so_far + difference * difference;
|
|
};
|
|
};
|
|
|
|
// using the unbiased version, we divide by num_samples - 1 (see
|
|
// http://mathworld.wolfram.com/SampleVariance.html)
|
|
const auto sample_variance_lon =
|
|
std::sqrt(std::accumulate(begin, end, 0., make_variance(mean_lon, extract_lon)) /
|
|
(number_of_coordinates - 1));
|
|
|
|
// if we don't change longitude, return the vertical line as is
|
|
if (std::abs(sample_variance_lon) <
|
|
std::numeric_limits<decltype(sample_variance_lon)>::epsilon())
|
|
return {*begin, *(end - 1)};
|
|
|
|
const auto sample_variance_lat =
|
|
std::sqrt(std::accumulate(begin, end, 0., make_variance(mean_lat, extract_lat)) /
|
|
(number_of_coordinates - 1));
|
|
|
|
if (std::abs(sample_variance_lat) <
|
|
std::numeric_limits<decltype(sample_variance_lat)>::epsilon())
|
|
return {*begin, *(end - 1)};
|
|
const auto linear_correlation =
|
|
std::accumulate(begin,
|
|
end,
|
|
0.,
|
|
[&](const auto sum_so_far, const auto current_coordinate)
|
|
{
|
|
return sum_so_far + (extract_lon(current_coordinate) - mean_lon) *
|
|
(extract_lat(current_coordinate) - mean_lat) /
|
|
(sample_variance_lon * sample_variance_lat);
|
|
}) /
|
|
(number_of_coordinates - 1);
|
|
|
|
const auto slope = linear_correlation * sample_variance_lat / sample_variance_lon;
|
|
const auto intercept = mean_lat - slope * mean_lon;
|
|
|
|
const auto GetLatAtLon = [intercept,
|
|
slope](const util::FloatLongitude longitude) -> util::FloatLatitude
|
|
{ return {intercept + slope * static_cast<double>((longitude))}; };
|
|
|
|
const double offset = 0.00001;
|
|
const Coordinate regression_first = {
|
|
toFixed(util::FloatLongitude{min_lon - offset}),
|
|
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{min_lon - offset})))};
|
|
const Coordinate regression_end = {
|
|
toFixed(util::FloatLongitude{max_lon + offset}),
|
|
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{max_lon + offset})))};
|
|
|
|
return {regression_first, regression_end};
|
|
}
|
|
|
|
template <typename iterator_type>
|
|
bool areParallel(const iterator_type lhs_begin,
|
|
const iterator_type lhs_end,
|
|
const iterator_type rhs_begin,
|
|
const iterator_type rhs_end)
|
|
{
|
|
const auto regression_lhs = leastSquareRegression(lhs_begin, lhs_end);
|
|
const auto regression_rhs = leastSquareRegression(rhs_begin, rhs_end);
|
|
|
|
const auto null_island = Coordinate(FixedLongitude{0}, FixedLatitude{0});
|
|
const auto difference_lhs = difference(regression_lhs.first, regression_lhs.second);
|
|
const auto difference_rhs = difference(regression_rhs.first, regression_rhs.second);
|
|
|
|
// we normalise the left slope to be zero, so we rotate the coordinates around 0,0 to match 90
|
|
// degrees
|
|
const auto bearing_lhs = bearing(null_island, difference_lhs);
|
|
|
|
// we rotate to have one of the lines facing horizontally to the right (bearing 90 degree)
|
|
const auto rotation_angle_radians = detail::degToRad(bearing_lhs - 90);
|
|
const auto rotated_difference_rhs = rotateCCWAroundZero(difference_rhs, rotation_angle_radians);
|
|
|
|
const auto get_slope = [](const Coordinate from, const Coordinate to)
|
|
{
|
|
const auto diff_lat = static_cast<int>(from.lat) - static_cast<int>(to.lat);
|
|
const auto diff_lon = static_cast<int>(from.lon) - static_cast<int>(to.lon);
|
|
if (diff_lon == 0)
|
|
return std::numeric_limits<double>::max();
|
|
return static_cast<double>(diff_lat) / static_cast<double>(diff_lon);
|
|
};
|
|
|
|
const auto slope_rhs = get_slope(null_island, rotated_difference_rhs);
|
|
// the left hand side has a slope of `0` after the rotation. We can check the slope of the right
|
|
// hand side to ensure we only considering slight slopes
|
|
return std::abs(slope_rhs) < 0.20; // twenty percent incline at the most
|
|
}
|
|
|
|
double computeArea(const std::vector<Coordinate> &polygon);
|
|
|
|
} // namespace osrm::util::coordinate_calculation
|
|
|
|
#endif // COORDINATE_CALCULATION
|