LCOV - code coverage report
Current view: top level - AC_Avoidance - AP_OADijkstra.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 337 482 69.9 %
Date: 2026-09-09 11:02:08 Functions: 20 22 90.9 %

          Line data    Source code
       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           7 : AP_OADijkstra::AP_OADijkstra(AP_Int16 &options) :
      37           7 :         _inclusion_polygon_pts(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
      38           7 :         _exclusion_polygon_pts(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
      39           7 :         _exclusion_circle_pts(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
      40           7 :         _short_path_data(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
      41           7 :         _path(OA_DIJKSTRA_EXPANDING_ARRAY_ELEMENTS_PER_CHUNK),
      42          21 :         _options(options)
      43             : {
      44           7 : }
      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         270 : 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         270 :     WITH_SEMAPHORE(AP::fence()->polyfence().get_loaded_fence_semaphore());
      59             : 
      60             :     // avoidance is not required if no fences
      61         270 :     if (!some_fences_enabled()) {
      62           0 :         dest_to_next_dest_clear = _dest_to_next_dest_clear = true;
      63           0 :         Write_OADijkstra(DIJKSTRA_STATE_NOT_REQUIRED, 0, 0, 0, destination, destination);
      64           0 :         return DIJKSTRA_STATE_NOT_REQUIRED;
      65             :     }
      66             : 
      67             :     // no avoidance required if destination is same as current location
      68         270 :     if (current_loc.same_latlon_as(destination)) {
      69             :         // we do not check path to next destination so conservatively set to false
      70           0 :         dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
      71           0 :         Write_OADijkstra(DIJKSTRA_STATE_NOT_REQUIRED, 0, 0, 0, destination, destination);
      72           0 :         return DIJKSTRA_STATE_NOT_REQUIRED;
      73             :     }
      74             : 
      75             :     // check for inclusion polygon updates
      76         270 :     if (check_inclusion_polygon_updated()) {
      77           1 :         _inclusion_polygon_with_margin_ok = false;
      78           1 :         _polyfence_visgraph_ok = false;
      79           1 :         _shortest_path_ok = false;
      80             :     }
      81             : 
      82             :     // check for exclusion polygon updates
      83         270 :     if (check_exclusion_polygon_updated()) {
      84           1 :         _exclusion_polygon_with_margin_ok = false;
      85           1 :         _polyfence_visgraph_ok = false;
      86           1 :         _shortest_path_ok = false;
      87             :     }
      88             : 
      89             :     // check for exclusion circle updates
      90         270 :     if (check_exclusion_circle_updated()) {
      91           1 :         _exclusion_circle_with_margin_ok = false;
      92           1 :         _polyfence_visgraph_ok = false;
      93           1 :         _shortest_path_ok = false;
      94             :     }
      95             : 
      96             :     // create inner polygon fence
      97         270 :     if (!_inclusion_polygon_with_margin_ok) {
      98           1 :         _inclusion_polygon_with_margin_ok = create_inclusion_polygon_with_margin(_polyfence_margin * 100.0f, _error_id);
      99           1 :         if (!_inclusion_polygon_with_margin_ok) {
     100           0 :             dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
     101           0 :             report_error(_error_id);
     102           0 :             Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
     103           0 :             return DIJKSTRA_STATE_ERROR;
     104             :         }
     105             :     }
     106             : 
     107             :     // create exclusion polygon outer fence
     108         270 :     if (!_exclusion_polygon_with_margin_ok) {
     109           1 :         _exclusion_polygon_with_margin_ok = create_exclusion_polygon_with_margin(_polyfence_margin * 100.0f, _error_id);
     110           1 :         if (!_exclusion_polygon_with_margin_ok) {
     111           0 :             dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
     112           0 :             report_error(_error_id);
     113           0 :             Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
     114           0 :             return DIJKSTRA_STATE_ERROR;
     115             :         }
     116             :     }
     117             : 
     118             :     // create exclusion circle points
     119         270 :     if (!_exclusion_circle_with_margin_ok) {
     120           1 :         _exclusion_circle_with_margin_ok = create_exclusion_circle_with_margin(_polyfence_margin * 100.0f, _error_id);
     121           1 :         if (!_exclusion_circle_with_margin_ok) {
     122           0 :             dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
     123           0 :             report_error(_error_id);
     124           0 :             Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
     125           0 :             return DIJKSTRA_STATE_ERROR;
     126             :         }
     127             :     }
     128             : 
     129             :     // create visgraph for all fence (with margin) points
     130         270 :     if (!_polyfence_visgraph_ok) {
     131           1 :         _polyfence_visgraph_ok = create_fence_visgraph(_error_id);
     132           1 :         if (!_polyfence_visgraph_ok) {
     133           0 :             _shortest_path_ok = false;
     134           0 :             dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
     135           0 :             report_error(_error_id);
     136           0 :             Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
     137           0 :             return DIJKSTRA_STATE_ERROR;
     138             :         }
     139             :         // reset logging count to restart logging updated graph
     140           1 :         _log_num_points = 0;
     141           1 :         _log_visgraph_version++;
     142             :     }
     143             : 
     144             :     // Log one visgraph point per loop
     145         270 :     if (_polyfence_visgraph_ok && (_log_num_points < total_numpoints()) && (_options & AP_OAPathPlanner::OARecoveryOptions::OA_OPTION_LOG_DIJKSTRA_POINTS) ) {
     146           0 :         Vector2f vis_point;
     147           0 :         if (get_point(_log_num_points, vis_point)) {
     148           0 :             Location log_location(Vector3f{vis_point.x, vis_point.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
     149           0 :             Write_Visgraph_point(_log_visgraph_version, _log_num_points, log_location.lat, log_location.lng);
     150           0 :             _log_num_points++;
     151             :         }
     152             :     }
     153             : 
     154             :     // rebuild path if destination or next_destination has changed
     155         270 :     if (!destination.same_latlon_as(_destination_prev) || !next_destination.same_latlon_as(_next_destination_prev)) {
     156           2 :         _destination_prev = destination;
     157           2 :         _next_destination_prev = next_destination;
     158           2 :         _shortest_path_ok = false;
     159             :     }
     160             : 
     161             :     // calculate shortest path from current_loc to destination
     162         270 :     if (!_shortest_path_ok) {
     163           2 :         _shortest_path_ok = calc_shortest_path(current_loc, destination, _error_id);
     164           2 :         if (!_shortest_path_ok) {
     165           0 :             dest_to_next_dest_clear = _dest_to_next_dest_clear = false;
     166           0 :             report_error(_error_id);
     167           0 :             Write_OADijkstra(DIJKSTRA_STATE_ERROR, (uint8_t)_error_id, 0, 0, destination, destination);
     168           0 :             return DIJKSTRA_STATE_ERROR;
     169             :         }
     170             :         // start from 2nd point on path (first is the original origin)
     171           2 :         _path_idx_returned = 1;
     172             : 
     173             :         // check if path from destination to next_destination intersects with a fence
     174           2 :         _dest_to_next_dest_clear = false;
     175           2 :         if (!next_destination.is_zero()) {
     176           0 :             Vector2f seg_start, seg_end;
     177           0 :             if (destination.get_vector_xy_from_origin_NE_cm(seg_start) &&
     178           0 :                 next_destination.get_vector_xy_from_origin_NE_cm(seg_end)) {
     179           0 :                 _dest_to_next_dest_clear = !intersects_fence(seg_start, seg_end);
     180             :             }
     181             :         }
     182             :     }
     183             : 
     184             :     // path has been created, return latest point
     185         270 :     Vector2f dest_pos;
     186         270 :     const uint8_t path_length = get_shortest_path_numpoints() > 0 ? (get_shortest_path_numpoints() - 1) : 0;
     187         270 :     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         227 :         Vector2f origin_pos;
     191         227 :         if ((_path_idx_returned > 0) && get_shortest_path_point(_path_idx_returned-1, origin_pos)) {
     192             :             // convert offset from ekf origin to Location
     193         227 :             Location temp_loc(Vector3f{origin_pos.x, origin_pos.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
     194         227 :             origin_new = temp_loc;
     195             :         } else {
     196             :             // for first point use current loc as origin
     197           0 :             origin_new = current_loc;
     198             :         }
     199             : 
     200             :         // convert offset from ekf origin to Location
     201         227 :         Location temp_loc(Vector3f{dest_pos.x, dest_pos.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
     202         227 :         destination_new = destination;
     203         227 :         destination_new.lat = temp_loc.lat;
     204         227 :         destination_new.lng = temp_loc.lng;
     205             : 
     206             :         // provide next destination to allow smooth cornering
     207         227 :         next_destination_new.zero();
     208         227 :         Vector2f next_dest_pos;
     209         227 :         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         176 :             Location next_loc(Vector3f{next_dest_pos.x, next_dest_pos.y, 0.0}, Location::AltFrame::ABOVE_ORIGIN);
     212         176 :             next_destination_new = destination;
     213         176 :             next_destination_new.lat = next_loc.lat;
     214         176 :             next_destination_new.lng = next_loc.lng;
     215             :         } else {
     216             :             // return destination as next_destination
     217          51 :             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         227 :         dest_to_next_dest_clear = _dest_to_next_dest_clear;
     222             : 
     223             :         // check if we should advance to next point for next iteration
     224         227 :         const bool near_oa_wp = current_loc.get_distance(destination_new) <= 2.0f;
     225         227 :         const bool past_oa_wp = current_loc.past_interval_finish_line(origin_new, destination_new);
     226         227 :         if (near_oa_wp || past_oa_wp) {
     227          10 :             _path_idx_returned++;
     228             :         }
     229             :         // log success
     230         227 :         Write_OADijkstra(DIJKSTRA_STATE_SUCCESS, 0, _path_idx_returned, get_shortest_path_numpoints(), destination, destination_new);
     231         227 :         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          43 :     dest_to_next_dest_clear = _dest_to_next_dest_clear;
     237          43 :     Write_OADijkstra(DIJKSTRA_STATE_NOT_REQUIRED, 0, 0, 0, destination, destination);
     238          43 :     return DIJKSTRA_STATE_NOT_REQUIRED;
     239         270 : }
     240             : 
     241             : // returns true if at least one inclusion or exclusion zone is enabled
     242         270 : bool AP_OADijkstra::some_fences_enabled() const
     243             : {
     244         270 :     const AC_Fence *fence = AC_Fence::get_singleton();
     245         270 :     if (fence == nullptr) {
     246           0 :         return false;
     247             :     }
     248         270 :     if ((fence->polyfence().get_inclusion_polygon_count() == 0) &&
     249         270 :         (fence->polyfence().get_exclusion_polygon_count() == 0) &&
     250           0 :         (fence->polyfence().get_exclusion_circle_count() == 0)) {
     251           0 :         return false;
     252             :     }
     253         270 :     return ((fence->get_enabled_fences() & AC_FENCE_TYPE_POLYGON) > 0);
     254             : }
     255             : 
     256             : // return error message for a given error id
     257           0 : const char* AP_OADijkstra::get_error_msg(AP_OADijkstra_Error error_id) const
     258             : {
     259           0 :     switch (error_id) {
     260           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_NONE:
     261           0 :         return "no error";
     262             :         break;
     263           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY:
     264           0 :         return "out of memory";
     265             :         break;
     266           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_POINTS:
     267           0 :         return "overlapping polygon points";
     268             :         break;
     269           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_FAILED_TO_BUILD_INNER_POLYGON:
     270           0 :         return "failed to build inner polygon";
     271             :         break;
     272           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_LINES:
     273           0 :         return "overlapping polygon lines";
     274             :         break;
     275           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED:
     276           0 :         return "fence disabled";
     277             :         break;
     278           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_TOO_MANY_FENCE_POINTS:
     279           0 :         return "too many fence points";
     280             :         break;
     281           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_NO_POSITION_ESTIMATE:
     282           0 :         return "no position estimate";
     283             :         break;
     284           0 :     case AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH:
     285           0 :         return "could not find path";
     286             :         break;
     287             :     }
     288             : 
     289             :     // we should never reach here but just in case
     290           0 :     return "unknown error";
     291             : }
     292             : 
     293           0 : void AP_OADijkstra::report_error(AP_OADijkstra_Error error_id)
     294             : {
     295             :     // report errors to GCS every 5 seconds
     296           0 :     uint32_t now_ms = AP_HAL::millis();
     297           0 :     if ((error_id != AP_OADijkstra_Error::DIJKSTRA_ERROR_NONE) &&
     298           0 :         ((error_id != _error_last_id) || ((now_ms - _error_last_report_ms) > OA_DIJKSTRA_ERROR_REPORTING_INTERVAL_MS))) {
     299           0 :         const char* error_msg = get_error_msg(error_id);
     300             :         (void)error_msg;  // in case !HAL_GCS_ENABLED
     301           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Dijkstra: %s", error_msg);
     302           0 :         _error_last_id = error_id;
     303           0 :         _error_last_report_ms = now_ms;
     304             :     }
     305           0 : }
     306             : 
     307             : // check if polygon fence has been updated since we created the inner fence. returns true if changed
     308         270 : bool AP_OADijkstra::check_inclusion_polygon_updated() const
     309             : {
     310             :     // exit immediately if polygon fence is not enabled
     311         270 :     const AC_Fence *fence = AC_Fence::get_singleton();
     312         270 :     if (fence == nullptr) {
     313           0 :         return false;
     314             :     }
     315         270 :     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           1 : bool AP_OADijkstra::create_inclusion_polygon_with_margin(float margin_cm, AP_OADijkstra_Error &err_id)
     321             : {
     322           1 :     const AC_Fence *fence = AC_Fence::get_singleton();
     323             : 
     324           1 :     if (fence == nullptr) {
     325           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
     326           0 :         return false;
     327             :     }
     328             : 
     329             :     // skip unnecessary retry to build inclusion polygon if previous fence points have not changed 
     330           1 :     if (_inclusion_polygon_update_ms == fence->polyfence().get_inclusion_polygon_update_ms()) {
     331           0 :         return false;
     332             :     }
     333             : 
     334           1 :     _inclusion_polygon_update_ms = fence->polyfence().get_inclusion_polygon_update_ms();
     335             : 
     336             :     // clear all points
     337           1 :     _inclusion_polygon_numpoints = 0;
     338             : 
     339             :     // return immediately if no polygons
     340           1 :     const uint8_t num_inclusion_polygons = fence->polyfence().get_inclusion_polygon_count();
     341             : 
     342             :     // iterate through polygons and create inner points
     343           2 :     for (uint8_t i = 0; i < num_inclusion_polygons; i++) {
     344             :         uint16_t num_points;
     345           1 :         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           1 :         uint16_t new_points = 0;
     350          20 :         for (uint16_t j = 0; j < num_points; j++) {
     351             : 
     352             :             // find points before and after current point (relative to current point)
     353          19 :             const uint16_t before_idx = (j == 0) ? num_points-1 : j-1;
     354          19 :             const uint16_t after_idx = (j == num_points-1) ? 0 : j+1;
     355          19 :             Vector2f before_pt = boundary[before_idx] - boundary[j];
     356          19 :             Vector2f after_pt = boundary[after_idx] - boundary[j];
     357             : 
     358             :             // if points are overlapping fail
     359          19 :             if (before_pt.is_zero() || after_pt.is_zero() || (before_pt == after_pt)) {
     360           0 :                 err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_POINTS;
     361           0 :                 return false;
     362             :             }
     363             : 
     364             :             // scale points to be unit vectors
     365          19 :             before_pt.normalize();
     366          19 :             after_pt.normalize();
     367             : 
     368             :             // calculate intermediate point and scale to margin
     369          19 :             Vector2f intermediate_pt = after_pt + before_pt;
     370          19 :             intermediate_pt.normalize();
     371          19 :             intermediate_pt *= margin_cm;
     372             : 
     373             :             // find final point which is outside the inside polygon
     374          19 :             Vector2f temp_point = boundary[j] + intermediate_pt;
     375          19 :             if (Polygon_outside(temp_point, boundary, num_points)) {
     376           7 :                 intermediate_pt *= -1.0;
     377           7 :                 temp_point = boundary[j] + intermediate_pt;
     378           7 :                 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           0 :                     err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_LINES;
     382           0 :                     return false;
     383             :                 }
     384             :             }
     385             : 
     386             :             // don't add points in corners
     387          19 :             if (fabsf(wrap_PI(intermediate_pt.angle() - before_pt.angle())) < M_PI_2) {
     388          12 :                 continue;
     389             :             }
     390             : 
     391             :             // expand array if required
     392           7 :             if (!_inclusion_polygon_pts.expand_to_hold(_inclusion_polygon_numpoints + new_points + 1)) {
     393           0 :                 err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     394           0 :                 return false;
     395             :             }
     396             :             // add point
     397           7 :             _inclusion_polygon_pts[_inclusion_polygon_numpoints + new_points] = temp_point;
     398           7 :             new_points++;
     399             :         }
     400             : 
     401             :         // update total number of points
     402           1 :         _inclusion_polygon_numpoints += new_points;
     403             :     }
     404           1 :     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         270 : bool AP_OADijkstra::check_exclusion_polygon_updated() const
     410             : {
     411         270 :     const AC_Fence *fence = AC_Fence::get_singleton();
     412         270 :     if (fence == nullptr) {
     413           0 :         return false;
     414             :     }
     415         270 :     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           1 : bool AP_OADijkstra::create_exclusion_polygon_with_margin(float margin_cm, AP_OADijkstra_Error &err_id)
     421             : {
     422           1 :     const AC_Fence *fence = AC_Fence::get_singleton();
     423             : 
     424           1 :     if (fence == nullptr) {
     425           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
     426           0 :         return false;
     427             :     }
     428             : 
     429             :     // skip unnecessary retry to build exclusion polygon if previous fence points have not changed 
     430           1 :     if (_exclusion_polygon_update_ms == fence->polyfence().get_exclusion_polygon_update_ms()) {
     431           0 :         return false;
     432             :     }
     433             : 
     434           1 :     _exclusion_polygon_update_ms = fence->polyfence().get_exclusion_polygon_update_ms();
     435             : 
     436             : 
     437             :     // clear all points
     438           1 :     _exclusion_polygon_numpoints = 0;
     439             : 
     440             :     // return immediately if no exclusion polygons
     441           1 :     const uint8_t num_exclusion_polygons = fence->polyfence().get_exclusion_polygon_count();
     442             : 
     443             :     // iterate through exclusion polygons and create outer points
     444           2 :     for (uint8_t i = 0; i < num_exclusion_polygons; i++) {
     445             :         uint16_t num_points;
     446           1 :         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           1 :         uint16_t new_points = 0;
     451           5 :         for (uint16_t j = 0; j < num_points; j++) {
     452             : 
     453             :             // find points before and after current point (relative to current point)
     454           4 :             const uint16_t before_idx = (j == 0) ? num_points-1 : j-1;
     455           4 :             const uint16_t after_idx = (j == num_points-1) ? 0 : j+1;
     456           4 :             Vector2f before_pt = boundary[before_idx] - boundary[j];
     457           4 :             Vector2f after_pt = boundary[after_idx] - boundary[j];
     458             : 
     459             :             // if points are overlapping fail
     460           4 :             if (before_pt.is_zero() || after_pt.is_zero() || (before_pt == after_pt)) {
     461           0 :                 err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_POINTS;
     462           0 :                 return false;
     463             :             }
     464             : 
     465             :             // scale points to be unit vectors
     466           4 :             before_pt.normalize();
     467           4 :             after_pt.normalize();
     468             : 
     469             :             // calculate intermediate point and scale to margin
     470           4 :             Vector2f intermediate_pt = after_pt + before_pt;
     471           4 :             intermediate_pt.normalize();
     472           4 :             intermediate_pt *= margin_cm;
     473             : 
     474             :             // find final point which is outside the original polygon
     475           4 :             Vector2f temp_point = boundary[j] + intermediate_pt;
     476           4 :             if (!Polygon_outside(temp_point, boundary, num_points)) {
     477           4 :                 intermediate_pt *= -1;
     478           4 :                 temp_point = boundary[j] + intermediate_pt;
     479           4 :                 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           0 :                     err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OVERLAPPING_POLYGON_LINES;
     483           0 :                     return false;
     484             :                 }
     485             :             }
     486             : 
     487             :             // don't add points in corners
     488           4 :             if (fabsf(wrap_PI(intermediate_pt.angle() - before_pt.angle())) < M_PI_2) {
     489           0 :                 continue;
     490             :             }
     491             : 
     492             :             // expand array if required
     493           4 :             if (!_exclusion_polygon_pts.expand_to_hold(_exclusion_polygon_numpoints + new_points + 1)) {
     494           0 :                 err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     495           0 :                 return false;
     496             :             }
     497             :             // add point
     498           4 :             _exclusion_polygon_pts[_exclusion_polygon_numpoints + new_points] = temp_point;
     499           4 :             new_points++;
     500             :         }
     501             : 
     502             :         // update total number of points
     503           1 :         _exclusion_polygon_numpoints += new_points;
     504             :     }
     505           1 :     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         270 : bool AP_OADijkstra::check_exclusion_circle_updated() const
     511             : {
     512             :     // exit immediately if fence is not enabled
     513         270 :     const AC_Fence *fence = AC_Fence::get_singleton();
     514         270 :     if (fence == nullptr) {
     515           0 :         return false;
     516             :     }
     517         270 :     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           1 : 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           1 :     const AC_Fence *fence = AC_Fence::get_singleton();
     526           1 :     if (fence == nullptr) {
     527           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
     528           0 :         return false;
     529             :     }
     530             : 
     531             :     // clear all points
     532           1 :     _exclusion_circle_numpoints = 0;
     533             : 
     534             :     // unit length offsets for polygon points around circles
     535           1 :     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           1 :     const uint8_t num_points_per_circle = ARRAY_SIZE(unit_offsets);
     544             : 
     545             :     // expand polygon point array if required
     546           1 :     const uint8_t num_exclusion_circles = fence->polyfence().get_exclusion_circle_count();
     547           1 :     if (!_exclusion_circle_pts.expand_to_hold(num_exclusion_circles * num_points_per_circle)) {
     548           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     549           0 :         return false;
     550             :     }
     551             : 
     552             :     // iterate through exclusion circles and create outer polygon points
     553           2 :     for (uint8_t i = 0; i < num_exclusion_circles; i++) {
     554           1 :         Vector2f circle_pos_cm;
     555             :         float radius;
     556           1 :         if (fence->polyfence().get_exclusion_circle(i, circle_pos_cm, radius)) {
     557             :             // scaler to ensure lines between points do not intersect circle
     558           1 :             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           7 :             for (uint8_t j = 0; j < num_points_per_circle; j++) {
     562           6 :                 _exclusion_circle_pts[_exclusion_circle_numpoints] = circle_pos_cm + (unit_offsets[j] * scaler);
     563           6 :                 _exclusion_circle_numpoints++;
     564             :             }
     565             :         }
     566             :     }
     567             : 
     568             :     // record fence update time so we don't process these exclusion circles again
     569           1 :     _exclusion_circle_update_ms = fence->polyfence().get_exclusion_circle_update_ms();
     570             : 
     571           1 :     return true;
     572             : }
     573             : 
     574             : // returns total number of points across all fence types
     575        1453 : uint16_t AP_OADijkstra::total_numpoints() const
     576             : {
     577        1453 :     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         903 : bool AP_OADijkstra::get_point(uint16_t index, Vector2f &point) const
     582             : {
     583             :     // sanity check index
     584         903 :     if (index >= total_numpoints()) {
     585           0 :         return false;
     586             :     }
     587             : 
     588             :     // return an inclusion polygon point
     589         903 :     if (index < _inclusion_polygon_numpoints) {
     590         402 :         point = _inclusion_polygon_pts[index];
     591         402 :         return true;
     592             :     }
     593         501 :     index -= _inclusion_polygon_numpoints;
     594             : 
     595             :     // return an exclusion polygon point
     596         501 :     if (index < _exclusion_polygon_numpoints) {
     597         219 :         point = _exclusion_polygon_pts[index];
     598         219 :         return true;
     599             :     }
     600         282 :     index -= _exclusion_polygon_numpoints;
     601             : 
     602             :     // return an exclusion circle point
     603         282 :     if (index < _exclusion_circle_numpoints) {
     604         282 :         point = _exclusion_circle_pts[index];
     605         282 :         return true;
     606             :     }
     607             : 
     608             :     // we should never get here but just in case
     609           0 :     return false;
     610             : }
     611             : 
     612             : // returns true if line segment intersects polygon or circular fence
     613         206 : bool AP_OADijkstra::intersects_fence(const Vector2f &seg_start, const Vector2f &seg_end) const
     614             : {
     615             :     // return immediately if fence is not enabled
     616         206 :     const AC_Fence *fence = AC_Fence::get_singleton();
     617         206 :     if (fence == nullptr) {
     618           0 :         return false;
     619             :     }
     620             : 
     621             :     // determine if segment crosses any of the inclusion polygons
     622         206 :     uint16_t num_points = 0;
     623         293 :     for (uint8_t i = 0; i < fence->polyfence().get_inclusion_polygon_count(); i++) {
     624         206 :         const Vector2f* boundary = fence->polyfence().get_inclusion_polygon(i, num_points);
     625         206 :         if (boundary != nullptr) {
     626         206 :             Vector2f intersection;
     627         206 :             if (Polygon_intersects(boundary, num_points, seg_start, seg_end, intersection)) {
     628         119 :                 return true;
     629             :             }
     630             :         }
     631             :     }
     632             : 
     633             :     // determine if segment crosses any of the exclusion polygons
     634         152 :     for (uint8_t i = 0; i < fence->polyfence().get_exclusion_polygon_count(); i++) {
     635          87 :         const Vector2f* boundary = fence->polyfence().get_exclusion_polygon(i, num_points);
     636          87 :         if (boundary != nullptr) {
     637          87 :             Vector2f intersection;
     638          87 :             if (Polygon_intersects(boundary, num_points, seg_start, seg_end, intersection)) {
     639          22 :                 return true;
     640             :             }
     641             :         }
     642             :     }
     643             : 
     644             :     // determine if segment crosses any of the inclusion circles
     645          65 :     for (uint8_t i = 0; i < fence->polyfence().get_inclusion_circle_count(); i++) {
     646           0 :         Vector2f center_pos_cm;
     647             :         float radius;
     648           0 :         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           0 :             const float radius_cm_sq = sq(radius * 100.0f) ;
     651           0 :             if ((seg_start - center_pos_cm).length_squared() > radius_cm_sq) {
     652           0 :                 return true;
     653             :             }
     654           0 :             if ((seg_end - center_pos_cm).length_squared() > radius_cm_sq) {
     655           0 :                 return true;
     656             :             }
     657             :         }
     658             :     }
     659             : 
     660             :     // determine if segment crosses any of the exclusion circles
     661         104 :     for (uint8_t i = 0; i < fence->polyfence().get_exclusion_circle_count(); i++) {
     662          65 :         Vector2f center_pos_cm;
     663             :         float radius;
     664          65 :         if (fence->polyfence().get_exclusion_circle(i, center_pos_cm, radius)) {
     665             :             // calculate distance between circle's center and segment
     666          65 :             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          65 :             if (dist_cm <= (radius * 100.0f)) {
     670          26 :                 return true;
     671             :             }
     672             :         }
     673             :     }
     674             : 
     675             :     // if we got this far then no intersection
     676          39 :     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           1 : bool AP_OADijkstra::create_fence_visgraph(AP_OADijkstra_Error &err_id)
     683             : {
     684             :     // exit immediately if fence is not enabled
     685           1 :     const AC_Fence *fence = AC_Fence::get_singleton();
     686           1 :     if (fence == nullptr) {
     687           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_FENCE_DISABLED;
     688           0 :         return false;
     689             :     }
     690             : 
     691             :     // fail if more fence points than algorithm can handle
     692           1 :     if (total_numpoints() >= OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX) {
     693           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_TOO_MANY_FENCE_POINTS;
     694           0 :         return false;
     695             :     }
     696             : 
     697             :     // clear fence points visibility graph
     698           1 :     _fence_visgraph.clear();
     699             : 
     700             :     // calculate distance from each point to all other points
     701          17 :     for (uint8_t i = 0; i < total_numpoints() - 1; i++) {
     702          16 :         Vector2f start_seg;
     703          16 :         if (get_point(i, start_seg)) {
     704         152 :             for (uint8_t j = i + 1; j < total_numpoints(); j++) {
     705         136 :                 Vector2f end_seg;
     706         136 :                 if (get_point(j, end_seg)) {
     707             :                     // if line segment does not intersect with any inclusion or exclusion zones add to visgraph
     708         136 :                     if (!intersects_fence(start_seg, end_seg)) {
     709          35 :                         if (!_fence_visgraph.add_item({AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT, i},
     710             :                                                       {AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT, j},
     711          70 :                                                       (start_seg - end_seg).length())) {
     712             :                             // failure to add a point can only be caused by out-of-memory
     713           0 :                             err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     714           0 :                             return false;
     715             :                         }
     716             :                     }
     717             :                 }
     718             :             }
     719             :         }
     720             :     }
     721             : 
     722           1 :     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           4 : 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           4 :     visgraph.clear();
     733             : 
     734             :     // calculate distance from position to all inclusion/exclusion fence points
     735          72 :     for (uint8_t i = 0; i < total_numpoints(); i++) {
     736          68 :         Vector2f seg_end;
     737          68 :         if (get_point(i, seg_end)) {
     738          68 :             if (!intersects_fence(position, seg_end)) {
     739             :                 // line segment does not intersect with fences so add to visgraph
     740           4 :                 if (!visgraph.add_item(oaid, {AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT, i}, (position - seg_end).length())) {
     741           0 :                     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           4 :     if (add_extra_position) {
     749           2 :         if (!intersects_fence(position, extra_position)) {
     750           0 :             if (!visgraph.add_item(oaid, {AP_OAVisGraph::OATYPE_DESTINATION, 0}, (position - extra_position).length())) {
     751           0 :                 return false;
     752             :             }
     753             :         }
     754             :     }
     755             : 
     756           4 :     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          23 : void AP_OADijkstra::update_visible_node_distances(node_index curr_node_idx)
     762             : {
     763             :     // sanity check
     764          23 :     if (curr_node_idx >= _short_path_data_numpoints) {
     765           0 :         return;
     766             :     }
     767             : 
     768             :     // get current node for convenience
     769          23 :     const ShortPathNode &curr_node = _short_path_data[curr_node_idx];
     770             : 
     771             :     // for each visibility graph
     772          23 :     const AP_OAVisGraph* visgraphs[] = {&_fence_visgraph, &_destination_visgraph};
     773          69 :     for (uint8_t v=0; v<ARRAY_SIZE(visgraphs); v++) {
     774             : 
     775             :         // skip if empty
     776          46 :         const AP_OAVisGraph &curr_visgraph = *visgraphs[v];
     777          46 :         if (curr_visgraph.num_items() == 0) {
     778           0 :             continue;
     779             :         }
     780             : 
     781             :         // search visibility graph for items visible from current_node
     782         874 :         for (uint16_t i = 0; i < curr_visgraph.num_items(); i++) {
     783         828 :             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         828 :             if ((curr_node.id == item.id1) || (curr_node.id == item.id2)) {
     786         125 :                 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         125 :                 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         125 :                     const float dist_to_item_via_current_node = _short_path_data[curr_node_idx].distance_cm + item.distance_cm;
     792         125 :                     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          30 :                         _short_path_data[item_node_idx].distance_cm = dist_to_item_via_current_node;
     795          30 :                         _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         154 : bool AP_OADijkstra::find_node_from_id(const AP_OAVisGraph::OAItemID &id, node_index &node_idx) const
     806             : {
     807         154 :     switch (id.id_type) {
     808           0 :     case AP_OAVisGraph::OATYPE_SOURCE:
     809             :         // source node is always the first node
     810           0 :         if (_short_path_data_numpoints > 0) {
     811           0 :             node_idx = 0;
     812           0 :             return true;
     813             :         }
     814           0 :         break;
     815          29 :     case AP_OAVisGraph::OATYPE_DESTINATION:
     816             :         // destination is always the 2nd node
     817          29 :         if (_short_path_data_numpoints > 1) {
     818          29 :             node_idx = 1;
     819          29 :             return true;
     820             :         }
     821           0 :         break;
     822         125 :     case AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT:
     823             :         // intermediate nodes start from 3rd node
     824         125 :         if (_short_path_data_numpoints > id.id_num + 2) {
     825         125 :             node_idx = id.id_num + 2;
     826         125 :             return true;
     827             :         }
     828           0 :         break;
     829             :     }
     830             : 
     831             :     // could not find node
     832           0 :     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          25 : bool AP_OADijkstra::find_closest_node_idx(node_index &node_idx) const
     838             : {
     839          25 :     node_index lowest_idx = 0;
     840          25 :     float lowest_dist = FLT_MAX;
     841             : 
     842             :     // scan through all nodes looking for closest
     843         500 :     for (node_index i=0; i<_short_path_data_numpoints; i++) {
     844         475 :         const ShortPathNode &node = _short_path_data[i];
     845         475 :         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         375 :             continue;
     848             :         }
     849             :         // figure out the pos of this node
     850         100 :         Vector2f node_pos;
     851         100 :         float dist_with_heuristics = FLT_MAX;
     852         100 :         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         100 :             const float heuristics = (node_pos-_path_destination).length();
     856         100 :             dist_with_heuristics = node.distance_cm + heuristics;
     857             :         } else {
     858             :             // shouldn't happen
     859           0 :             return false;
     860             :         }
     861         100 :         if (dist_with_heuristics < lowest_dist) {
     862             :             // for NOW, this is the closest node
     863          48 :             lowest_idx = i;
     864          48 :             lowest_dist = dist_with_heuristics;
     865             :         }
     866             :     }
     867             : 
     868          25 :     if (lowest_dist < FLT_MAX) {
     869             :         // found the closest node
     870          25 :         node_idx = lowest_idx;
     871          25 :         return true;
     872             :     }
     873           0 :     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           2 : 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           4 :     if (!origin.get_vector_xy_from_origin_NE_cm(_path_source) ||
     884           2 :         !destination.get_vector_xy_from_origin_NE_cm(_path_destination)) {
     885           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_NO_POSITION_ESTIMATE;
     886           0 :         return false;
     887             :     }
     888             : 
     889             :     // create visgraphs of origin and destination to fence points
     890           2 :     if (!update_visgraph(_source_visgraph, {AP_OAVisGraph::OATYPE_SOURCE, 0}, _path_source, true, _path_destination)) {
     891           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     892           0 :         return false;
     893             :     }
     894           2 :     if (!update_visgraph(_destination_visgraph, {AP_OAVisGraph::OATYPE_DESTINATION, 0}, _path_destination)) {
     895           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     896           0 :         return false;
     897             :     }
     898             : 
     899             :     // expand _short_path_data if necessary
     900           2 :     if (!_short_path_data.expand_to_hold(2 + total_numpoints())) {
     901           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     902           0 :         return false;
     903             :     }
     904             : 
     905             :     // add origin and destination (node_type, id, visited, distance_from_idx, distance_cm) to short_path_data array
     906           2 :     _short_path_data[0] = {{AP_OAVisGraph::OATYPE_SOURCE, 0}, false, 0, 0};
     907           2 :     _short_path_data[1] = {{AP_OAVisGraph::OATYPE_DESTINATION, 0}, false, OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX, FLT_MAX};
     908           2 :     _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          36 :     for (uint8_t i=0; i<total_numpoints(); i++) {
     912          34 :         _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           2 :     node_index current_node_idx = 0;
     917             : 
     918             :     // update nodes visible from source point
     919           4 :     for (uint16_t i = 0; i < _source_visgraph.num_items(); i++) {
     920             :         node_index node_idx;
     921           2 :         if (find_node_from_id(_source_visgraph[i].id2, node_idx)) {
     922           2 :             _short_path_data[node_idx].distance_cm = _source_visgraph[i].distance_cm;
     923           2 :             _short_path_data[node_idx].distance_from_idx = current_node_idx;
     924             :         } else {
     925           0 :             err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH;
     926           0 :             return false;
     927             :         }
     928             :     }
     929             :     // mark source node as visited
     930           2 :     _short_path_data[current_node_idx].visited = true;
     931             : 
     932             :     // move current_node_idx to node with lowest distance
     933          25 :     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          25 :         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           2 :             break;
     939             :         }
     940             :         // update distances to all neighbours of current node
     941          23 :         update_visible_node_distances(current_node_idx);
     942             : 
     943             :         // mark current node as visited
     944          23 :         _short_path_data[current_node_idx].visited = true;
     945             :     }
     946             : 
     947             :     // extract path starting from destination
     948           2 :     bool success = false;
     949             :     node_index nidx;
     950           2 :     if (!find_node_from_id({AP_OAVisGraph::OATYPE_DESTINATION,0}, nidx)) {
     951           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH;
     952           0 :         return false;
     953             :     }
     954           2 :     _path_numpoints = 0;
     955             :     while (true) {
     956          14 :         if (!_path.expand_to_hold(_path_numpoints + 1)) {
     957           0 :             err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_OUT_OF_MEMORY;
     958           0 :             return false;
     959             :         }
     960             :         // fail if newest node has invalid distance_from_index
     961          28 :         if ((_short_path_data[nidx].distance_from_idx == OA_DIJKSTRA_POLYGON_SHORTPATH_NOTSET_IDX) ||
     962          14 :             (_short_path_data[nidx].distance_cm >= FLT_MAX)) {
     963           0 :             break;
     964             :         } else {
     965             :             // add node's id to path array
     966          14 :             _path[_path_numpoints] = _short_path_data[nidx].id;
     967          14 :             _path_numpoints++;
     968             : 
     969             :             // we are done if node is the source
     970          14 :             if (_short_path_data[nidx].id.id_type == AP_OAVisGraph::OATYPE_SOURCE) {
     971           2 :                 success = true;
     972           2 :                 break;
     973             :             } else {
     974             :                 // follow node's "distance_from_idx" to previous node on path
     975          12 :                 nidx = _short_path_data[nidx].distance_from_idx;
     976             :             }
     977             :         }
     978             :     }
     979             :     // report error in case path not found
     980           2 :     if (!success) {
     981           0 :         err_id = AP_OADijkstra_Error::DIJKSTRA_ERROR_COULD_NOT_FIND_PATH;
     982             :     }
     983             : 
     984           2 :     return success;
     985             : }
     986             : 
     987             : // return point from final path as an offset (in cm) from the ekf origin
     988         630 : bool AP_OADijkstra::get_shortest_path_point(uint8_t point_num, Vector2f& pos) const
     989             : {
     990         630 :     if ((_path_numpoints == 0) || (point_num >= _path_numpoints)) {
     991           0 :         return false;
     992             :     }
     993             : 
     994             :     // get id from path
     995         630 :     AP_OAVisGraph::OAItemID id = _path[_path_numpoints - point_num - 1];
     996             : 
     997         630 :     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         730 : 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         730 :     switch (id.id_type) {
    1005          45 :     case AP_OAVisGraph::OATYPE_SOURCE:
    1006          45 :         pos = _path_source;
    1007          45 :         return true;
    1008           2 :     case AP_OAVisGraph::OATYPE_DESTINATION:
    1009           2 :         pos = _path_destination;
    1010           2 :         return true;
    1011         683 :     case AP_OAVisGraph::OATYPE_INTERMEDIATE_POINT:
    1012         683 :         return get_point(id.id_num, pos);
    1013             :     }
    1014             : 
    1015             :     // we should never reach here but just in case
    1016           0 :     return false;
    1017             : }
    1018             : #endif // AP_FENCE_ENABLED
    1019             : 
    1020             : 
    1021             : #endif  // AP_OAPATHPLANNER_DIJKSTRA_ENABLED

Generated by: LCOV version 1.14