Skip to content

Commit 4cac734

Browse files
committed
Added PathComponentsIterator to iterate over PathComponents easily and tracks state
1 parent 28fd076 commit 4cac734

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Finally, it contains the polygon information. This can be represented either as
3232

3333
When setting the polygon (`goal.polygons`), this is a vector of polygons. If only considering a bounding field, only populate the first field shape. If there are internal voids, use subsequent polygons to indicate them. The coordinate type has `axis1` and `axis2` instead of X and Y as the server can process both GPS and cartesian coordinates. If specifying the polygon outside of GML files, you must specify the frame of reference of the polygon using the `goal.frame_id` field. This is not used for GML files as those should contain the frame within it.
3434

35-
The result returns a `result.nav_path` -- which is a `nav_msgs/Path` containing the coverage path requested **only if** all `generate_path` is `true`. This can be followed by a local trajectory planner or controller directly. This is what is used in the `opennav_coverage_bt` examples for basic coverage navigation. It also returns `result.coverage_path` which contains an ordered set of swaths and paths to connect them (if applicable settings enabled) which can be used for more task-specific navigation. For example, navigating with a tool down or enabled on swaths and raised in turns to connect to other swaths. A utility is provided in `opennav_coverage/utils.hpp` for iterating through this custom `coverage_path` for convenience.
35+
The result returns a `result.nav_path` -- which is a `nav_msgs/Path` containing the coverage path requested **only if** all `generate_path` is `true`. This can be followed by a local trajectory planner or controller directly. This is what is used in the `opennav_coverage_bt` examples for basic coverage navigation. It also returns `result.coverage_path` which contains an ordered set of swaths and paths to connect them (if applicable settings enabled) which can be used for more task-specific navigation. For example, navigating with a tool down or enabled on swaths and raised in turns to connect to other swaths. A utility is provided in `opennav_coverage/utils.hpp` for iterating through this custom `coverage_path` for convenience, `PathComponentsIterator`.
3636

3737
It also returns an error code, if any error occurred and the total planning time for metrics analysis.
3838

opennav_coverage/include/opennav_coverage/utils.hpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <string>
2020
#include <algorithm>
2121
#include <memory>
22+
#include <utility>
2223

2324
#include "fields2cover.h" // NOLINT
2425
#include "rclcpp/rclcpp.hpp"
@@ -276,6 +277,82 @@ inline void toUpper(std::string & string)
276277
std::transform(string.begin(), string.end(), string.begin(), ::toupper);
277278
}
278279

280+
/**
281+
* @brief A Path Components iterator object to get next turn/swath from action return
282+
*
283+
* Example Use:
284+
for (opennav_coverage::util::PathComponentsIterator it(msg); it.isValid(); it.advance()) {
285+
auto curr_row_info = it.getNext();
286+
287+
// Always should be valid
288+
(void)std::get<0>(curr_row_info)->start;
289+
290+
if (std::get<1>(curr_row_info)) {
291+
// Always should be before last
292+
(void)std::get<1>(curr_row_info)->poses;
293+
}
294+
}
295+
296+
auto last_row_info = it.getNext();
297+
ASSERT(std::get<1>(last_row_info) == nullptr);
298+
*/
299+
class PathComponentsIterator
300+
{
301+
public:
302+
/**
303+
* @brief A Path Components iterator constructor
304+
* @param PathComponents object to iterate over
305+
*/
306+
explicit PathComponentsIterator(opennav_coverage_msgs::msg::PathComponents & msg)
307+
: path_components_(msg), idx_(0)
308+
{
309+
if (fabs(path_components_.swaths.size() - path_components_.turns.size()) > 1) {
310+
throw std::runtime_error("PathComponents size not valid for iteration!");
311+
} else if (!path_components_.contains_turns) {
312+
throw std::runtime_error("PathComponents cannot be iterated over without turns!");
313+
} else if (!path_components_.swaths_ordered) {
314+
throw std::runtime_error("PathComponents cannot be iterated over without ordered swaths!");
315+
}
316+
317+
max_idx_ = path_components_.swaths.size();
318+
}
319+
320+
/**
321+
* @brief For condition if still valid to continue iterating
322+
*/
323+
bool isValid()
324+
{
325+
return idx_ <= max_idx_;
326+
}
327+
328+
/**
329+
* @brief For loop marching
330+
*/
331+
void advance()
332+
{
333+
idx_++;
334+
}
335+
336+
/**
337+
* @brief Get the data of the current iteration
338+
* @return returns a pair of pointers to the current swath and its next turn
339+
* If at the end, the turn is nullptr, so be sure to check it!
340+
*/
341+
std::pair<opennav_coverage_msgs::msg::Swath *, nav_msgs::msg::Path *>
342+
getNext()
343+
{
344+
if (idx_ < max_idx_ - 1) {
345+
return std::make_pair(&path_components_.swaths[idx_], &path_components_.turns[idx_]);
346+
} else {
347+
return std::make_pair(&path_components_.swaths[idx_], nullptr);
348+
}
349+
}
350+
351+
opennav_coverage_msgs::msg::PathComponents & path_components_;
352+
unsigned int idx_;
353+
unsigned int max_idx_;
354+
};
355+
279356
} // namespace util
280357

281358
} // namespace opennav_coverage

opennav_coverage/test/test_utils.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#include <utility>
16+
1517
#include "gtest/gtest.h"
1618
#include "rclcpp/rclcpp.hpp"
1719
#include "opennav_coverage/utils.hpp"
@@ -193,4 +195,43 @@ TEST(UtilsTests, TestgetFieldFromGoal)
193195
EXPECT_EQ(field2.field.getGeometry(0).getGeometry(1).size(), 3u);
194196
}
195197

198+
TEST(UtilsTests, TestPathComponentsIterator)
199+
{
200+
opennav_coverage_msgs::msg::PathComponents msg;
201+
202+
// Sizes don't match
203+
msg.swaths.resize(4);
204+
EXPECT_THROW(opennav_coverage::util::PathComponentsIterator it(msg), std::runtime_error);
205+
206+
// Isn't properly filled in to contain turns
207+
msg.turns.resize(4);
208+
EXPECT_THROW(opennav_coverage::util::PathComponentsIterator it(msg), std::runtime_error);
209+
210+
// Isn't properly filled in to be ordered
211+
msg.contains_turns = true;
212+
EXPECT_THROW(opennav_coverage::util::PathComponentsIterator it(msg), std::runtime_error);
213+
214+
// Now should work
215+
msg.swaths_ordered = true;
216+
EXPECT_NO_THROW(opennav_coverage::util::PathComponentsIterator it(msg));
217+
218+
unsigned int i = 0;
219+
opennav_coverage::util::PathComponentsIterator it(msg);
220+
for (; it.isValid(); it.advance()) {
221+
auto curr_row_info = it.getNext();
222+
223+
// Always should be valid
224+
(void)std::get<0>(curr_row_info)->start;
225+
226+
if (i < 3) {
227+
// Always should be before last
228+
(void)std::get<1>(curr_row_info)->poses;
229+
}
230+
i++;
231+
}
232+
233+
auto last_row_info = it.getNext();
234+
EXPECT_EQ(std::get<1>(last_row_info), nullptr);
235+
}
236+
196237
} // namespace opennav_coverage

0 commit comments

Comments
 (0)