-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
coordinate_calculation.hpp
387 lines (321 loc) · 16 KB
/
coordinate_calculation.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
#ifndef COORDINATE_CALCULATION
#define COORDINATE_CALCULATION
#include "util/coordinate.hpp"
#include <boost/math/constants/constants.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <utility>
#include <vector>
namespace osrm
{
namespace util
{
namespace coordinate_calculation
{
namespace detail
{
const constexpr long double DEGREE_TO_RAD = 0.017453292519943295769236907684886;
const constexpr long 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)
{
using namespace boost::math::constants;
return degree * (pi<double>() / 180.0);
}
inline double radToDeg(const double radian)
{
using namespace boost::math::constants;
return radian * (180.0 * (1. / pi<double>()));
}
}
//! Takes the squared euclidean distance of the input coordinates. Does not return meters!
std::uint64_t squaredEuclideanDistance(const Coordinate lhs, const Coordinate rhs);
double haversineDistance(const Coordinate first_coordinate, const Coordinate second_coordinate);
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
boost::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
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;
};
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);
} // ns coordinate_calculation
} // ns util
} // ns osrm
#endif // COORDINATE_CALCULATION