Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AC_Avoidance/AP_OADijkstra.cpp
4182 views
1
/*
2
This program is free software: you can redistribute it and/or modify
3
it under the terms of the GNU General Public License as published by
4
the Free Software Foundation, either version 3 of the License, or
5
(at your option) any later version.
6
7
This program is distributed in the hope that it will be useful,
8
but WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
GNU General Public License for more details.
11
12
You should have received a copy of the GNU General Public License
13
along with this program. If not, see <http://www.gnu.org/licenses/>.
14
*/
15
16
#include "AC_Avoidance_config.h"
17
18
#if AP_OAPATHPLANNER_DIJKSTRA_ENABLED
19
20
#include "AP_OADijkstra.h"
21
#include "AP_OAPathPlanner.h"
22
23
#include <AC_Fence/AC_Fence.h>
24
25
#if AP_FENCE_ENABLED
26
27
#include <AP_AHRS/AP_AHRS.h>
28
#include <AP_Logger/AP_Logger.h>
29
#include <GCS_MAVLink/GCS.h>
30
31
#define OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK 32 // expanding arrays for fence points and paths to destination will grow in increments of 20 elements
32
#define OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX 255 // index use to indicate we do not have a tentative short path for a node
33
#define OA_DIJKSTRA_ERROR_REPORTING_INTERVAL_MS 5000 // failure messages sent to GCS every 5 seconds
34
35
/// Constructor
36
AP_OADijkstra::AP_OADijkstra(AP_Int16 &options) :
37
_inclusion_polygon_pts(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
38
_exclusion_polygon_pts(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
39
_exclusion_circle_pts(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
40
_short_path_data(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
41
_path(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
42
_options(options)
43
{
44
}
45
46
// calculate a destination to avoid fences
47
// returns DIJKSTRA_STATE_SUCCESS and populates origin_new, destination_new and next_destination_new if avoidance is required
48
// next_destination_new will be non-zero if there is a next destination
49
// dest_to_next_dest_clear will be set to true if the path from (the input) destination to (input) next_destination is clear
50
AP_OADijkstra::AP_OADijkstra_State AP_OADijkstra::update(const Location &current_loc,
51
const Location &destination,
52
const Location &next_destination,
53
Location& origin_new,
54
Location& destination_new,
55
Location& next_destination_new,
56
bool& dest_to_next_dest_clear)
57
{
58
WITH_SEMAPHORE(AP::fence()->polyfence().get_loaded_fence_semaphore());
59
60
// avoidance is not required if no fences
61
if (!some_fences_enabled()) {
62
dest_to_next_dest_clear = _dest_to_next_dest_clear = true;
63
Write_OADijkstra(DIJKSTRA_STATE_NOT_REQUIRED, 0, 0, 0, destination, destination);
64
return DIJKSTRA_STATE_NOT_REQUIRED;
65
}
66
67
// no avoidance required if destination is same as current location
68
if (current_loc.same_latlon_as(destination)) {
69
// we do not check path to next destination so conservatively set to false
70
dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
71
Write_OADijkstra(DIJKSTRA_STATE_NOT_REQUIRED, 0, 0, 0, destination, destination);
72
return DIJKSTRA_STATE_NOT_REQUIRED;
73
}
74
75
// check for inclusion polygon updates
76
if (check_inclusion_polygon_updated()) {
77
_inclusion_polygon_with_margin_ok = false;
78
_polyfence_visgraph_ok = false;
79
_shortest_path_ok = false;
80
}
81
82
// check for exclusion polygon updates
83
if (check_exclusion_polygon_updated()) {
84
_exclusion_polygon_with_margin_ok = false;
85
_polyfence_visgraph_ok = false;
86
_shortest_path_ok = false;
87
}
88
89
// check for exclusion circle updates
90
if (check_exclusion_circle_updated()) {
91
_exclusion_circle_with_margin_ok = false;
92
_polyfence_visgraph_ok = false;
93
_shortest_path_ok = false;
94
}
95
96
// create inner polygon fence
97
if (!_inclusion_polygon_with_margin_ok) {
98
_inclusion_polygon_with_margin_ok = create_inclusion_polygon_with_margin(_polyfence_margin * 100.0f, _error_id);
99
if (!_inclusion_polygon_with_margin_ok) {
100
dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
101
report_error(_error_id);
102
Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
103
return DIJKSTRA_STATE_ERROR;
104
}
105
}
106
107
// create exclusion polygon outer fence
108
if (!_exclusion_polygon_with_margin_ok) {
109
_exclusion_polygon_with_margin_ok = create_exclusion_polygon_with_margin(_polyfence_margin * 100.0f, _error_id);
110
if (!_exclusion_polygon_with_margin_ok) {
111
dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
112
report_error(_error_id);
113
Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
114
return DIJKSTRA_STATE_ERROR;
115
}
116
}
117
118
// create exclusion circle points
119
if (!_exclusion_circle_with_margin_ok) {
120
_exclusion_circle_with_margin_ok = create_exclusion_circle_with_margin(_polyfence_margin * 100.0f, _error_id);
121
if (!_exclusion_circle_with_margin_ok) {
122
dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
123
report_error(_error_id);
124
Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
125
return DIJKSTRA_STATE_ERROR;
126
}
127
}
128
129
// create visgraph for all fence (with margin) points
130
if (!_polyfence_visgraph_ok) {
131
_polyfence_visgraph_ok = create_fence_visgraph(_error_id);
132
if (!_polyfence_visgraph_ok) {
133
_shortest_path_ok = false;
134
dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
135
report_error(_error_id);
136
Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
137
return DIJKSTRA_STATE_ERROR;
138
}
139
// reset logging count to restart logging updated graph
140
_log_num_points = 0;
141
_log_visgraph_version++;
142
}
143
144
// Log one visgraph point per loop
145
if (_polyfence_visgraph_ok && (_log_num_points < total_numpoints()) && (_options & AP_OAPathPlanner::OARecoveryOptions::OA_OPTION_LOG_DIJKSTRA_POINTS) ) {
146
Vector2f vis_point;
147
if (get_point(_log_num_points, vis_point)) {
148
Location log_location(Vector3f{vis_point.x, vis_point.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
149
Write_Visgraph_point(_log_visgraph_version, _log_num_points, log_location.lat, log_location.lng);
150
_log_num_points++;
151
}
152
}
153
154
// rebuild path if destination or next_destination has changed
155
if (!destination.same_latlon_as(_destination_prev) || !next_destination.same_latlon_as(_next_destination_prev)) {
156
_destination_prev = destination;
157
_next_destination_prev = next_destination;
158
_shortest_path_ok = false;
159
}
160
161
// calculate shortest path from current_loc to destination
162
if (!_shortest_path_ok) {
163
_shortest_path_ok = calc_shortest_path(current_loc, destination, _error_id);
164
if (!_shortest_path_ok) {
165
dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
166
report_error(_error_id);
167
Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
168
return DIJKSTRA_STATE_ERROR;
169
}
170
// start from 2nd point on path (first is the original origin)
171
_path_idx_returned = 1;
172
173
// check if path from destination to next_destination intersects with a fence
174
_dest_to_next_dest_clear = false;
175
if (!next_destination.is_zero()) {
176
Vector2f seg_start, seg_end;
177
if (destination.get_vector_xy_from_origin_NE_cm(seg_start) &&
178
next_destination.get_vector_xy_from_origin_NE_cm(seg_end)) {
179
_dest_to_next_dest_clear = !intersects_fence(seg_start, seg_end);
180
}
181
}
182
}
183
184
// path has been created, return latest point
185
Vector2f dest_pos;
186
const uint8_t path_length = get_shortest_path_numpoints() > 0 ? (get_shortest_path_numpoints() - 1) : 0;
187
if ((_path_idx_returned < path_length) && get_shortest_path_point(_path_idx_returned, dest_pos)) {
188
189
// for the first point return origin as current_loc
190
Vector2f origin_pos;
191
if ((_path_idx_returned > 0) && get_shortest_path_point(_path_idx_returned-1, origin_pos)) {
192
// convert offset from ekf origin to Location
193
Location temp_loc(Vector3f{origin_pos.x, origin_pos.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
194
origin_new = temp_loc;
195
} else {
196
// for first point use current loc as origin
197
origin_new = current_loc;
198
}
199
200
// convert offset from ekf origin to Location
201
Location temp_loc(Vector3f{dest_pos.x, dest_pos.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
202
destination_new = destination;
203
destination_new.lat = temp_loc.lat;
204
destination_new.lng = temp_loc.lng;
205
206
// provide next destination to allow smooth cornering
207
next_destination_new.zero();
208
Vector2f next_dest_pos;
209
if ((_path_idx_returned + 1 < path_length) && get_shortest_path_point(_path_idx_returned + 1, next_dest_pos)) {
210
// convert offset from ekf origin to Location
211
Location next_loc(Vector3f{next_dest_pos.x, next_dest_pos.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
212
next_destination_new = destination;
213
next_destination_new.lat = next_loc.lat;
214
next_destination_new.lng = next_loc.lng;
215
} else {
216
// return destination as next_destination
217
next_destination_new = destination;
218
}
219
220
// path to next destination clear state is still valid from previous calcs (was calced along with shortest path)
221
dest_to_next_dest_clear = _dest_to_next_dest_clear;
222
223
// check if we should advance to next point for next iteration
224
const bool near_oa_wp = current_loc.get_distance(destination_new) <= 2.0f;
225
const bool past_oa_wp = current_loc.past_interval_finish_line(origin_new, destination_new);
226
if (near_oa_wp || past_oa_wp) {
227
_path_idx_returned++;
228
}
229
// log success
230
Write_OADijkstra(DIJKSTRA_STATE_SUCCESS, 0, _path_idx_returned, get_shortest_path_numpoints(), destination, destination_new);
231
return DIJKSTRA_STATE_SUCCESS;
232
}
233
234
// we have reached the destination so avoidance is no longer required
235
// path to next destination clear state is still valid from previous calcs
236
dest_to_next_dest_clear = _dest_to_next_dest_clear;
237
Write_OADijkstra(DIJKSTRA_STATE_NOT_REQUIRED, 0, 0, 0, destination, destination);
238
return DIJKSTRA_STATE_NOT_REQUIRED;
239
}
240
241
// returns true if at least one inclusion or exclusion zone is enabled
242
bool AP_OADijkstra::some_fences_enabled() const
243
{
244
const AC_Fence *fence = AC_Fence::get_singleton();
245
if (fence == nullptr) {
246
return false;
247
}
248
if ((fence->polyfence().get_inclusion_polygon_count() == 0) &&
249
(fence->polyfence().get_exclusion_polygon_count() == 0) &&
250
(fence->polyfence().get_exclusion_circle_count() == 0)) {
251
return false;
252
}
253
return ((fence->get_enabled_fences() & AC_FENCE_TYPE_POLYGON) > 0);
254
}
255
256
// return error message for a given error id
257
const char* AP_OADijkstra::get_error_msg(AP_OADijkstra_Error error_id) const
258
{
259
switch (error_id) {
260
case AP_OADijkstra_Error::DIJKSTRA_ERROR_NONE:
261
return "no error";
262
break;
263
case AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY:
264
return "out of memory";
265
break;
266
case AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_POINTS:
267
return "overlapping polygon points";
268
break;
269
case AP_OADijkstra_Error::DIJKSTRA_ERROR_FAILED_TO_BUILD_INNER_POLYGON:
270
return "failed to build inner polygon";
271
break;
272
case AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_LINES:
273
return "overlapping polygon lines";
274
break;
275
case AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED:
276
return "fence disabled";
277
break;
278
case AP_OADijkstra_Error::DIJKSTRA_ERROR_TOO_MANY_FENCE_POINTS:
279
return "too many fence points";
280
break;
281
case AP_OADijkstra_Error::DIJKSTRA_ERROR_NO_POSITION_ESTIMATE:
282
return "no position estimate";
283
break;
284
case AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH:
285
return "could not find path";
286
break;
287
}
288
289
// we should never reach here but just in case
290
return "unknown error";
291
}
292
293
void AP_OADijkstra::report_error(AP_OADijkstra_Error error_id)
294
{
295
// report errors to GCS every 5 seconds
296
uint32_t now_ms = AP_HAL::millis();
297
if ((error_id != AP_OADijkstra_Error::DIJKSTRA_ERROR_NONE) &&
298
((error_id != _error_last_id) || ((now_ms - _error_last_report_ms) > OA_DIJKSTRA_ERROR_REPORTING_INTERVAL_MS))) {
299
const char* error_msg = get_error_msg(error_id);
300
(void)error_msg; // in case !HAL_GCS_ENABLED
301
GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Dijkstra: %s", error_msg);
302
_error_last_id = error_id;
303
_error_last_report_ms = now_ms;
304
}
305
}
306
307
// check if polygon fence has been updated since we created the inner fence. returns true if changed
308
bool AP_OADijkstra::check_inclusion_polygon_updated() const
309
{
310
// exit immediately if polygon fence is not enabled
311
const AC_Fence *fence = AC_Fence::get_singleton();
312
if (fence == nullptr) {
313
return false;
314
}
315
return (_inclusion_polygon_update_ms != fence->polyfence().get_inclusion_polygon_update_ms());
316
}
317
318
// create polygons inside the existing inclusion polygons
319
// returns true on success. returns false on failure and err_id is updated
320
bool AP_OADijkstra::create_inclusion_polygon_with_margin(float margin_cm, AP_OADijkstra_Error &err_id)
321
{
322
const AC_Fence *fence = AC_Fence::get_singleton();
323
324
if (fence == nullptr) {
325
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
326
return false;
327
}
328
329
// skip unnecessary retry to build inclusion polygon if previous fence points have not changed
330
if (_inclusion_polygon_update_ms == fence->polyfence().get_inclusion_polygon_update_ms()) {
331
return false;
332
}
333
334
_inclusion_polygon_update_ms = fence->polyfence().get_inclusion_polygon_update_ms();
335
336
// clear all points
337
_inclusion_polygon_numpoints = 0;
338
339
// return immediately if no polygons
340
const uint8_t num_inclusion_polygons = fence->polyfence().get_inclusion_polygon_count();
341
342
// iterate through polygons and create inner points
343
for (uint8_t i = 0; i < num_inclusion_polygons; i++) {
344
uint16_t num_points;
345
const Vector2f* boundary = fence->polyfence().get_inclusion_polygon(i, num_points);
346
347
// for each point in inclusion polygon
348
// Note: boundary is "unclosed" meaning the last point is *not* the same as the first
349
uint16_t new_points = 0;
350
for (uint16_t j = 0; j < num_points; j++) {
351
352
// find points before and after current point (relative to current point)
353
const uint16_t before_idx = (j == 0) ? num_points-1 : j-1;
354
const uint16_t after_idx = (j == num_points-1) ? 0 : j+1;
355
Vector2f before_pt = boundary[before_idx] - boundary[j];
356
Vector2f after_pt = boundary[after_idx] - boundary[j];
357
358
// if points are overlapping fail
359
if (before_pt.is_zero() || after_pt.is_zero() || (before_pt == after_pt)) {
360
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_POINTS;
361
return false;
362
}
363
364
// scale points to be unit vectors
365
before_pt.normalize();
366
after_pt.normalize();
367
368
// calculate intermediate point and scale to margin
369
Vector2f intermediate_pt = after_pt + before_pt;
370
intermediate_pt.normalize();
371
intermediate_pt *= margin_cm;
372
373
// find final point which is outside the inside polygon
374
Vector2f temp_point = boundary[j] + intermediate_pt;
375
if (Polygon_outside(temp_point, boundary, num_points)) {
376
intermediate_pt *= -1.0;
377
temp_point = boundary[j] + intermediate_pt;
378
if (Polygon_outside(temp_point, boundary, num_points)) {
379
// could not find a point on either side that was outside the exclusion polygon so fail
380
// this may happen if the exclusion polygon has overlapping lines
381
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_LINES;
382
return false;
383
}
384
}
385
386
// don't add points in corners
387
if (fabsf(wrap_PI(intermediate_pt.angle() - before_pt.angle())) < M_PI_2) {
388
continue;
389
}
390
391
// expand array if required
392
if (!_inclusion_polygon_pts.expand_to_hold(_inclusion_polygon_numpoints + new_points + 1)) {
393
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
394
return false;
395
}
396
// add point
397
_inclusion_polygon_pts[_inclusion_polygon_numpoints + new_points] = temp_point;
398
new_points++;
399
}
400
401
// update total number of points
402
_inclusion_polygon_numpoints += new_points;
403
}
404
return true;
405
}
406
407
// check if exclusion polygons have been updated since create_exclusion_polygon_with_margin was run
408
// returns true if changed
409
bool AP_OADijkstra::check_exclusion_polygon_updated() const
410
{
411
const AC_Fence *fence = AC_Fence::get_singleton();
412
if (fence == nullptr) {
413
return false;
414
}
415
return (_exclusion_polygon_update_ms != fence->polyfence().get_exclusion_polygon_update_ms());
416
}
417
418
// create polygons around existing exclusion polygons
419
// returns true on success. returns false on failure and err_id is updated
420
bool AP_OADijkstra::create_exclusion_polygon_with_margin(float margin_cm, AP_OADijkstra_Error &err_id)
421
{
422
const AC_Fence *fence = AC_Fence::get_singleton();
423
424
if (fence == nullptr) {
425
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
426
return false;
427
}
428
429
// skip unnecessary retry to build exclusion polygon if previous fence points have not changed
430
if (_exclusion_polygon_update_ms == fence->polyfence().get_exclusion_polygon_update_ms()) {
431
return false;
432
}
433
434
_exclusion_polygon_update_ms = fence->polyfence().get_exclusion_polygon_update_ms();
435
436
437
// clear all points
438
_exclusion_polygon_numpoints = 0;
439
440
// return immediately if no exclusion polygons
441
const uint8_t num_exclusion_polygons = fence->polyfence().get_exclusion_polygon_count();
442
443
// iterate through exclusion polygons and create outer points
444
for (uint8_t i = 0; i < num_exclusion_polygons; i++) {
445
uint16_t num_points;
446
const Vector2f* boundary = fence->polyfence().get_exclusion_polygon(i, num_points);
447
448
// for each point in exclusion polygon
449
// Note: boundary is "unclosed" meaning the last point is *not* the same as the first
450
uint16_t new_points = 0;
451
for (uint16_t j = 0; j < num_points; j++) {
452
453
// find points before and after current point (relative to current point)
454
const uint16_t before_idx = (j == 0) ? num_points-1 : j-1;
455
const uint16_t after_idx = (j == num_points-1) ? 0 : j+1;
456
Vector2f before_pt = boundary[before_idx] - boundary[j];
457
Vector2f after_pt = boundary[after_idx] - boundary[j];
458
459
// if points are overlapping fail
460
if (before_pt.is_zero() || after_pt.is_zero() || (before_pt == after_pt)) {
461
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_POINTS;
462
return false;
463
}
464
465
// scale points to be unit vectors
466
before_pt.normalize();
467
after_pt.normalize();
468
469
// calculate intermediate point and scale to margin
470
Vector2f intermediate_pt = after_pt + before_pt;
471
intermediate_pt.normalize();
472
intermediate_pt *= margin_cm;
473
474
// find final point which is outside the original polygon
475
Vector2f temp_point = boundary[j] + intermediate_pt;
476
if (!Polygon_outside(temp_point, boundary, num_points)) {
477
intermediate_pt *= -1;
478
temp_point = boundary[j] + intermediate_pt;
479
if (!Polygon_outside(temp_point, boundary, num_points)) {
480
// could not find a point on either side that was outside the exclusion polygon so fail
481
// this may happen if the exclusion polygon has overlapping lines
482
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_LINES;
483
return false;
484
}
485
}
486
487
// don't add points in corners
488
if (fabsf(wrap_PI(intermediate_pt.angle() - before_pt.angle())) < M_PI_2) {
489
continue;
490
}
491
492
// expand array if required
493
if (!_exclusion_polygon_pts.expand_to_hold(_exclusion_polygon_numpoints + new_points + 1)) {
494
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
495
return false;
496
}
497
// add point
498
_exclusion_polygon_pts[_exclusion_polygon_numpoints + new_points] = temp_point;
499
new_points++;
500
}
501
502
// update total number of points
503
_exclusion_polygon_numpoints += new_points;
504
}
505
return true;
506
}
507
508
// check if exclusion circles have been updated since create_exclusion_circle_with_margin was run
509
// returns true if changed
510
bool AP_OADijkstra::check_exclusion_circle_updated() const
511
{
512
// exit immediately if fence is not enabled
513
const AC_Fence *fence = AC_Fence::get_singleton();
514
if (fence == nullptr) {
515
return false;
516
}
517
return (_exclusion_circle_update_ms != fence->polyfence().get_exclusion_circle_update_ms());
518
}
519
520
// create polygons around existing exclusion circles
521
// returns true on success. returns false on failure and err_id is updated
522
bool AP_OADijkstra::create_exclusion_circle_with_margin(float margin_cm, AP_OADijkstra_Error &err_id)
523
{
524
// exit immediately if fence is not enabled
525
const AC_Fence *fence = AC_Fence::get_singleton();
526
if (fence == nullptr) {
527
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
528
return false;
529
}
530
531
// clear all points
532
_exclusion_circle_numpoints = 0;
533
534
// unit length offsets for polygon points around circles
535
const Vector2f unit_offsets[] = {
536
{cosf(radians(30)), cosf(radians(30-90))}, // north-east
537
{cosf(radians(90)), cosf(radians(90-90))}, // east
538
{cosf(radians(150)), cosf(radians(150-90))},// south-east
539
{cosf(radians(210)), cosf(radians(210-90))},// south-west
540
{cosf(radians(270)), cosf(radians(270-90))},// west
541
{cosf(radians(330)), cosf(radians(330-90))},// north-west
542
};
543
const uint8_t num_points_per_circle = ARRAY_SIZE(unit_offsets);
544
545
// expand polygon point array if required
546
const uint8_t num_exclusion_circles = fence->polyfence().get_exclusion_circle_count();
547
if (!_exclusion_circle_pts.expand_to_hold(num_exclusion_circles * num_points_per_circle)) {
548
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
549
return false;
550
}
551
552
// iterate through exclusion circles and create outer polygon points
553
for (uint8_t i = 0; i < num_exclusion_circles; i++) {
554
Vector2f circle_pos_cm;
555
float radius;
556
if (fence->polyfence().get_exclusion_circle(i, circle_pos_cm, radius)) {
557
// scaler to ensure lines between points do not intersect circle
558
const float scaler = (1.0f / cosf(radians(180.0f / (float)num_points_per_circle))) * ((radius * 100.0f) + margin_cm);
559
560
// add points to array
561
for (uint8_t j = 0; j < num_points_per_circle; j++) {
562
_exclusion_circle_pts[_exclusion_circle_numpoints] = circle_pos_cm + (unit_offsets[j] * scaler);
563
_exclusion_circle_numpoints++;
564
}
565
}
566
}
567
568
// record fence update time so we don't process these exclusion circles again
569
_exclusion_circle_update_ms = fence->polyfence().get_exclusion_circle_update_ms();
570
571
return true;
572
}
573
574
// returns total number of points across all fence types
575
uint16_t AP_OADijkstra::total_numpoints() const
576
{
577
return _inclusion_polygon_numpoints + _exclusion_polygon_numpoints + _exclusion_circle_numpoints;
578
}
579
580
// get a single point across the total list of points from all fence types
581
bool AP_OADijkstra::get_point(uint16_t index, Vector2f &point) const
582
{
583
// sanity check index
584
if (index >= total_numpoints()) {
585
return false;
586
}
587
588
// return an inclusion polygon point
589
if (index < _inclusion_polygon_numpoints) {
590
point = _inclusion_polygon_pts[index];
591
return true;
592
}
593
index -= _inclusion_polygon_numpoints;
594
595
// return an exclusion polygon point
596
if (index < _exclusion_polygon_numpoints) {
597
point = _exclusion_polygon_pts[index];
598
return true;
599
}
600
index -= _exclusion_polygon_numpoints;
601
602
// return an exclusion circle point
603
if (index < _exclusion_circle_numpoints) {
604
point = _exclusion_circle_pts[index];
605
return true;
606
}
607
608
// we should never get here but just in case
609
return false;
610
}
611
612
// returns true if line segment intersects polygon or circular fence
613
bool AP_OADijkstra::intersects_fence(const Vector2f &seg_start, const Vector2f &seg_end) const
614
{
615
// return immediately if fence is not enabled
616
const AC_Fence *fence = AC_Fence::get_singleton();
617
if (fence == nullptr) {
618
return false;
619
}
620
621
// determine if segment crosses any of the inclusion polygons
622
uint16_t num_points = 0;
623
for (uint8_t i = 0; i < fence->polyfence().get_inclusion_polygon_count(); i++) {
624
const Vector2f* boundary = fence->polyfence().get_inclusion_polygon(i, num_points);
625
if (boundary != nullptr) {
626
Vector2f intersection;
627
if (Polygon_intersects(boundary, num_points, seg_start, seg_end, intersection)) {
628
return true;
629
}
630
}
631
}
632
633
// determine if segment crosses any of the exclusion polygons
634
for (uint8_t i = 0; i < fence->polyfence().get_exclusion_polygon_count(); i++) {
635
const Vector2f* boundary = fence->polyfence().get_exclusion_polygon(i, num_points);
636
if (boundary != nullptr) {
637
Vector2f intersection;
638
if (Polygon_intersects(boundary, num_points, seg_start, seg_end, intersection)) {
639
return true;
640
}
641
}
642
}
643
644
// determine if segment crosses any of the inclusion circles
645
for (uint8_t i = 0; i < fence->polyfence().get_inclusion_circle_count(); i++) {
646
Vector2f center_pos_cm;
647
float radius;
648
if (fence->polyfence().get_inclusion_circle(i, center_pos_cm, radius)) {
649
// intersects circle if either start or end is further from the center than the radius
650
const float radius_cm_sq = sq(radius * 100.0f) ;
651
if ((seg_start - center_pos_cm).length_squared() > radius_cm_sq) {
652
return true;
653
}
654
if ((seg_end - center_pos_cm).length_squared() > radius_cm_sq) {
655
return true;
656
}
657
}
658
}
659
660
// determine if segment crosses any of the exclusion circles
661
for (uint8_t i = 0; i < fence->polyfence().get_exclusion_circle_count(); i++) {
662
Vector2f center_pos_cm;
663
float radius;
664
if (fence->polyfence().get_exclusion_circle(i, center_pos_cm, radius)) {
665
// calculate distance between circle's center and segment
666
const float dist_cm = Vector2f::closest_distance_between_line_and_point(seg_start, seg_end, center_pos_cm);
667
668
// intersects if distance is less than radius
669
if (dist_cm <= (radius * 100.0f)) {
670
return true;
671
}
672
}
673
}
674
675
// if we got this far then no intersection
676
return false;
677
}
678
679
// create visibility graph for all fence (with margin) points
680
// returns true on success. returns false on failure and err_id is updated
681
// requires these functions to have been run create_inclusion_polygon_with_margin, create_exclusion_polygon_with_margin, create_exclusion_circle_with_margin
682
bool AP_OADijkstra::create_fence_visgraph(AP_OADijkstra_Error &err_id)
683
{
684
// exit immediately if fence is not enabled
685
const AC_Fence *fence = AC_Fence::get_singleton();
686
if (fence == nullptr) {
687
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
688
return false;
689
}
690
691
// fail if more fence points than algorithm can handle
692
if (total_numpoints() >= OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX) {
693
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_TOO_MANY_FENCE_POINTS;
694
return false;
695
}
696
697
// clear fence points visibility graph
698
_fence_visgraph.clear();
699
700
// calculate distance from each point to all other points
701
for (uint8_t i = 0; i < total_numpoints() - 1; i++) {
702
Vector2f start_seg;
703
if (get_point(i, start_seg)) {
704
for (uint8_t j = i + 1; j < total_numpoints(); j++) {
705
Vector2f end_seg;
706
if (get_point(j, end_seg)) {
707
// if line segment does not intersect with any inclusion or exclusion zones add to visgraph
708
if (!intersects_fence(start_seg, end_seg)) {
709
if (!_fence_visgraph.add_item({AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT, i},
710
{AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT, j},
711
(start_seg - end_seg).length())) {
712
// failure to add a point can only be caused by out-of-memory
713
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
714
return false;
715
}
716
}
717
}
718
}
719
}
720
}
721
722
return true;
723
}
724
725
// updates visibility graph for a given position which is an offset (in cm) from the ekf origin
726
// to add an additional position (i.e. the destination) set add_extra_position = true and provide the position in the extra_position argument
727
// requires create_inclusion_polygon_with_margin to have been run
728
// returns true on success
729
bool AP_OADijkstra::update_visgraph(AP_OAVisGraph& visgraph, const AP_OAVisGraph::OAItemID& oaid, const Vector2f &position, bool add_extra_position, Vector2f extra_position)
730
{
731
// clear visibility graph
732
visgraph.clear();
733
734
// calculate distance from position to all inclusion/exclusion fence points
735
for (uint8_t i = 0; i < total_numpoints(); i++) {
736
Vector2f seg_end;
737
if (get_point(i, seg_end)) {
738
if (!intersects_fence(position, seg_end)) {
739
// line segment does not intersect with fences so add to visgraph
740
if (!visgraph.add_item(oaid, {AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT, i}, (position - seg_end).length())) {
741
return false;
742
}
743
}
744
}
745
}
746
747
// add extra point to visibility graph if it doesn't intersect with polygon fence or exclusion polygons
748
if (add_extra_position) {
749
if (!intersects_fence(position, extra_position)) {
750
if (!visgraph.add_item(oaid, {AP_OAVisGraph::OATYPE_DESTINATION, 0}, (position - extra_position).length())) {
751
return false;
752
}
753
}
754
}
755
756
return true;
757
}
758
759
// update total distance for all nodes visible from current node
760
// curr_node_idx is an index into the _short_path_data array
761
void AP_OADijkstra::update_visible_node_distances(node_index curr_node_idx)
762
{
763
// sanity check
764
if (curr_node_idx >= _short_path_data_numpoints) {
765
return;
766
}
767
768
// get current node for convenience
769
const ShortPathNode &curr_node = _short_path_data[curr_node_idx];
770
771
// for each visibility graph
772
const AP_OAVisGraph* visgraphs[] = {&_fence_visgraph, &_destination_visgraph};
773
for (uint8_t v=0; v<ARRAY_SIZE(visgraphs); v++) {
774
775
// skip if empty
776
const AP_OAVisGraph &curr_visgraph = *visgraphs[v];
777
if (curr_visgraph.num_items() == 0) {
778
continue;
779
}
780
781
// search visibility graph for items visible from current_node
782
for (uint16_t i = 0; i < curr_visgraph.num_items(); i++) {
783
const AP_OAVisGraph::VisGraphItem &item = curr_visgraph[i];
784
// match if current node's id matches either of the id's in the graph (i.e. either end of the vector)
785
if ((curr_node.id == item.id1) || (curr_node.id == item.id2)) {
786
AP_OAVisGraph::OAItemID matching_id = (curr_node.id == item.id1) ? item.id2 : item.id1;
787
// find item's id in node array
788
node_index item_node_idx;
789
if (find_node_from_id(matching_id, item_node_idx)) {
790
// if current node's distance + distance to item is less than item's current distance, update item's distance
791
const float dist_to_item_via_current_node = _short_path_data[curr_node_idx].distance_cm + item.distance_cm;
792
if (dist_to_item_via_current_node < _short_path_data[item_node_idx].distance_cm) {
793
// update item's distance and set "distance_from_idx" to current node's index
794
_short_path_data[item_node_idx].distance_cm = dist_to_item_via_current_node;
795
_short_path_data[item_node_idx].distance_from_idx = curr_node_idx;
796
}
797
}
798
}
799
}
800
}
801
}
802
803
// find a node's index into _short_path_data array from it's id (i.e. id type and id number)
804
// returns true if successful and node_idx is updated
805
bool AP_OADijkstra::find_node_from_id(const AP_OAVisGraph::OAItemID &id, node_index &node_idx) const
806
{
807
switch (id.id_type) {
808
case AP_OAVisGraph::OATYPE_SOURCE:
809
// source node is always the first node
810
if (_short_path_data_numpoints > 0) {
811
node_idx = 0;
812
return true;
813
}
814
break;
815
case AP_OAVisGraph::OATYPE_DESTINATION:
816
// destination is always the 2nd node
817
if (_short_path_data_numpoints > 1) {
818
node_idx = 1;
819
return true;
820
}
821
break;
822
case AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT:
823
// intermediate nodes start from 3rd node
824
if (_short_path_data_numpoints > id.id_num + 2) {
825
node_idx = id.id_num + 2;
826
return true;
827
}
828
break;
829
}
830
831
// could not find node
832
return false;
833
}
834
835
// find index of node with lowest tentative distance (ignore visited nodes)
836
// returns true if successful and node_idx argument is updated
837
bool AP_OADijkstra::find_closest_node_idx(node_index &node_idx) const
838
{
839
node_index lowest_idx = 0;
840
float lowest_dist = FLT_MAX;
841
842
// scan through all nodes looking for closest
843
for (node_index i=0; i<_short_path_data_numpoints; i++) {
844
const ShortPathNode &node = _short_path_data[i];
845
if (node.visited || is_equal(_short_path_data[i].distance_cm, FLT_MAX)) {
846
// if node is already visited OR cannot be reached yet, we can't use it
847
continue;
848
}
849
// figure out the pos of this node
850
Vector2f node_pos;
851
float dist_with_heuristics = FLT_MAX;
852
if (convert_node_to_point(node.id, node_pos)) {
853
// heuristics is is simple Euclidean distance from the node to the destination
854
// This should be admissible, therefore optimal path is guaranteed
855
const float heuristics = (node_pos-_path_destination).length();
856
dist_with_heuristics = node.distance_cm + heuristics;
857
} else {
858
// shouldn't happen
859
return false;
860
}
861
if (dist_with_heuristics < lowest_dist) {
862
// for NOW, this is the closest node
863
lowest_idx = i;
864
lowest_dist = dist_with_heuristics;
865
}
866
}
867
868
if (lowest_dist < FLT_MAX) {
869
// found the closest node
870
node_idx = lowest_idx;
871
return true;
872
}
873
return false;
874
}
875
876
// calculate shortest path from origin to destination
877
// returns true on success. returns false on failure and err_id is updated
878
// requires these functions to have been run: create_inclusion_polygon_with_margin, create_exclusion_polygon_with_margin, create_exclusion_circle_with_margin, create_polygon_fence_visgraph
879
// resulting path is stored in _shortest_path array as vector offsets from EKF origin
880
bool AP_OADijkstra::calc_shortest_path(const Location &origin, const Location &destination, AP_OADijkstra_Error &err_id)
881
{
882
// convert origin and destination to offsets from EKF origin
883
if (!origin.get_vector_xy_from_origin_NE_cm(_path_source) ||
884
!destination.get_vector_xy_from_origin_NE_cm(_path_destination)) {
885
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_NO_POSITION_ESTIMATE;
886
return false;
887
}
888
889
// create visgraphs of origin and destination to fence points
890
if (!update_visgraph(_source_visgraph, {AP_OAVisGraph::OATYPE_SOURCE, 0}, _path_source, true, _path_destination)) {
891
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
892
return false;
893
}
894
if (!update_visgraph(_destination_visgraph, {AP_OAVisGraph::OATYPE_DESTINATION, 0}, _path_destination)) {
895
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
896
return false;
897
}
898
899
// expand _short_path_data if necessary
900
if (!_short_path_data.expand_to_hold(2 + total_numpoints())) {
901
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
902
return false;
903
}
904
905
// add origin and destination (node_type, id, visited, distance_from_idx, distance_cm) to short_path_data array
906
_short_path_data[0] = {{AP_OAVisGraph::OATYPE_SOURCE, 0}, false, 0, 0};
907
_short_path_data[1] = {{AP_OAVisGraph::OATYPE_DESTINATION, 0}, false, OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX, FLT_MAX};
908
_short_path_data_numpoints = 2;
909
910
// add all inclusion and exclusion fence points to short_path_data array (node_type, id, visited, distance_from_idx, distance_cm)
911
for (uint8_t i=0; i<total_numpoints(); i++) {
912
_short_path_data[_short_path_data_numpoints++] = {{AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT, i}, false, OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX, FLT_MAX};
913
}
914
915
// start algorithm from source point
916
node_index current_node_idx = 0;
917
918
// update nodes visible from source point
919
for (uint16_t i = 0; i < _source_visgraph.num_items(); i++) {
920
node_index node_idx;
921
if (find_node_from_id(_source_visgraph[i].id2, node_idx)) {
922
_short_path_data[node_idx].distance_cm = _source_visgraph[i].distance_cm;
923
_short_path_data[node_idx].distance_from_idx = current_node_idx;
924
} else {
925
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH;
926
return false;
927
}
928
}
929
// mark source node as visited
930
_short_path_data[current_node_idx].visited = true;
931
932
// move current_node_idx to node with lowest distance
933
while (find_closest_node_idx(current_node_idx)) {
934
node_index dest_node;
935
// See if this next "closest" node is actually the destination
936
if (find_node_from_id({AP_OAVisGraph::OATYPE_DESTINATION,0}, dest_node) && current_node_idx == dest_node) {
937
// We have discovered destination.. Don't bother with the rest of the graph
938
break;
939
}
940
// update distances to all neighbours of current node
941
update_visible_node_distances(current_node_idx);
942
943
// mark current node as visited
944
_short_path_data[current_node_idx].visited = true;
945
}
946
947
// extract path starting from destination
948
bool success = false;
949
node_index nidx;
950
if (!find_node_from_id({AP_OAVisGraph::OATYPE_DESTINATION,0}, nidx)) {
951
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH;
952
return false;
953
}
954
_path_numpoints = 0;
955
while (true) {
956
if (!_path.expand_to_hold(_path_numpoints + 1)) {
957
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
958
return false;
959
}
960
// fail if newest node has invalid distance_from_index
961
if ((_short_path_data[nidx].distance_from_idx == OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX) ||
962
(_short_path_data[nidx].distance_cm >= FLT_MAX)) {
963
break;
964
} else {
965
// add node's id to path array
966
_path[_path_numpoints] = _short_path_data[nidx].id;
967
_path_numpoints++;
968
969
// we are done if node is the source
970
if (_short_path_data[nidx].id.id_type == AP_OAVisGraph::OATYPE_SOURCE) {
971
success = true;
972
break;
973
} else {
974
// follow node's "distance_from_idx" to previous node on path
975
nidx = _short_path_data[nidx].distance_from_idx;
976
}
977
}
978
}
979
// report error in case path not found
980
if (!success) {
981
err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH;
982
}
983
984
return success;
985
}
986
987
// return point from final path as an offset (in cm) from the ekf origin
988
bool AP_OADijkstra::get_shortest_path_point(uint8_t point_num, Vector2f& pos) const
989
{
990
if ((_path_numpoints == 0) || (point_num >= _path_numpoints)) {
991
return false;
992
}
993
994
// get id from path
995
AP_OAVisGraph::OAItemID id = _path[_path_numpoints - point_num - 1];
996
997
return convert_node_to_point(id, pos);
998
}
999
1000
// find the position of a node as an offset (in cm) from the ekf origin
1001
bool AP_OADijkstra::convert_node_to_point(const AP_OAVisGraph::OAItemID& id, Vector2f& pos) const
1002
{
1003
// convert id to a position offset from EKF origin
1004
switch (id.id_type) {
1005
case AP_OAVisGraph::OATYPE_SOURCE:
1006
pos = _path_source;
1007
return true;
1008
case AP_OAVisGraph::OATYPE_DESTINATION:
1009
pos = _path_destination;
1010
return true;
1011
case AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT:
1012
return get_point(id.id_num, pos);
1013
}
1014
1015
// we should never reach here but just in case
1016
return false;
1017
}
1018
#endif // AP_FENCE_ENABLED
1019
1020
1021
#endif // AP_OAPATHPLANNER_DIJKSTRA_ENABLED
1022
1023