271 lines
9.0 KiB
C++
271 lines
9.0 KiB
C++
#include "util/coordinate_calculation.hpp"
|
|
|
|
#include "util/string_util.hpp"
|
|
#include "util/trigonometry_table.hpp"
|
|
|
|
#include <boost/assert.hpp>
|
|
#include <boost/math/constants/constants.hpp>
|
|
|
|
#include <cmath>
|
|
|
|
#include <limits>
|
|
|
|
namespace osrm
|
|
{
|
|
namespace util
|
|
{
|
|
namespace coordinate_calculation
|
|
{
|
|
|
|
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 * (RAD);
|
|
|
|
const double dlong1 = ln1 * (RAD);
|
|
const double dlat2 = lt2 * (RAD);
|
|
const double dlong2 = ln2 * (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 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) * RAD;
|
|
const double float_lon1 = (lon1 / COORDINATE_PRECISION) * RAD;
|
|
const double float_lat2 = (lat2 / COORDINATE_PRECISION) * RAD;
|
|
const double float_lon2 = (lon2 / COORDINATE_PRECISION) * 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) * EARTH_RADIUS;
|
|
}
|
|
|
|
double perpendicularDistance(const Coordinate source_coordinate,
|
|
const Coordinate target_coordinate,
|
|
const Coordinate query_location)
|
|
{
|
|
double ratio;
|
|
Coordinate nearest_location;
|
|
|
|
return perpendicularDistance(source_coordinate, target_coordinate, query_location,
|
|
nearest_location, ratio);
|
|
}
|
|
|
|
double perpendicularDistance(const Coordinate segment_source,
|
|
const Coordinate segment_target,
|
|
const Coordinate query_location,
|
|
Coordinate &nearest_location,
|
|
double &ratio)
|
|
{
|
|
using namespace coordinate_calculation;
|
|
|
|
return perpendicularDistanceFromProjectedCoordinate(
|
|
segment_source, segment_target, query_location,
|
|
{static_cast<double>(toFloating(query_location.lon)),
|
|
mercator::latToY(toFloating(query_location.lat))},
|
|
nearest_location, ratio);
|
|
}
|
|
|
|
double perpendicularDistanceFromProjectedCoordinate(
|
|
const Coordinate source_coordinate,
|
|
const Coordinate target_coordinate,
|
|
const Coordinate query_location,
|
|
const std::pair<double, double> projected_xy_coordinate)
|
|
{
|
|
double ratio;
|
|
Coordinate nearest_location;
|
|
|
|
return perpendicularDistanceFromProjectedCoordinate(source_coordinate, target_coordinate,
|
|
query_location, projected_xy_coordinate,
|
|
nearest_location, ratio);
|
|
}
|
|
|
|
double perpendicularDistanceFromProjectedCoordinate(
|
|
const Coordinate segment_source,
|
|
const Coordinate segment_target,
|
|
const Coordinate query_location,
|
|
const std::pair<double, double> projected_xy_coordinate,
|
|
Coordinate &nearest_location,
|
|
double &ratio)
|
|
{
|
|
using namespace coordinate_calculation;
|
|
|
|
BOOST_ASSERT(query_location.IsValid());
|
|
|
|
// initialize values
|
|
const double x = projected_xy_coordinate.first;
|
|
const double y = projected_xy_coordinate.second;
|
|
const double a = mercator::latToY(toFloating(segment_source.lat));
|
|
const double b = static_cast<double>(toFloating(segment_source.lon));
|
|
const double c = mercator::latToY(toFloating(segment_target.lat));
|
|
const double d = static_cast<double>(toFloating(segment_target.lon));
|
|
double p, q /*,mX*/, new_y;
|
|
if (std::abs(a - c) > std::numeric_limits<double>::epsilon())
|
|
{
|
|
const double m = (d - b) / (c - a); // slope
|
|
// Projection of (x,y) on line joining (a,b) and (c,d)
|
|
p = ((x + (m * y)) + (m * m * a - m * b)) / (1.0 + m * m);
|
|
q = b + m * (p - a);
|
|
}
|
|
else
|
|
{
|
|
p = c;
|
|
q = y;
|
|
}
|
|
new_y = (d * p - c * q) / (a * d - b * c);
|
|
|
|
// discretize the result to coordinate precision. it's a hack!
|
|
if (std::abs(new_y) < (1.0 / COORDINATE_PRECISION))
|
|
{
|
|
new_y = 0.0;
|
|
}
|
|
|
|
// compute ratio
|
|
ratio = static_cast<double>((p - new_y * a) /
|
|
c); // These values are actually n/m+n and m/m+n , we need
|
|
// not calculate the explicit values of m an n as we
|
|
// are just interested in the ratio
|
|
if (std::isnan(ratio))
|
|
{
|
|
ratio = (segment_target == query_location ? 1.0 : 0.0);
|
|
}
|
|
else if (std::abs(ratio) <= std::numeric_limits<double>::epsilon())
|
|
{
|
|
ratio = 0.0;
|
|
}
|
|
else if (std::abs(ratio - 1.0) <= std::numeric_limits<double>::epsilon())
|
|
{
|
|
ratio = 1.0;
|
|
}
|
|
|
|
// compute nearest location
|
|
BOOST_ASSERT(!std::isnan(ratio));
|
|
if (ratio <= 0.0)
|
|
{
|
|
nearest_location = segment_source;
|
|
}
|
|
else if (ratio >= 1.0)
|
|
{
|
|
nearest_location = segment_target;
|
|
}
|
|
else
|
|
{
|
|
// point lies in between
|
|
nearest_location.lon = toFixed(FloatLongitude(q));
|
|
nearest_location.lat = toFixed(FloatLatitude(mercator::yToLat(p)));
|
|
}
|
|
BOOST_ASSERT(nearest_location.IsValid());
|
|
|
|
const double approximate_distance = greatCircleDistance(query_location, nearest_location);
|
|
BOOST_ASSERT(0.0 <= approximate_distance);
|
|
return approximate_distance;
|
|
}
|
|
|
|
double degToRad(const double degree)
|
|
{
|
|
using namespace boost::math::constants;
|
|
return degree * (pi<double>() / 180.0);
|
|
}
|
|
|
|
double radToDeg(const double radian)
|
|
{
|
|
using namespace boost::math::constants;
|
|
return radian * (180.0 * (1. / pi<double>()));
|
|
}
|
|
|
|
double bearing(const Coordinate first_coordinate, const Coordinate second_coordinate)
|
|
{
|
|
const double lon_diff =
|
|
static_cast<double>(toFloating(second_coordinate.lon - first_coordinate.lon));
|
|
const double lon_delta = degToRad(lon_diff);
|
|
const double lat1 = degToRad(static_cast<double>(toFloating(first_coordinate.lat)));
|
|
const double lat2 = 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 = radToDeg(std::atan2(y, x));
|
|
while (result < 0.0)
|
|
{
|
|
result += 360.0;
|
|
}
|
|
|
|
while (result >= 360.0)
|
|
{
|
|
result -= 360.0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double computeAngle(const Coordinate first, const Coordinate second, const Coordinate third)
|
|
{
|
|
using namespace boost::math::constants;
|
|
using namespace coordinate_calculation;
|
|
|
|
const double v1x = static_cast<double>(toFloating(first.lon - second.lon));
|
|
const double v1y =
|
|
mercator::latToY(toFloating(first.lat)) - mercator::latToY(toFloating(second.lat));
|
|
const double v2x = static_cast<double>(toFloating(third.lon - second.lon));
|
|
const double v2y =
|
|
mercator::latToY(toFloating(third.lat)) - mercator::latToY(toFloating(second.lat));
|
|
|
|
double angle = (atan2_lookup(v2y, v2x) - atan2_lookup(v1y, v1x)) * 180. / pi<double>();
|
|
|
|
while (angle < 0.)
|
|
{
|
|
angle += 360.;
|
|
}
|
|
|
|
return angle;
|
|
}
|
|
|
|
namespace mercator
|
|
{
|
|
FloatLatitude yToLat(const double value)
|
|
{
|
|
using namespace boost::math::constants;
|
|
|
|
return FloatLatitude(
|
|
180. * (1. / pi<long double>()) *
|
|
(2. * std::atan(std::exp(value * pi<double>() / 180.)) - half_pi<double>()));
|
|
}
|
|
|
|
double latToY(const FloatLatitude latitude)
|
|
{
|
|
using namespace boost::math::constants;
|
|
|
|
return 180. * (1. / pi<double>()) *
|
|
std::log(std::tan((pi<double>() / 4.) +
|
|
static_cast<double>(latitude) * (pi<double>() / 180.) / 2.));
|
|
}
|
|
} // ns mercato // ns mercatorr
|
|
} // ns coordinate_calculation
|
|
} // ns util
|
|
} // ns osrm
|