Skip to content

Commit 91d63c3

Browse files
committed
AP_Math: correct Polygon_closest_distance_point() for use with lat/lng at small distances
add polygon proximity tests
1 parent 4957895 commit 91d63c3

File tree

4 files changed

+148
-16
lines changed

4 files changed

+148
-16
lines changed

libraries/AP_Math/polygon.cpp

+87-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
#include "AP_Math.h"
20+
#include "float.h"
2021

2122
#pragma GCC optimize("O2")
2223

@@ -124,7 +125,6 @@ template bool Polygon_complete<int32_t>(const Vector2l *V, unsigned n);
124125
template bool Polygon_outside<float>(const Vector2f &P, const Vector2f *V, unsigned n);
125126
template bool Polygon_complete<float>(const Vector2f *V, unsigned n);
126127

127-
128128
/*
129129
determine if the polygon of N verticies defined by points V is
130130
intersected by a line from point p1 to point p2
@@ -196,21 +196,96 @@ float Polygon_closest_distance_line(const Vector2f *V, unsigned N, const Vector2
196196
return sqrtf(closest_sq);
197197
}
198198

199+
const ftype EARTH_RADIUS_METERS = 6378137.0; // Earth's radius in meters (WGS84)
200+
201+
// Converts 1e7 degrees to radians
202+
static inline ftype to_radians(ftype degrees_1e7)
203+
{
204+
return radians(degrees_1e7 / 1.0e7);
205+
}
206+
207+
// Converts (latitude, longitude) in radians to 3D Cartesian coordinates on a unit sphere
208+
static void latlng_to_cartesian(ftype latRad, ftype lonRad, Vector3F& v)
209+
{
210+
v.x = cosF(latRad) * cosF(lonRad);
211+
v.y = cosF(latRad) * sinF(lonRad);
212+
v.z = sinF(latRad);
213+
}
214+
215+
// Haversine distance between two points given in 1e7 degrees
216+
static ftype haversine(long lat1_1e7, long lon1_1e7, long lat2_1e7, long lon2_1e7)
217+
{
218+
ftype lat1 = to_radians(lat1_1e7);
219+
ftype lat2 = to_radians(lat2_1e7);
220+
221+
ftype dLat = to_radians(lat2_1e7 - lat1_1e7);
222+
ftype dLon = to_radians(lon2_1e7 - lon1_1e7);
223+
224+
// (d/2r)^2
225+
ftype a = dLat * dLat / 4 + cosF(lat2) * cosF(lat1) * dLon * dLon / 4;
226+
ftype d = sqrtF(a) * 2 * EARTH_RADIUS_METERS;
227+
228+
return d;
229+
}
230+
231+
// Compute closest distance from a point to a line segment on a sphere
232+
static ftype closest_distance_to_segment(const Vector2l& point, const Vector2l& line_start, const Vector2l& line_end)
233+
{
234+
// Convert lat/lon to 3D Cartesian coordinates on a unit sphere
235+
Vector3F v1, v2, vp;
236+
latlng_to_cartesian(to_radians(line_start.x), to_radians(line_start.y), v1);
237+
latlng_to_cartesian(to_radians(line_end.x), to_radians(line_end.y), v2);
238+
latlng_to_cartesian(to_radians(point.x), to_radians(point.y), vp);
239+
240+
// Compute vector from lineStart to lineEnd (v) and from lineStart to point (w)
241+
Vector3F v = v2 - v1;
242+
Vector3F w = vp - v1;
243+
244+
// Project w onto v
245+
ftype dot_vw = v * w;
246+
ftype dot_vv = v * v;
247+
ftype t = dot_vw / dot_vv;
248+
249+
Vector3F closest;
250+
if (t < 0) {
251+
// Closest to line start
252+
closest = v1;
253+
} else if (t > 1) {
254+
// Closest to line end
255+
closest = v2;
256+
} else {
257+
// Closest point is on the line
258+
closest = v1 + v * t;
259+
}
260+
261+
// Convert closest point back to lat/lon (reverse cartesian)
262+
ftype norm = closest.length();
263+
ftype closestLat = asinF(closest.z / norm); // Latitude from z-coordinate
264+
ftype closestLon = atan2F(closest.y, closest.x); // Longitude from x and y
265+
266+
// Convert back to 1e7 degrees
267+
long closestLat_1e7 = static_cast<long>(degrees(closestLat) * 1e7);
268+
long closestLon_1e7 = static_cast<long>(degrees(closestLon) * 1e7);
269+
270+
// Calculate the Haversine distance from the point to the closest point on the line
271+
return haversine(point.x, point.y, closestLat_1e7, closestLon_1e7);
272+
}
273+
199274
/*
200-
return the closest distance that point p comes to an edge of closed
201-
polygon V, defined by N points
275+
return the closest distance in meters that point p in lat/lng 1e7 comes to an edge of closed
276+
polygon V, defined by N points of lat/lng 1e7
202277
*/
203-
float Polygon_closest_distance_point(const Vector2f *V, unsigned N, const Vector2f &p)
278+
ftype Polygon_closest_distance_point(const Vector2l *V, unsigned N, const Vector2l &p)
204279
{
205-
float closest_sq = FLT_MAX;
206-
for (uint8_t i=0; i<N-1; i++) {
207-
const Vector2f &v1 = V[i];
208-
const Vector2f &v2 = V[i+1];
280+
ftype closest = std::numeric_limits<ftype>::max();
281+
for (uint8_t i=0; i<N; i++) {
282+
const Vector2l &v1 = V[i];
283+
const Vector2l &v2 = V[(i+1) % N];
209284

210-
float dist_sq = Vector2f::closest_distance_between_line_and_point_squared(v1, v2, p);
211-
if (dist_sq < closest_sq) {
212-
closest_sq = dist_sq;
285+
ftype distance = closest_distance_to_segment(p, v1, v2);
286+
if (distance < closest) {
287+
closest = distance;
213288
}
214289
}
215-
return sqrtf(closest_sq);
290+
return closest;
216291
}

libraries/AP_Math/polygon.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ bool Polygon_intersects(const Vector2f *V, unsigned N, const Vector2f &p1, const
4040
float Polygon_closest_distance_line(const Vector2f *V, unsigned N, const Vector2f &p1, const Vector2f &p2);
4141

4242
/*
43-
return the closest distance that a point p comes to an edge of
44-
closed polygon V, defined by N points
43+
return the closest distance that a point p in lat/lng comes to an edge of
44+
closed polygon V, defined by N points of lat/lng
4545
*/
46-
float Polygon_closest_distance_point(const Vector2f *V, unsigned N, const Vector2f &p);
46+
ftype Polygon_closest_distance_point(const Vector2l *V, unsigned N, const Vector2l &p);

libraries/AP_Math/tests/test_polygon.cpp

+57
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,63 @@ TEST(Polygon, obc)
304304
TEST_POLYGON_POINTS(OBC_boundary, OBC_test_points);
305305
}
306306

307+
#define TEST_POLYGON_DISTANCE_POINTS(POLYGON, TEST_POINTS) \
308+
do { \
309+
for (uint32_t i = 0; i < ARRAY_SIZE(TEST_POINTS); i++) { \
310+
EXPECT_TRUE(fabsF(TEST_POINTS[i].distance - \
311+
Polygon_closest_distance_point(POLYGON, \
312+
ARRAY_SIZE(POLYGON),\
313+
TEST_POINTS[i].point)) <= 1.0f); \
314+
} \
315+
} while(0)
316+
317+
// Center of London (Charing Cross)
318+
const int32_t CENTER_LAT = static_cast<int32_t>(51.5085 * 1E7); // 515085000
319+
const int32_t CENTER_LON = static_cast<int32_t>(-0.1257 * 1E7); // -1257000
320+
321+
const int32_t CENTER_NORTH_LAT = static_cast<int32_t>((51.5085 + 0.0003366)* 1E7); // 37.5m from edge
322+
const int32_t CENTER_EAST_LON = static_cast<int32_t>((-0.1257 + 0.0005388)* 1E7); // 37.5m from edge
323+
const int32_t CENTER_SOUTH_LAT = static_cast<int32_t>((51.5085 - 0.0003366) * 1E7); // 37.5m from edge
324+
const int32_t CENTER_WEST_LON = static_cast<int32_t>((-0.1257 - 0.0005388) * 1E7); // 37.5m from edge
325+
326+
// Bounding box coordinates (in 1E7 degrees)
327+
const int32_t NORTH_WEST_LAT = static_cast<int32_t>((51.5085 + 0.0006732) * 1E7); // 515092732
328+
const int32_t NORTH_WEST_LON = static_cast<int32_t>((-0.1257 - 0.0010776) * 1E7); // -1267776
329+
330+
const int32_t NORTH_EAST_LAT = static_cast<int32_t>((51.5085 + 0.0006732) * 1E7); // 515092732
331+
const int32_t NORTH_EAST_LON = static_cast<int32_t>((-0.1257 + 0.0010776) * 1E7); // -1246224
332+
333+
const int32_t SOUTH_WEST_LAT = static_cast<int32_t>((51.5085 - 0.0006732) * 1E7); // 515080268
334+
const int32_t SOUTH_WEST_LON = static_cast<int32_t>((-0.1257 - 0.0010776) * 1E7); // -1267776
335+
336+
const int32_t SOUTH_EAST_LAT = static_cast<int32_t>((51.5085 - 0.0006732) * 1E7); // 515080268
337+
const int32_t SOUTH_EAST_LON = static_cast<int32_t>((-0.1257 + 0.0010776) * 1E7); // -1246224
338+
339+
// Array of coordinates in [latitude, longitude] pairs for each corner
340+
static const Vector2l London_boundary[] {
341+
Vector2l(NORTH_WEST_LAT, NORTH_WEST_LON), // Northwest corner
342+
Vector2l(NORTH_EAST_LAT, NORTH_EAST_LON), // Northeast corner
343+
Vector2l(SOUTH_EAST_LAT, SOUTH_EAST_LON), // Southeast corner
344+
Vector2l(SOUTH_WEST_LAT, SOUTH_WEST_LON), // Southwest corner
345+
Vector2l(NORTH_WEST_LAT, NORTH_WEST_LON), // Northwest corner
346+
};
347+
348+
static const struct {
349+
Vector2l point;
350+
float distance;
351+
} London_test_points[] = {
352+
{ Vector2l(CENTER_LAT, CENTER_LON), 75.0f, },
353+
{ Vector2l(CENTER_NORTH_LAT, CENTER_LON), 37.5f, },
354+
{ Vector2l(CENTER_LAT, CENTER_EAST_LON), 37.5f, },
355+
{ Vector2l(CENTER_SOUTH_LAT, CENTER_LON), 37.5f, },
356+
{ Vector2l(CENTER_LAT, CENTER_WEST_LON), 37.5f, },
357+
};
358+
359+
TEST(Polygon, London_distance)
360+
{
361+
TEST_POLYGON_DISTANCE_POINTS(London_boundary, London_test_points);
362+
}
363+
307364
static const Vector2f PROX_boundary[] = {
308365
Vector2f{938.315063f,388.662872f},
309366
Vector2f{545.622803f,1317.25f},

libraries/AP_Math/vector2.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -457,4 +457,4 @@ template bool Vector2<long>::operator ==(const Vector2<long> &v) const;
457457
template bool Vector2<long>::operator !=(const Vector2<long> &v) const;
458458
template bool Vector2<int>::operator ==(const Vector2<int> &v) const;
459459
template bool Vector2<int>::operator !=(const Vector2<int> &v) const;
460-
460+
template int32_t Vector2l::closest_distance_between_line_and_point_squared(Vector2l const&, Vector2l const&, Vector2l const&);

0 commit comments

Comments
 (0)