Skip to content

Commit 48ba3e7

Browse files
initial commit of row coverage capability (open-navigation#36)
* initial commit of row coverage capability * linting * readme
1 parent ce3f55a commit 48ba3e7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2503
-46
lines changed

README.md

+39-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
# Open Navigation's Nav2 Complete Coverage
22

3-
This package contains the Complete Coverage Task Server & auxiliary tools utilizing the [Fields2Cover](https://github.com/Fields2Cover/Fields2Cover) complete coverage planning system which includes a great deal of options in headland, swath, route, and final path planning. You can find more information about Fields2Cover (F2C) in its [ReadTheDocs Documentation](https://fields2cover.github.io/index.html). It can accept both GPS and Cartesian coordinates and publishes the field, headland, swaths, and route as separate topics in cartesian coordinates for debugging and visualization.
3+
This package contains the Complete Coverage Task Server & auxiliary tools utilizing the [Fields2Cover](https://github.com/Fields2Cover/Fields2Cover) complete coverage planning system which includes a great deal of options in headland, swath, route, and final path planning. You can find more information about Fields2Cover (F2C) in its [ReadTheDocs Documentation](https://fields2cover.github.io/index.html). It can accept both GPS and Cartesian coordinates and publishes the field, headland, swaths, and route as separate topics in cartesian coordinates for debugging and visualization. It can also compute coverage paths based on open-field polygons **or** based on annotated rows as might exist in a tree farm or other applications with both irregular and regular pre-established rows.
44

55
This capability was created by [Open Navigation LLC](https://www.opennav.org/) in partnership with [Bonsai Robotics](https://www.bonsairobotics.ai/). Bonsai Robotics builds autonomous software for machines in adverse and GPS degraded conditions utilizing vision. Bonsai Robotics funded the development of this work for their own product and has graciously allowed Open Navigation to open-source it for the community to leverage in their own systems. Please thank Bonsai Robotics for their commendable donation to the ROS community! Bonsai is hiring [here](https://www.bonsairobotics.ai/jobs).
66

77
![BonsaixOpenNavigation](https://github.com/ros-planning/navigation2/assets/14944147/b5c23851-0694-4b87-b5ab-fb7c957413f4)
88

99
This server exposes all of the features of Fields2Cover as a Lifecycle-Component Nav2 Task Server like all others within the Nav2 Framework, so it should feel very familiar to those using Nav2 already. The server is split into modular stages with factories and enum types for all known options which can be easily expanded up over time scalably. It even could be expanded to include custom coverage capabilities separate of F2C if desired. This capability is split into 5 packages:
1010

11-
- `opennav_coverage`: Contains the main Nav2 Task Server.
11+
- `opennav_coverage`: Contains the main Nav2 Task Server. Given a **field polygon**, computes swaths, routes, and paths. Best of 'open field' applications with regular rows desired.
12+
13+
- `opennav_row_coverage`: Contains another main Nav2 Task Server. Given a set of **precomputed** or **annotated** rows, computes swaths, routes, and paths. Best for applications with pre-established or irregular rows.
1214

1315
- `opennav_coverage_msgs`: Contains the action definition for the Coverage Navigator, Coverage Planner. Also contains several useful message types for F2C.
1416

15-
- `opennav_coverage_bt`: Contains the Behavior Tree Nodes and an example XML file using the Task Server to complete a simple coverage navigation task.
17+
- `opennav_coverage_bt`: Contains the Behavior Tree Nodes and an example XMLs file using the Task Server to complete a simple coverage and row coverage navigation tasks.
1618

1719
- `opennav_coverage_navigator`: Contains the BT Navigator plugin exposing `NavigateCompleteCoverage` action server analog to `NavigateToPose` and `NavigateThroughPoses`.
1820

@@ -22,8 +24,11 @@ Fields2Cover is a living library with new features planned to be added (for exam
2224

2325
[![IMAGE ALT TEXT](./opennav_coverage_demo/test/demo.png)](https://www.youtube.com/watch?v=XC_qf5AyNpU)
2426

25-
PS: Click on image to see the video! :-)
27+
Have pre-annotated rows due to physical constraints and want to still compute the patterned route & feasible paths between them? Extra wacky to show that they don't need to be parallel nor regularly spaced!
28+
29+
[![IMAGE ALT TEXT](./opennav_coverage_demo/test/demo_rows.png)](https://www.youtube.com/watch?v=NMznTft56jE)
2630

31+
PS: Click on either image to see the demo videos! :-)
2732

2833
## Interfaces
2934

@@ -35,14 +40,16 @@ This contains `generate_headland`, `generate_route`, and `generate_path` about w
3540

3641
Each of the stages (including `generate_swaths`, which is always on) has its own `_mode` message in the action containing its potential parameters to specify a mode. If not modified, it uses the parameters set in the server at launch time or after dynamic reconfiguration. See the parameter information below or the message files for complete details.
3742

38-
Finally, it contains the polygon information. This can be represented either as GML files, with [an example in `opennav_coverage/test`](./opennav_coverage/test/test_field.xml), or as a polygon in the message itself. If using GML files, set `goal.use_gml_file = true`. If your GML file contains multiple fields, set the ID of which to use with `goal.gml_field_id = id`, whereas the number is its ordered position in the file.
43+
Finally, it contains the polygon information. This can be represented either as GML files, with [an example in `opennav_coverage/test`](./opennav_coverage/test/test_field.xml), or as a polygon in the message itself. If using GML files, set `goal.use_gml_file = true`.
3944

4045
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.
4146

4247
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`.
4348

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

51+
Note that `SwathMode` settings are to be paired with the `opennav_coverage` server with polygons, while the `RowSwathMode` settings are to be paired with the `opennav_row_coverage` server based on annotated rows.
52+
4653
### NavigateCompleteCoverage
4754

4855
The Coverage Navigator calls the `ComputeCoveragePath` action within its BT XML. This navigator plugin exists to expose to the application layer the fields required to do Coverage-type navigation tasks rather than go-to-pose type tasks. Thus, this Action does not contain a "goal" or "start" pose, but the field filepath or polygon of interest for coverage navigation. See the section above for discussion on those types. It also contains a `goal.behavior_tree` field to specify which behavior tree to navigate using -- if not the default.
@@ -68,20 +75,21 @@ The complete set of options are exposed as both dynamic parameters and through t
6875
| operation_width | Width of implement or task for coverage | double |
6976
| min_turning_radius | Minimum turning radius for path planning | double |
7077
| linear_curv_change | Max linear curvature change | double |
71-
| default_allow_overlap | Whether to allow some coverage overlap in final pass to fill space by default | bool |
78+
| default_allow_overlap | Whether to allow some coverage overlap in final pass to fill space by default. Only for `opennav_coverage` | bool |
7279
| default_custom_order | Default custom order of swaths -> route in CUSTOM mode | `vector<int>` |
73-
| default_headland_type | Default headland mode. Option: CONSTANT | String |
74-
| default_headland_width | Default headland width to remove from field | double |
80+
| default_headland_type | Default headland mode. Option: CONSTANT. Only for `opennav_coverage` | String |
81+
| default_headland_width | Default headland width to remove from field. Only for `opennav_coverage` | double |
7582
| default_path_continuity_type | Default path continuity mode. Option: DISCONTINUOUS, CONTINUOUS | String |
7683
| default_path_type | Default path mode. Option: DUBIN, REEDS_SHEPP | String |
7784
| default_route_type | Default route mode. Option: BOUSTROPHEDON, SNAKE, SPIRAL, CUSTOM | String |
7885
| default_spiral_n | Default number for spiraling, when set as default | int |
79-
| default_step_angle | Default angular step to attempt swath generating in BRUTE_FORCE mode | double |
80-
| default_swath_angle | Default swath angle to use when known for swathc generation in SET_ANGLE mode | double |
81-
| default_swath_angle_type | Default swath angle computation type. Options: BRUTE_FORCE,SET_ANGLE | String |
82-
| default_swath_type | Default swath computation objective. Option: LENGTH, COVERAGE, NUMBER | String |
86+
| default_step_angle | Default angular step to attempt swath generating in BRUTE_FORCE mode. Only for `opennav_coverage` | double |
87+
| default_swath_angle | Default swath angle to use when known for swathc generation in SET_ANGLE mode. Only for `opennav_coverage` | double |
88+
| default_swath_angle_type | Default swath angle computation type. Options: BRUTE_FORCE,SET_ANGLE. Only for `opennav_coverage` | String |
89+
| default_swath_type | Default swath computation objective. Option: LENGTH, COVERAGE, NUMBER for `opennav_coverage`; Option: OFFSET, CENTER, ROWSARESWATHS for `opennav_row_coverage` | String |
8390
| default_turn_point_distance | Distance between points in path turns between swaths | double |
84-
91+
| default_offset | Offset to use for computing swaths from annotated rows. Only for `opennav_row_coverage` | double |
92+
| order_ids | For `opennav_row_coverage`, whether to reorder the parsed rows in the order of their `id`s | bool |
8593

8694
### CoverageNavigator
8795

@@ -122,6 +130,24 @@ All type string.
122130

123131
N/A
124132

133+
134+
#### A Quick Note On Skipping
135+
136+
Rows(r) and swaths(s) are numbered as such. r1, s1, r2, s2, .... rN-1, sN-1, rN.
137+
In order to skip particular rows the 'opennav_coverage_msgs/RowSwathMode' provides 'skip_ids'
138+
to be populated. For example, if the skip ids were set to {1, 3} for five rows the output would be the following: s2, s4.
139+
140+
### Swath Generation
141+
142+
As noted about `opennav_row_coverage` provides three ways to compute swaths which are `CENTER`, `OFFSET` and `SWATHSAREROWS`.
143+
144+
The `CENTER` generator iterates through each row, calculating a center between consecutive rows.
145+
146+
The `OFFSET` generator iterates through each row, calculating a relative offset between consecutive rows.
147+
148+
The `SWATHSAREROWS` generator iterates through each row and uses that row as the swath.
149+
150+
125151
## Citation
126152

127153
If you use this work, please make sure to cite both Nav2 and Fields2Cover:

opennav_coverage/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ install(DIRECTORY include/
8686
)
8787

8888
install(FILES test/test_field.xml
89+
test/cartesian_test_field.xml
90+
test/irregular_test_field.xml
8991
DESTINATION share/${PROJECT_NAME}/
9092
)
9193

opennav_coverage/include/opennav_coverage/types.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ typedef F2CRobot Robot;
3535
typedef F2CLinearRing Polygon;
3636
typedef F2CPoint Point;
3737
typedef f2c::types::PathState PathState;
38+
typedef f2c::types::LineString LineString;
39+
typedef F2CLineString LineString;
3840

3941
typedef std::shared_ptr<f2c::hg::HeadlandGeneratorBase> HeadlandGeneratorPtr;
4042
typedef std::shared_ptr<f2c::obj::SGObjective> SwathObjectivePtr;

opennav_coverage/include/opennav_coverage/utils.hpp

+19
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ inline opennav_coverage_msgs::msg::PathComponents toCoveragePathMsg(
9898
Swaths swaths = raw_swaths;
9999
if (!is_cartesian) {
100100
swaths = f2c::Transform::transformToPrevCRS(raw_swaths, field);
101+
} else {
102+
swaths.moveTo(field.getRefPoint());
101103
}
102104

103105
for (unsigned int i = 0; i != swaths.size(); i++) {
@@ -136,6 +138,8 @@ inline opennav_coverage_msgs::msg::PathComponents toCoveragePathMsg(
136138
Path path = raw_path;
137139
if (!is_cartesian) {
138140
path = f2c::Transform::transformToPrevCRS(raw_path, field);
141+
} else {
142+
path.moveTo(field.getRefPoint());
139143
}
140144

141145
PathSectionType curr_state = path.states[0].type;
@@ -218,6 +222,8 @@ inline nav_msgs::msg::Path toNavPathMsg(
218222
Path path = raw_path;
219223
if (!is_cartesian) {
220224
path = f2c::Transform::transformToPrevCRS(raw_path, field);
225+
} else {
226+
path.moveTo(field.getRefPoint());
221227
}
222228

223229
for (unsigned int i = 0; i != path.states.size(); i++) {
@@ -257,6 +263,19 @@ inline nav_msgs::msg::Path toNavPathMsg(
257263
return msg;
258264
}
259265

266+
/**
267+
* @brief Converts full path to nav_msgs/path message in cartesian UTM frame
268+
* @param path Full path to convert
269+
* @param header header
270+
* @return nav_msgs/Path Path
271+
*/
272+
inline nav_msgs::msg::Path toCartesianNavPathMsg(
273+
const Path & raw_path,
274+
const std_msgs::msg::Header & header, const float & pt_dist)
275+
{
276+
return toNavPathMsg(raw_path, F2CField(), header, true, pt_dist);
277+
}
278+
260279
/**
261280
* @brief Converts action goal coordinates into a Field type
262281
* Note that this may be in GPS or cartesian coordinates!

opennav_coverage/include/opennav_coverage/visualizer.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ class Visualizer
6262

6363
void visualize(
6464
const Field & total_field, const Field & no_headland_field,
65-
const std::shared_ptr<ComputeCoveragePath::Result> & result,
66-
const std_msgs::msg::Header & header);
65+
const Point & ref_pt, const nav_msgs::msg::Path & path,
66+
const Swaths swaths, const std_msgs::msg::Header & header);
6767

6868
rclcpp::Publisher<nav_msgs::msg::Path>::SharedPtr nav_plan_pub_;
6969
rclcpp::Publisher<geometry_msgs::msg::PolygonStamped>::SharedPtr headlands_pub_;

opennav_coverage/src/coverage_server.cpp

+14-6
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ bool CoverageServer::validateGoal(
134134
if (req->generate_path && !req->generate_route) {
135135
return false;
136136
}
137+
138+
// Swath Mode needs to be considered, not Row Swath Mode
139+
if (req->row_swath_mode.mode != "UNKNOWN") {
140+
return false;
141+
}
142+
137143
return true;
138144
}
139145

@@ -168,9 +174,7 @@ void CoverageServer::computeCoveragePath()
168174
F2CField master_field;
169175
std::string frame_id;
170176
if (goal->use_gml_file) {
171-
F2CFields parse_field;
172-
f2c::Parser::importGml(goal->gml_field, parse_field);
173-
master_field = parse_field[goal->gml_field_id];
177+
master_field = f2c::Parser::importFieldGml(goal->gml_field, true);
174178
frame_id = master_field.coord_sys;
175179
} else {
176180
master_field = util::getFieldFromGoal(goal);
@@ -201,13 +205,14 @@ void CoverageServer::computeCoveragePath()
201205
std_msgs::msg::Header header;
202206
header.stamp = now();
203207
header.frame_id = frame_id;
208+
Path path;
204209
if (goal->generate_route) {
205210
Swaths route = route_gen_->generateRoute(swaths, goal->route_mode);
206211

207212
// (4) Optional: Generate connection turns between ordered swaths
208213
// Converts UTM back to GPS, if necessary, for action returns
209214
if (goal->generate_path) {
210-
Path path = path_gen_->generatePath(route, goal->path_mode);
215+
path = path_gen_->generatePath(route, goal->path_mode);
211216
result->coverage_path =
212217
util::toCoveragePathMsg(path, master_field, header, cartesian_frame_);
213218
result->nav_path = util::toNavPathMsg(
@@ -224,8 +229,11 @@ void CoverageServer::computeCoveragePath()
224229
auto cycle_duration = this->now() - start_time;
225230
result->planning_time = cycle_duration;
226231

227-
// Visualize in native coordinates
228-
visualizer_->visualize(field, field_no_headland, result, header);
232+
// Visualize in Cartesian coordinates for debugging
233+
visualizer_->visualize(
234+
field, field_no_headland, master_field.getRefPoint(),
235+
util::toCartesianNavPathMsg(path, header, path_gen_->getTurnPointDistance()),
236+
swaths, header);
229237
action_server_->succeeded_current(result);
230238
} catch (CoverageException & e) {
231239
RCLCPP_ERROR(get_logger(), "Invalid mode set: %s", e.what());

0 commit comments

Comments
 (0)