7 This module offers GUI to specify a polygon for coverage path planning. 11 from __future__
import print_function
22 from geometry_msgs.msg
import Point
23 from std_msgs.msg
import Float64
26 from cpp_uav.srv
import Torres16
31 if matplotlib.__version__ >=
"2.1.0":
34 from matplotlib
import pyplot
as plt
35 from matplotlib.patches
import Polygon
36 from matplotlib.widgets
import Button
37 from matplotlib.widgets
import TextBox
39 print(
"Matplotlib 2.1.0 or newer is required to run this node.")
40 print(
"Please update or install Matplotlib.")
46 GUI class to specify a polygon 55 self.
fig = plt.figure(figsize=(10, 8))
59 self.
axis = self.fig.add_subplot(111)
62 self.axis.set_ylim([-100, 100])
63 self.axis.set_xlim([-100, 100])
66 self.axis.set_aspect(
'equal', adjustable=
"box")
68 self.fig.subplots_adjust(top=0.95, bottom=0.35, right=0.79)
87 self.
lines = {
"line": self.axis.plot([], [],
"-")[0],
88 "dot": self.axis.plot([], [],
"o")[0],
89 "path": self.axis.plot([], [],
"-")[0],
93 self.
lines[
"line"].figure.canvas.mpl_connect(
94 'button_press_event', self)
95 self.
lines[
"dot"].figure.canvas.mpl_connect(
'button_press_event', self)
104 "vertices_y": list(),
118 "image_resolution_w": 320,
119 "angle_of_view": 45.0,
139 Float64(footprint_width.data *
143 "horizontal_overwrap": Float64(0.3),
144 "vertical_overwrap": Float64(0.2)}
152 Button(plt.axes([0.8, 0.80, 0.15, 0.075]),
155 Button(plt.axes([0.8, 0.69, 0.15, 0.075]),
158 Button(plt.axes([0.8, 0.58, 0.15, 0.075]),
176 TextBox(plt.axes([0.25, 0.25, 0.1, 0.05]),
179 "image_resolution_w_box":
180 TextBox(plt.axes([0.6, 0.25, 0.1, 0.05]),
184 TextBox(plt.axes([0.25, 0.175, 0.1, 0.05]),
185 "Angle of view [deg]",
188 TextBox(plt.axes([0.6, 0.175, 0.1, 0.05]),
191 "horizontal_overwrap_box":
192 TextBox(plt.axes([0.25, 0.1, 0.1, 0.05]),
193 "Horizontal Overwrap [0.0 - 1.0]",
195 "vertical_overwrap_box":
196 TextBox(plt.axes([0.6, 0.1, 0.1, 0.05]),
197 "Vertical Overwrap [0.0 - 1.0]",
201 self.
text_boxes[
"image_resolution_h_box"].on_submit(
203 self.
text_boxes[
"image_resolution_w_box"].on_submit(
205 self.
text_boxes[
"angle_of_view_box"].on_submit(
208 self.
text_boxes[
"horizontal_overwrap_box"].on_submit(
210 self.
text_boxes[
"vertical_overwrap_box"].on_submit(
225 "footprint_length_text":
227 "Footprint Length [m]:\n " +
229 "footprint_width_text":
231 "Footprint Width [m]:\n " +
242 Callback function for button_press_event 243 @param event MouseEvent object 246 if event.inaxes != self.
lines[
"line"].axes:
250 self.
points[
"vertices_x"].append(event.xdata)
251 self.
points[
"vertices_y"].append(event.ydata)
253 self.
lines[
"dot"].set_data(self.
points[
"vertices_x"],
254 self.
points[
"vertices_y"])
255 self.
lines[
"dot"].figure.canvas.draw()
257 elif not self.
points[
"start"]:
259 self.
points[
"start"] = Point()
260 self.
points[
"start"].x = event.xdata
261 self.
points[
"start"].y = event.ydata
264 self.
labels[
"start_point"] = self.axis.text(
265 event.xdata, event.ydata,
"Start", color=
"red", fontsize=16)
266 self.
lines[
"dot"].set_data(self.
points[
"vertices_x"] + [event.xdata],
267 self.
points[
"vertices_y"] + [event.ydata])
268 self.
lines[
"dot"].figure.canvas.draw()
270 rospy.logwarn(
"Unable to register points anymore.")
274 Update values of coverage parameters 285 Update texts of coverage parameters 287 self.
labels[
"aspect_ratio_text"].set_text(
"Aspect ratio:\n " +
289 self.
labels[
"footprint_length_text"].set_text(
"Footprint Length [m]:\n " +
291 self.
labels[
"footprint_width_text"].set_text(
"Footprint Width [m]:\n " +
293 self.
labels[
"footprint_length_text"].figure.canvas.draw()
297 Callback function for "Draw Polygon" button 298 @param event MouseEvent object 301 if len(self.
points[
"vertices_x"]) < 3:
302 rospy.logerr(
"Unable to make a polygon.")
305 self.
lines[
"line"].set_data(self.
points[
"vertices_x"] + self.
points[
"vertices_x"][0:1],
306 self.
points[
"vertices_y"] + self.
points[
"vertices_y"][0:1])
307 self.
lines[
"line"].figure.canvas.draw()
313 Callback function for "Calculate CP" button 314 @param event MouseEvent object 319 if not self.
points[
"start"]:
320 rospy.logwarn(
"Choose start point.")
328 rospy.loginfo(
"Waiting for Server Node.")
330 rospy.wait_for_service(
"cpp_torres16",
332 except rospy.ROSException:
333 rospy.logerr(
"Server not found.")
339 except rospy.ServiceException
as ex:
340 rospy.logerr(str(ex))
349 for x_coord, y_coord
in zip(self.
points[
"vertices_x"],
350 self.
points[
"vertices_y"]):
354 vertices.append(point)
364 self.
points[
"waypoints"] = ret.path
368 for num, point
in enumerate(self.
points[
"waypoints"]):
370 waypoint_xs.append(self.
points[
"start"].x)
371 waypoint_ys.append(self.
points[
"start"].y)
372 self.
labels[
"SP"] = self.axis.text(
373 point.x, point.y,
"SP", color=
"red", fontsize=16)
374 waypoint_xs.append(point.x)
375 waypoint_ys.append(point.y)
376 if num == len(self.
points[
"waypoints"]) - 1:
377 waypoint_xs.append(self.
points[
"start"].x)
378 waypoint_ys.append(self.
points[
"start"].y)
379 self.
labels[
"EP"] = self.axis.text(
380 point.x, point.y,
"EP", color=
"red", fontsize=16)
382 arr = np.ndarray([len(subpolygon.points), 2])
383 for num, point
in enumerate(subpolygon.points):
384 arr[num][0] = point.x
385 arr[num][1] = point.y
386 patch = Polygon(xy=arr, alpha=0.5, edgecolor=
"navy")
387 self.axis.add_patch(patch)
388 self.patches.append(patch)
390 self.
lines[
"path"].set_data(waypoint_xs, waypoint_ys)
391 self.
lines[
"path"].figure.canvas.draw()
394 except rospy.ServiceException
as ex:
395 rospy.logerr(str(ex))
400 Callback function for "Clear" button 401 @param event MouseEvent object 405 self.
points[
"vertices_x"] = []
406 self.
points[
"vertices_y"] = []
411 self.
points[
"start"] =
None 414 self.
points[
"waypoints"] = []
415 self.
points[
"subpolygons"] = []
418 self.
lines[
"dot"].set_data(
420 self.
lines[
"line"].set_data(
422 self.
lines[
"path"].set_data([], [])
424 if self.
labels[
"start_point"]:
425 self.
labels[
"start_point"].remove()
428 self.
labels[
"SP"].remove()
429 self.
labels[
"EP"].remove()
439 self.
lines[
"dot"].figure.canvas.draw()
440 self.
lines[
"line"].figure.canvas.draw()
441 self.
lines[
"path"].figure.canvas.draw()
445 Called when content of "Image Height" is submitted 446 @param event Content of TextBox 459 Called when content of "Image Width" is submitted 460 @param event Content of TextBox 473 Called when content of "Angle of view" is submitted 474 @param event Content of TextBox 487 Called when content of "Height" is submitted 488 @param event Content of TextBox 501 Called when content of "Horizontal overwrap" is submitted 502 @param event Content of TextBox 506 if 0 < float(event) < 1.0:
517 Called when content of "Vertical overwrap" is submitted 518 @param event Content of TextBox 522 if 0 < float(event) < 1.0:
536 rospy.init_node(
'specify_node', anonymous=
True)
542 if __name__ ==
'__main__':
def vertical_overwrap_update(self, event)
Called when content of "Vertical overwrap" is submitted.
def image_resolution_w_update(self, event)
Called when content of "Image Width" is submitted.
def update_param_texts(self)
Update texts of coverage parameters.
def height_update(self, event)
Called when content of "Height" is submitted.
def init_node()
Initialize a node.
def angle_of_view_update(self, event)
Called when content of "Angle of view" is submitted.
def horizontal_overwrap_update(self, event)
Called when content of "Horizontal overwrap" is submitted.
def clear_figure(self, event)
Callback function for "Clear" button.
def draw_polygon(self, event)
Callback function for "Draw Polygon" button.
def image_resolution_h_update(self, event)
Called when content of "Image Height" is submitted.
def update_params(self)
Update values of coverage parameters.
def calculate_path(self, event)
Callback function for "Calculate CP" button.
def __call__(self, event)
Callback function for button_press_event.
def __init__(self)
Constructor.
GUI class to specify a polygon.