<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.openmower.de/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=CSobel</id>
	<title>Open Mower Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.openmower.de/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=CSobel"/>
	<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Special:Contributions/CSobel"/>
	<updated>2026-05-13T10:11:22Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.1</generator>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=343</id>
		<title>Modify map using drone footage</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=343"/>
		<updated>2023-06-25T12:23:32Z</updated>

		<summary type="html">&lt;p&gt;CSobel: added more area naming examples&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Introduction ===&lt;br /&gt;
This tutorial shows how to use an orthophoto (generated by taking aerial pictures with a drone) and modify the open mower map. In my case I recorded the boundary of my lawn area driving the mower around and later divided the area and added obstacles using the drone footage.&lt;br /&gt;
&lt;br /&gt;
[[File:Openmower map boundary orthophoto.png|none|thumb|openmower map boundary with orthophoto layer]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Generate an orthophoto ===&lt;br /&gt;
I took about 50 pictures with a drone ca. 10m above ground and generated an orthophoto using OpenDroneMap WebODM&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can read about the process here: https://docs.opendronemap.org/tutorials/&lt;br /&gt;
&lt;br /&gt;
OpenDroneMap WebODM runs in a docker container and can be found here: https://www.opendronemap.org/webodm/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can export an orthophoto .tif format there&lt;br /&gt;
&lt;br /&gt;
=== convert map.bag into GPX ===&lt;br /&gt;
Obviously back up your map.bag&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I wrote a python script to convert the map area coordinates into utm coordinates. I ran this on the robots raspberry pi. In reads the map.bag from .ros folder and exports a .gpx format file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Following python packages are needed&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pip install utm gpxpy&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coodinates in the script. I use the roof mouned RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs and run the python script.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert openmower ros map.bag to gpx file&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
from codecs import latin_1_decode&lt;br /&gt;
import rosbag&lt;br /&gt;
import utm&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import math&lt;br /&gt;
import os&lt;br /&gt;
from tf.transformations import euler_from_quaternion&lt;br /&gt;
&lt;br /&gt;
bag = rosbag.Bag(&#039;/home/pi/.ros/map.bag&#039;)&lt;br /&gt;
# bag = rosbag.Bag(&#039;output.bag&#039;)&lt;br /&gt;
&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
&lt;br /&gt;
topics = bag.get_type_and_topic_info()[1].keys()&lt;br /&gt;
print(topics)&lt;br /&gt;
&lt;br /&gt;
gpx = gpxpy.gpx.GPX()&lt;br /&gt;
gpx.name = &#039;open_mower_map&#039;&lt;br /&gt;
gpx.description = &#039;Map data from openmower map.bag&#039;&lt;br /&gt;
&lt;br /&gt;
# base point (e.g. gps antenna)&lt;br /&gt;
# base_point = gpxpy.gpx.GPXWaypoint(latitude=lat0, longitude=lon0, name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
# gpx.waypoints.append(base_point)&lt;br /&gt;
&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
gpx.tracks.append(gpx_track)&lt;br /&gt;
gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
gpx_track.segments.append(gpx_segment)&lt;br /&gt;
gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat0, lon0, elevation=0))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Loop through areas and obstacles&lt;br /&gt;
area_counter = 0&lt;br /&gt;
&lt;br /&gt;
for area_type in [&#039;mowing_areas&#039;, &#039;navigation_areas&#039;]:&lt;br /&gt;
    for topic, msg, t in bag.read_messages(topics=[area_type]):&lt;br /&gt;
        print(&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        # Create track in GPX:&lt;br /&gt;
        gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        gpx.tracks.append(gpx_track)&lt;br /&gt;
        # Create segment in GPX track:&lt;br /&gt;
        gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
        gpx_track.segments.append(gpx_segment)&lt;br /&gt;
&lt;br /&gt;
        # outline&lt;br /&gt;
        for point in msg.area.points:&lt;br /&gt;
            # Create points:&lt;br /&gt;
            lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
            gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
&lt;br /&gt;
        # obstacles&lt;br /&gt;
        obstacle_counter = 0&lt;br /&gt;
        for obst in msg.obstacles:&lt;br /&gt;
            print(&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            # Create track in GPX:&lt;br /&gt;
            gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            gpx.tracks.append(gpx_track)&lt;br /&gt;
            # Create segment in GPX track:&lt;br /&gt;
            gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
            gpx_track.segments.append(gpx_segment)&lt;br /&gt;
            for point in obst.points:&lt;br /&gt;
                # Create points:&lt;br /&gt;
                lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
                gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
            obstacle_counter += 1&lt;br /&gt;
        area_counter += 1&lt;br /&gt;
&lt;br /&gt;
# docking point&lt;br /&gt;
for topic, msg, t in bag.read_messages(topics=[&#039;docking_point&#039;]):&lt;br /&gt;
    # print(msg)&lt;br /&gt;
    print(&amp;quot;parsing docking point&amp;quot;)&lt;br /&gt;
    quat_list = [msg.orientation.x, msg.orientation.y, msg.orientation.z, msg.orientation.w]&lt;br /&gt;
    (roll, pitch, yaw) = euler_from_quaternion(quat_list)&lt;br /&gt;
    # print((roll, pitch, yaw))&lt;br /&gt;
    x2 = msg.position.x + math.cos(yaw)&lt;br /&gt;
    y2 = msg.position.y + math.sin(yaw)&lt;br /&gt;
&lt;br /&gt;
    lat1, lon1 = utm.to_latlon(x0 + msg.position.x, y0 + msg.position.y, zone, ut)&lt;br /&gt;
    lat2, lon2 = utm.to_latlon(x0 + x2,y0 + y2, zone, ut)&lt;br /&gt;
&lt;br /&gt;
    gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;docking_point&amp;quot;)&lt;br /&gt;
    gpx.tracks.append(gpx_track)&lt;br /&gt;
    gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
    gpx_track.segments.append(gpx_segment)&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat1, lon1, elevation=0))&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat2, lon2, elevation=0))&lt;br /&gt;
&lt;br /&gt;
print(&#039;Created GPX file&#039;)&lt;br /&gt;
with open(&amp;quot;map_bag.gpx&amp;quot;, &amp;quot;w&amp;quot;) as f:&lt;br /&gt;
    f.write(gpx.to_xml())&lt;br /&gt;
&lt;br /&gt;
bag.close()&lt;br /&gt;
print(&#039;Done&#039;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== load GPX file into JOSM ===&lt;br /&gt;
Install the Java OpenStreetMap Editor from:&lt;br /&gt;
&lt;br /&gt;
https://josm.openstreetmap.de/wiki/Download&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start JOSM and install the PicLayer Plugin. For this go to &#039;&#039;Edit-&amp;gt;Preferences-&amp;gt;Plugins&#039;&#039; and search for PicLayer and tick the checkbox.&lt;br /&gt;
&lt;br /&gt;
Open your .gpx file, you shoud see the recorded boundaries&lt;br /&gt;
[[File:JOSM imported GPX file.png|none|thumb|Boundaries visible in JOSM from imported GPX file]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Right click on the Layer output.gpx and select &amp;quot;convert to data layer&amp;quot;. Proceed without simplifying. Now every node should be visible.&lt;br /&gt;
&lt;br /&gt;
=== overlay and align with an orthophoto ===&lt;br /&gt;
&lt;br /&gt;
* select &#039;&#039;Imagery-&amp;gt;More...-&amp;gt;New picture layer from file...&#039;&#039; and open your orthophoto .tif file&lt;br /&gt;
* you can now use the PicLayer tools from the toolbar to align you image layer. When all is aligned, right click on the picture layer and save the picture calibration for later use.&lt;br /&gt;
[[File:JOSM with orthophoto.png|none|thumb|mowing area boundaries in JOSM with orthophoto]]&lt;br /&gt;
&lt;br /&gt;
=== modify the boundaries ===&lt;br /&gt;
you can now add/remove/modify/merge/divide:&lt;br /&gt;
&lt;br /&gt;
* mowing or navigation areas&lt;br /&gt;
* obstacles&lt;br /&gt;
* docking point&lt;br /&gt;
&lt;br /&gt;
Using all the nice tools from JOSM.&lt;br /&gt;
&lt;br /&gt;
Following rules for this:&lt;br /&gt;
&lt;br /&gt;
* mowing and navigation areas need to be in counter clockwise direction&lt;br /&gt;
* obstacles need to be in clockwise direction&lt;br /&gt;
&lt;br /&gt;
definition of the feature is in the value of gpx:name tag of each way like this:&lt;br /&gt;
&lt;br /&gt;
* mowing_area_0_area&lt;br /&gt;
* mowing_area_0_obstacle_0&lt;br /&gt;
*mowing_area_0_obstacle_1&lt;br /&gt;
* mowing_area_1_area&lt;br /&gt;
* navigation_area_0_area&lt;br /&gt;
*navigation_area_1_area&lt;br /&gt;
*navigation_area_1_obstacle_0&lt;br /&gt;
* docking_point&lt;br /&gt;
[[File:JOSM gpsname tag information.png|none|thumb|area boundary JOSM gps:name tag information]]&lt;br /&gt;
Here you can see an added obstacle.&lt;br /&gt;
&lt;br /&gt;
=== export as GPX and convert back to openmower map.bag ===&lt;br /&gt;
Right click on the gpx layer and &amp;quot;save as&amp;quot;. Select gpx format.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source ./devel/setup.bash&amp;lt;/code&amp;gt; in your &amp;lt;code&amp;gt;open_mower_ros&amp;lt;/code&amp;gt; folder to get the MapArea custom ros message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coordinates in the script. I use the roof mounted RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs in the python script.&lt;br /&gt;
&lt;br /&gt;
Run following python script and put the output_map.bag where your ros.bag was.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;gpx_to_bag.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x gpx_to_bag.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert gpx files to openmower ros map.bag&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import utm&lt;br /&gt;
import rosbag&lt;br /&gt;
import math&lt;br /&gt;
from geometry_msgs.msg import Polygon, Point32, Pose&lt;br /&gt;
from mower_map.msg import MapArea  # Imports the custom message from its package&lt;br /&gt;
from tf.transformations import quaternion_from_euler&lt;br /&gt;
&lt;br /&gt;
# Base point&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_file = open(&#039;map_bag_osm.gpx&#039;, &#039;r&#039;)&lt;br /&gt;
gpx = gpxpy.parse(gpx_file)&lt;br /&gt;
&lt;br /&gt;
mowing_area_dict = {}&lt;br /&gt;
navigation_area_dict = {}&lt;br /&gt;
base_point = []&lt;br /&gt;
docking_points = []&lt;br /&gt;
&lt;br /&gt;
for track in gpx.tracks:&lt;br /&gt;
	print(&amp;quot;reading from gpx: &amp;quot;, track.name)&lt;br /&gt;
	# get points&lt;br /&gt;
	point_list = []&lt;br /&gt;
	for segment in track.segments:&lt;br /&gt;
		print(&amp;quot;Point count:{}&amp;quot;.format(len(segment.points)))&lt;br /&gt;
		for point in segment.points:&lt;br /&gt;
			# print(f&#039;Point at ({point.latitude},{point.longitude}) -&amp;gt; {point.elevation}&#039;)&lt;br /&gt;
			x, y, zone, ut = utm.from_latlon(point.latitude, point.longitude)&lt;br /&gt;
			point_list.append(Point32(x=x-x0, y=y-y0, z=0.0))&lt;br /&gt;
	# store in deciated dict&lt;br /&gt;
	if &amp;quot;mowing&amp;quot; in track.name:&lt;br /&gt;
		mowing_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;navigation&amp;quot; in track.name:&lt;br /&gt;
		navigation_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;base&amp;quot; in track.name:&lt;br /&gt;
		base_point = point_list&lt;br /&gt;
	elif &amp;quot;docking&amp;quot; in track.name:&lt;br /&gt;
		docking_points  = point_list&lt;br /&gt;
&lt;br /&gt;
docking_pose = Pose()&lt;br /&gt;
if len(docking_points) == 2:&lt;br /&gt;
	yaw = math.atan2(docking_points[1].y - docking_points[0].y, docking_points[1].x - docking_points[0].x)&lt;br /&gt;
	quaternion = quaternion_from_euler(0,0,yaw)&lt;br /&gt;
	&lt;br /&gt;
	docking_pose.position.x = docking_points[0].x&lt;br /&gt;
	docking_pose.position.y = docking_points[0].y&lt;br /&gt;
	# Pose Orientation&lt;br /&gt;
	docking_pose.orientation.x = quaternion[0]&lt;br /&gt;
	docking_pose.orientation.y = quaternion[1]&lt;br /&gt;
	docking_pose.orientation.z = quaternion[2]&lt;br /&gt;
	docking_pose.orientation.w = quaternion[3]&lt;br /&gt;
		&lt;br /&gt;
&lt;br /&gt;
def sort_dict_return_MapAreas(area_dict):&lt;br /&gt;
	areas = {}&lt;br /&gt;
	for track_name, point_list in sorted(area_dict.items()): # store sorted polygons to MapArea&lt;br /&gt;
		map_area = MapArea()&lt;br /&gt;
		print(&amp;quot;converting:&amp;quot;, track_name)&lt;br /&gt;
		area_number = track_name.split(&amp;quot;_&amp;quot;)[2]&lt;br /&gt;
		poly = Polygon(points=point_list)&lt;br /&gt;
		if &amp;quot;area&amp;quot; in track_name[-4:]:&lt;br /&gt;
			map_area.area = poly&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
		elif &amp;quot;obstacle&amp;quot; in track_name:&lt;br /&gt;
			map_area = areas[area_number]&lt;br /&gt;
			map_area.obstacles.append(poly)&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
	return areas&lt;br /&gt;
&lt;br /&gt;
mowing_areas_list = sort_dict_return_MapAreas(mowing_area_dict).values()&lt;br /&gt;
navigation_areas_list = sort_dict_return_MapAreas(navigation_area_dict).values()&lt;br /&gt;
&lt;br /&gt;
with rosbag.Bag(&#039;output_map.bag&#039;, &#039;w&#039;) as outbag:&lt;br /&gt;
	print(&#039;writing bag file&#039;)&lt;br /&gt;
	for mowing_area in mowing_areas_list:&lt;br /&gt;
		outbag.write(&amp;quot;mowing_areas&amp;quot;, mowing_area)&lt;br /&gt;
	for navigation_area in navigation_areas_list:	&lt;br /&gt;
		outbag.write(&amp;quot;navigation_areas&amp;quot;, navigation_area)&lt;br /&gt;
	outbag.write(&amp;quot;docking_point&amp;quot;, docking_pose)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=337</id>
		<title>Modify map using drone footage</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=337"/>
		<updated>2023-06-03T20:29:13Z</updated>

		<summary type="html">&lt;p&gt;CSobel: improved image information&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Introduction ===&lt;br /&gt;
This tutorial shows how to use an orthophoto (generated by taking aerial pictures with a drone) and modify the open mower map. In my case I recorded the boundary of my lawn area driving the mower around and later divided the area and added obstacles using the drone footage.&lt;br /&gt;
&lt;br /&gt;
[[File:Openmower map boundary orthophoto.png|none|thumb|openmower map boundary with orthophoto layer]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Generate an orthophoto ===&lt;br /&gt;
I took about 50 pictures with a drone ca. 10m above ground and generated an orthophoto using OpenDroneMap WebODM&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can read about the process here: https://docs.opendronemap.org/tutorials/&lt;br /&gt;
&lt;br /&gt;
OpenDroneMap WebODM runs in a docker container and can be found here: https://www.opendronemap.org/webodm/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can export an orthophoto .tif format there&lt;br /&gt;
&lt;br /&gt;
=== convert map.bag into GPX ===&lt;br /&gt;
Obviously back up your map.bag&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I wrote a python script to convert the map area coordinates into utm coordinates. I ran this on the robots raspberry pi. In reads the map.bag from .ros folder and exports a .gpx format file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Following python packages are needed&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pip install utm gpxpy&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coodinates in the script. I use the roof mouned RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs and run the python script.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert openmower ros map.bag to gpx file&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
from codecs import latin_1_decode&lt;br /&gt;
import rosbag&lt;br /&gt;
import utm&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import math&lt;br /&gt;
import os&lt;br /&gt;
from tf.transformations import euler_from_quaternion&lt;br /&gt;
&lt;br /&gt;
bag = rosbag.Bag(&#039;/home/pi/.ros/map.bag&#039;)&lt;br /&gt;
# bag = rosbag.Bag(&#039;output.bag&#039;)&lt;br /&gt;
&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
&lt;br /&gt;
topics = bag.get_type_and_topic_info()[1].keys()&lt;br /&gt;
print(topics)&lt;br /&gt;
&lt;br /&gt;
gpx = gpxpy.gpx.GPX()&lt;br /&gt;
gpx.name = &#039;open_mower_map&#039;&lt;br /&gt;
gpx.description = &#039;Map data from openmower map.bag&#039;&lt;br /&gt;
&lt;br /&gt;
# base point (e.g. gps antenna)&lt;br /&gt;
# base_point = gpxpy.gpx.GPXWaypoint(latitude=lat0, longitude=lon0, name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
# gpx.waypoints.append(base_point)&lt;br /&gt;
&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
gpx.tracks.append(gpx_track)&lt;br /&gt;
gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
gpx_track.segments.append(gpx_segment)&lt;br /&gt;
gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat0, lon0, elevation=0))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Loop through areas and obstacles&lt;br /&gt;
area_counter = 0&lt;br /&gt;
&lt;br /&gt;
for area_type in [&#039;mowing_areas&#039;, &#039;navigation_areas&#039;]:&lt;br /&gt;
    for topic, msg, t in bag.read_messages(topics=[area_type]):&lt;br /&gt;
        print(&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        # Create track in GPX:&lt;br /&gt;
        gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        gpx.tracks.append(gpx_track)&lt;br /&gt;
        # Create segment in GPX track:&lt;br /&gt;
        gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
        gpx_track.segments.append(gpx_segment)&lt;br /&gt;
&lt;br /&gt;
        # outline&lt;br /&gt;
        for point in msg.area.points:&lt;br /&gt;
            # Create points:&lt;br /&gt;
            lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
            gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
&lt;br /&gt;
        # obstacles&lt;br /&gt;
        obstacle_counter = 0&lt;br /&gt;
        for obst in msg.obstacles:&lt;br /&gt;
            print(&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            # Create track in GPX:&lt;br /&gt;
            gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            gpx.tracks.append(gpx_track)&lt;br /&gt;
            # Create segment in GPX track:&lt;br /&gt;
            gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
            gpx_track.segments.append(gpx_segment)&lt;br /&gt;
            for point in obst.points:&lt;br /&gt;
                # Create points:&lt;br /&gt;
                lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
                gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
            obstacle_counter += 1&lt;br /&gt;
        area_counter += 1&lt;br /&gt;
&lt;br /&gt;
# docking point&lt;br /&gt;
for topic, msg, t in bag.read_messages(topics=[&#039;docking_point&#039;]):&lt;br /&gt;
    # print(msg)&lt;br /&gt;
    print(&amp;quot;parsing docking point&amp;quot;)&lt;br /&gt;
    quat_list = [msg.orientation.x, msg.orientation.y, msg.orientation.z, msg.orientation.w]&lt;br /&gt;
    (roll, pitch, yaw) = euler_from_quaternion(quat_list)&lt;br /&gt;
    # print((roll, pitch, yaw))&lt;br /&gt;
    x2 = msg.position.x + math.cos(yaw)&lt;br /&gt;
    y2 = msg.position.y + math.sin(yaw)&lt;br /&gt;
&lt;br /&gt;
    lat1, lon1 = utm.to_latlon(x0 + msg.position.x, y0 + msg.position.y, zone, ut)&lt;br /&gt;
    lat2, lon2 = utm.to_latlon(x0 + x2,y0 + y2, zone, ut)&lt;br /&gt;
&lt;br /&gt;
    gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;docking_point&amp;quot;)&lt;br /&gt;
    gpx.tracks.append(gpx_track)&lt;br /&gt;
    gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
    gpx_track.segments.append(gpx_segment)&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat1, lon1, elevation=0))&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat2, lon2, elevation=0))&lt;br /&gt;
&lt;br /&gt;
print(&#039;Created GPX file&#039;)&lt;br /&gt;
with open(&amp;quot;map_bag.gpx&amp;quot;, &amp;quot;w&amp;quot;) as f:&lt;br /&gt;
    f.write(gpx.to_xml())&lt;br /&gt;
&lt;br /&gt;
bag.close()&lt;br /&gt;
print(&#039;Done&#039;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== load GPX file into JOSM ===&lt;br /&gt;
Install the Java OpenStreetMap Editor from:&lt;br /&gt;
&lt;br /&gt;
https://josm.openstreetmap.de/wiki/Download&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start JOSM and install the PicLayer Plugin. For this go to &#039;&#039;Edit-&amp;gt;Preferences-&amp;gt;Plugins&#039;&#039; and search for PicLayer and tick the checkbox.&lt;br /&gt;
&lt;br /&gt;
Open your .gpx file, you shoud see the recorded boundaries&lt;br /&gt;
[[File:JOSM imported GPX file.png|none|thumb|Boundaries visible in JOSM from imported GPX file]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Right click on the Layer output.gpx and select &amp;quot;convert to data layer&amp;quot;. Proceed without simplifying. Now every node should be visible.&lt;br /&gt;
&lt;br /&gt;
=== overlay and align with an orthophoto ===&lt;br /&gt;
&lt;br /&gt;
* select &#039;&#039;Imagery-&amp;gt;More...-&amp;gt;New picture layer from file...&#039;&#039; and open your orthophoto .tif file&lt;br /&gt;
* you can now use the PicLayer tools from the toolbar to align you image layer. When all is aligned, right click on the picture layer and save the picture calibration for later use.&lt;br /&gt;
[[File:JOSM with orthophoto.png|none|thumb|mowing area boundaries in JOSM with orthophoto]]&lt;br /&gt;
&lt;br /&gt;
=== modify the boundaries ===&lt;br /&gt;
you can now add/remove/modify/merge/divide:&lt;br /&gt;
&lt;br /&gt;
* mowing or navigation areas&lt;br /&gt;
* obstacles&lt;br /&gt;
* docking point&lt;br /&gt;
&lt;br /&gt;
Using all the nice tools from JOSM.&lt;br /&gt;
&lt;br /&gt;
Following rules for this:&lt;br /&gt;
&lt;br /&gt;
* mowing and navigation areas need to be in counter clockwise direction&lt;br /&gt;
* obstacles need to be in clockwise direction&lt;br /&gt;
&lt;br /&gt;
definition of the feature is in the value of gpx:name tag of each way like this:&lt;br /&gt;
&lt;br /&gt;
* mowing_area_0_area&lt;br /&gt;
* mowing_area_0_obstacle&lt;br /&gt;
* mowing_area_1_area&lt;br /&gt;
* navigation_area_0_area&lt;br /&gt;
* docking_point&lt;br /&gt;
[[File:JOSM gpsname tag information.png|none|thumb|area boundary JOSM gps:name tag information]]&lt;br /&gt;
Here you can see an added obstacle.&lt;br /&gt;
&lt;br /&gt;
=== export as GPX and convert back to openmower map.bag ===&lt;br /&gt;
Right click on the gpx layer and &amp;quot;save as&amp;quot;. Select gpx format.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source ./devel/setup.bash&amp;lt;/code&amp;gt; in your &amp;lt;code&amp;gt;open_mower_ros&amp;lt;/code&amp;gt; folder to get the MapArea custom ros message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coordinates in the script. I use the roof mounted RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs in the python script.&lt;br /&gt;
&lt;br /&gt;
Run following python script and put the output_map.bag where your ros.bag was.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;gpx_to_bag.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x gpx_to_bag.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert gpx files to openmower ros map.bag&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import utm&lt;br /&gt;
import rosbag&lt;br /&gt;
import math&lt;br /&gt;
from geometry_msgs.msg import Polygon, Point32, Pose&lt;br /&gt;
from mower_map.msg import MapArea  # Imports the custom message from its package&lt;br /&gt;
from tf.transformations import quaternion_from_euler&lt;br /&gt;
&lt;br /&gt;
# Base point&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_file = open(&#039;map_bag_osm.gpx&#039;, &#039;r&#039;)&lt;br /&gt;
gpx = gpxpy.parse(gpx_file)&lt;br /&gt;
&lt;br /&gt;
mowing_area_dict = {}&lt;br /&gt;
navigation_area_dict = {}&lt;br /&gt;
base_point = []&lt;br /&gt;
docking_points = []&lt;br /&gt;
&lt;br /&gt;
for track in gpx.tracks:&lt;br /&gt;
	print(&amp;quot;reading from gpx: &amp;quot;, track.name)&lt;br /&gt;
	# get points&lt;br /&gt;
	point_list = []&lt;br /&gt;
	for segment in track.segments:&lt;br /&gt;
		print(&amp;quot;Point count:{}&amp;quot;.format(len(segment.points)))&lt;br /&gt;
		for point in segment.points:&lt;br /&gt;
			# print(f&#039;Point at ({point.latitude},{point.longitude}) -&amp;gt; {point.elevation}&#039;)&lt;br /&gt;
			x, y, zone, ut = utm.from_latlon(point.latitude, point.longitude)&lt;br /&gt;
			point_list.append(Point32(x=x-x0, y=y-y0, z=0.0))&lt;br /&gt;
	# store in deciated dict&lt;br /&gt;
	if &amp;quot;mowing&amp;quot; in track.name:&lt;br /&gt;
		mowing_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;navigation&amp;quot; in track.name:&lt;br /&gt;
		navigation_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;base&amp;quot; in track.name:&lt;br /&gt;
		base_point = point_list&lt;br /&gt;
	elif &amp;quot;docking&amp;quot; in track.name:&lt;br /&gt;
		docking_points  = point_list&lt;br /&gt;
&lt;br /&gt;
docking_pose = Pose()&lt;br /&gt;
if len(docking_points) == 2:&lt;br /&gt;
	yaw = math.atan2(docking_points[1].y - docking_points[0].y, docking_points[1].x - docking_points[0].x)&lt;br /&gt;
	quaternion = quaternion_from_euler(0,0,yaw)&lt;br /&gt;
	&lt;br /&gt;
	docking_pose.position.x = docking_points[0].x&lt;br /&gt;
	docking_pose.position.y = docking_points[0].y&lt;br /&gt;
	# Pose Orientation&lt;br /&gt;
	docking_pose.orientation.x = quaternion[0]&lt;br /&gt;
	docking_pose.orientation.y = quaternion[1]&lt;br /&gt;
	docking_pose.orientation.z = quaternion[2]&lt;br /&gt;
	docking_pose.orientation.w = quaternion[3]&lt;br /&gt;
		&lt;br /&gt;
&lt;br /&gt;
def sort_dict_return_MapAreas(area_dict):&lt;br /&gt;
	areas = {}&lt;br /&gt;
	for track_name, point_list in sorted(area_dict.items()): # store sorted polygons to MapArea&lt;br /&gt;
		map_area = MapArea()&lt;br /&gt;
		print(&amp;quot;converting:&amp;quot;, track_name)&lt;br /&gt;
		area_number = track_name.split(&amp;quot;_&amp;quot;)[2]&lt;br /&gt;
		poly = Polygon(points=point_list)&lt;br /&gt;
		if &amp;quot;area&amp;quot; in track_name[-4:]:&lt;br /&gt;
			map_area.area = poly&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
		elif &amp;quot;obstacle&amp;quot; in track_name:&lt;br /&gt;
			map_area = areas[area_number]&lt;br /&gt;
			map_area.obstacles.append(poly)&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
	return areas&lt;br /&gt;
&lt;br /&gt;
mowing_areas_list = sort_dict_return_MapAreas(mowing_area_dict).values()&lt;br /&gt;
navigation_areas_list = sort_dict_return_MapAreas(navigation_area_dict).values()&lt;br /&gt;
&lt;br /&gt;
with rosbag.Bag(&#039;output_map.bag&#039;, &#039;w&#039;) as outbag:&lt;br /&gt;
	print(&#039;writing bag file&#039;)&lt;br /&gt;
	for mowing_area in mowing_areas_list:&lt;br /&gt;
		outbag.write(&amp;quot;mowing_areas&amp;quot;, mowing_area)&lt;br /&gt;
	for navigation_area in navigation_areas_list:	&lt;br /&gt;
		outbag.write(&amp;quot;navigation_areas&amp;quot;, navigation_area)&lt;br /&gt;
	outbag.write(&amp;quot;docking_point&amp;quot;, docking_pose)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_gpsname_tag_information.png&amp;diff=336</id>
		<title>File:JOSM gpsname tag information.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_gpsname_tag_information.png&amp;diff=336"/>
		<updated>2023-06-03T20:28:01Z</updated>

		<summary type="html">&lt;p&gt;CSobel: CSobel uploaded a new version of File:JOSM gpsname tag information.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;areas with gpx:name tag for featue definition&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_gpsname_tag_information.png&amp;diff=335</id>
		<title>File:JOSM gpsname tag information.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_gpsname_tag_information.png&amp;diff=335"/>
		<updated>2023-06-03T20:22:57Z</updated>

		<summary type="html">&lt;p&gt;CSobel: CSobel uploaded a new version of File:JOSM gpsname tag information.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;areas with gpx:name tag for featue definition&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_with_orthophoto.png&amp;diff=334</id>
		<title>File:JOSM with orthophoto.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_with_orthophoto.png&amp;diff=334"/>
		<updated>2023-06-03T20:19:46Z</updated>

		<summary type="html">&lt;p&gt;CSobel: CSobel uploaded a new version of File:JOSM with orthophoto.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mowing area boundaries in JOSM with orthophoto&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=333</id>
		<title>File:JOSM imported GPX file.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=333"/>
		<updated>2023-06-03T20:16:23Z</updated>

		<summary type="html">&lt;p&gt;CSobel: CSobel reverted File:JOSM imported GPX file.png to an old version&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Boundaries visible in JOSM from imported GPX file&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=332</id>
		<title>File:JOSM imported GPX file.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=332"/>
		<updated>2023-06-03T20:16:00Z</updated>

		<summary type="html">&lt;p&gt;CSobel: CSobel reverted File:JOSM imported GPX file.png to an old version&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Boundaries visible in JOSM from imported GPX file&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=331</id>
		<title>File:JOSM imported GPX file.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=331"/>
		<updated>2023-06-03T20:15:26Z</updated>

		<summary type="html">&lt;p&gt;CSobel: CSobel uploaded a new version of File:JOSM imported GPX file.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Boundaries visible in JOSM from imported GPX file&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=Mini_HOWTOs&amp;diff=324</id>
		<title>Mini HOWTOs</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Mini_HOWTOs&amp;diff=324"/>
		<updated>2023-05-11T08:47:16Z</updated>

		<summary type="html">&lt;p&gt;CSobel: added: Modify map using drone footage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Here you can find various collected instructions on how to do various tasks related to the OpenMower project. Most are rather impressionistic and are not meant to be detailed, thoroughly tested guides, but more a quick notion on how to do something rather than having to be a detective and find out entirely on your own.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Disclaimer&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;This material is provided as-is and free of charge. No guarantees of any kind are made that the information contained herein is exact or correct or does not violate any local regulations or cause any security risk. Any use you make of any information given here is at your own discretion and risk.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Warning ==&lt;br /&gt;
Please note that this project currently is in a very early stage! So don&#039;t go buying stuff right away! You will need to order and solder your own boards, since there are no assembled boards for sale yet.&lt;br /&gt;
&lt;br /&gt;
Please make sure that you&#039;re actually allowed to build this device in your area. There may be laws / patents prohibiting you of doing so!&lt;br /&gt;
&lt;br /&gt;
Please just be responsible and if you&#039;re not sure what you&#039;re doing, please don&#039;t do anything.&lt;br /&gt;
&lt;br /&gt;
BTW the warranty of your brand new mower will obviously be void as soon as you modify it in any way, so be aware.&lt;br /&gt;
&lt;br /&gt;
== List of Mini HOWTOs ==&lt;br /&gt;
&lt;br /&gt;
* [[Simple set-up of a RTK base station]]&lt;br /&gt;
*[[Setting up ROS on a Raspberry Pi (intended to be the Rover -- OpenMower)]] (placeholder for now)&lt;br /&gt;
*[[Flashing firmware on the Pi Pico]]&lt;br /&gt;
*[[Flashing Xesc firmware]]&lt;br /&gt;
*[[Running an OpenMower simulation]]&lt;br /&gt;
*[[Setting up ROS Mobile]]&lt;br /&gt;
*[[Modify map using drone footage]]&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=323</id>
		<title>Modify map using drone footage</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=323"/>
		<updated>2023-05-11T08:45:16Z</updated>

		<summary type="html">&lt;p&gt;CSobel: formatting and more background&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Introduction ===&lt;br /&gt;
This tutorial shows how to use an orthophoto (generated by taking aerial pictures with a drone) and modify the open mower map. In my case I recorded the boundary of my lawn area driving the mower around and later divided the area and added obstacles using the drone footage.&lt;br /&gt;
&lt;br /&gt;
[[File:Openmower map boundary orthophoto.png|none|thumb|openmower map boundary with orthophoto layer]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Generate an orthophoto ===&lt;br /&gt;
I took about 50 pictures with a drone ca. 10m above ground and generated an orthophoto using OpenDroneMap WebODM&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can read about the process here: https://docs.opendronemap.org/tutorials/&lt;br /&gt;
&lt;br /&gt;
OpenDroneMap WebODM runs in a docker container and can be found here: https://www.opendronemap.org/webodm/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can export an orthophoto .tif format there&lt;br /&gt;
&lt;br /&gt;
=== convert map.bag into GPX ===&lt;br /&gt;
Obviously back up your map.bag&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I wrote a python script to convert the map area coordinates into utm coordinates. I ran this on the robots raspberry pi. In reads the map.bag from .ros folder and exports a .gpx format file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Following python packages are needed&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pip install utm gpxpy&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coodinates in the script. I use the roof mouned RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs and run the python script.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert openmower ros map.bag to gpx file&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
from codecs import latin_1_decode&lt;br /&gt;
import rosbag&lt;br /&gt;
import utm&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import math&lt;br /&gt;
import os&lt;br /&gt;
from tf.transformations import euler_from_quaternion&lt;br /&gt;
&lt;br /&gt;
bag = rosbag.Bag(&#039;/home/pi/.ros/map.bag&#039;)&lt;br /&gt;
# bag = rosbag.Bag(&#039;output.bag&#039;)&lt;br /&gt;
&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
&lt;br /&gt;
topics = bag.get_type_and_topic_info()[1].keys()&lt;br /&gt;
print(topics)&lt;br /&gt;
&lt;br /&gt;
gpx = gpxpy.gpx.GPX()&lt;br /&gt;
gpx.name = &#039;open_mower_map&#039;&lt;br /&gt;
gpx.description = &#039;Map data from openmower map.bag&#039;&lt;br /&gt;
&lt;br /&gt;
# base point (e.g. gps antenna)&lt;br /&gt;
# base_point = gpxpy.gpx.GPXWaypoint(latitude=lat0, longitude=lon0, name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
# gpx.waypoints.append(base_point)&lt;br /&gt;
&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
gpx.tracks.append(gpx_track)&lt;br /&gt;
gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
gpx_track.segments.append(gpx_segment)&lt;br /&gt;
gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat0, lon0, elevation=0))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Loop through areas and obstacles&lt;br /&gt;
area_counter = 0&lt;br /&gt;
&lt;br /&gt;
for area_type in [&#039;mowing_areas&#039;, &#039;navigation_areas&#039;]:&lt;br /&gt;
    for topic, msg, t in bag.read_messages(topics=[area_type]):&lt;br /&gt;
        print(&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        # Create track in GPX:&lt;br /&gt;
        gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        gpx.tracks.append(gpx_track)&lt;br /&gt;
        # Create segment in GPX track:&lt;br /&gt;
        gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
        gpx_track.segments.append(gpx_segment)&lt;br /&gt;
&lt;br /&gt;
        # outline&lt;br /&gt;
        for point in msg.area.points:&lt;br /&gt;
            # Create points:&lt;br /&gt;
            lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
            gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
&lt;br /&gt;
        # obstacles&lt;br /&gt;
        obstacle_counter = 0&lt;br /&gt;
        for obst in msg.obstacles:&lt;br /&gt;
            print(&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            # Create track in GPX:&lt;br /&gt;
            gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            gpx.tracks.append(gpx_track)&lt;br /&gt;
            # Create segment in GPX track:&lt;br /&gt;
            gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
            gpx_track.segments.append(gpx_segment)&lt;br /&gt;
            for point in obst.points:&lt;br /&gt;
                # Create points:&lt;br /&gt;
                lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
                gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
            obstacle_counter += 1&lt;br /&gt;
        area_counter += 1&lt;br /&gt;
&lt;br /&gt;
# docking point&lt;br /&gt;
for topic, msg, t in bag.read_messages(topics=[&#039;docking_point&#039;]):&lt;br /&gt;
    # print(msg)&lt;br /&gt;
    print(&amp;quot;parsing docking point&amp;quot;)&lt;br /&gt;
    quat_list = [msg.orientation.x, msg.orientation.y, msg.orientation.z, msg.orientation.w]&lt;br /&gt;
    (roll, pitch, yaw) = euler_from_quaternion(quat_list)&lt;br /&gt;
    # print((roll, pitch, yaw))&lt;br /&gt;
    x2 = msg.position.x + math.cos(yaw)&lt;br /&gt;
    y2 = msg.position.y + math.sin(yaw)&lt;br /&gt;
&lt;br /&gt;
    lat1, lon1 = utm.to_latlon(x0 + msg.position.x, y0 + msg.position.y, zone, ut)&lt;br /&gt;
    lat2, lon2 = utm.to_latlon(x0 + x2,y0 + y2, zone, ut)&lt;br /&gt;
&lt;br /&gt;
    gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;docking_point&amp;quot;)&lt;br /&gt;
    gpx.tracks.append(gpx_track)&lt;br /&gt;
    gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
    gpx_track.segments.append(gpx_segment)&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat1, lon1, elevation=0))&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat2, lon2, elevation=0))&lt;br /&gt;
&lt;br /&gt;
print(&#039;Created GPX file&#039;)&lt;br /&gt;
with open(&amp;quot;map_bag.gpx&amp;quot;, &amp;quot;w&amp;quot;) as f:&lt;br /&gt;
    f.write(gpx.to_xml())&lt;br /&gt;
&lt;br /&gt;
bag.close()&lt;br /&gt;
print(&#039;Done&#039;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== load GPX file into JOSM ===&lt;br /&gt;
Install the Java OpenStreetMap Editor from:&lt;br /&gt;
&lt;br /&gt;
https://josm.openstreetmap.de/wiki/Download&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start JOSM and install the PicLayer Plugin. For this go to &#039;&#039;Edit-&amp;gt;Preferences-&amp;gt;Plugins&#039;&#039; and search for PicLayer and tick the checkbox.&lt;br /&gt;
&lt;br /&gt;
Open your .gpx file, you shoud see the recorded boundaries&lt;br /&gt;
[[File:JOSM imported GPX file.png|none|thumb|Boundaries visible in JOSM from imported GPX file]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Right click on the Layer output.gpx and select &amp;quot;convert to data layer&amp;quot;. Proceed without simplifying. Now every node should be visible.&lt;br /&gt;
&lt;br /&gt;
=== overlay and align with an orthophoto ===&lt;br /&gt;
&lt;br /&gt;
* select &#039;&#039;Imagery-&amp;gt;More...-&amp;gt;New picture layer from file...&#039;&#039; and open your orthophoto .tif file&lt;br /&gt;
* you can now use the PicLayer tools from the toolbar to align you image layer. When all is aligned, right click on the picture layer and save the picture calibration for later use.&lt;br /&gt;
&lt;br /&gt;
[[File:JOSM with orthophoto.png|none|thumb|mowing area boundaries in JOSM with orthophoto]]&lt;br /&gt;
&lt;br /&gt;
=== modify the boundaries ===&lt;br /&gt;
you can now add/remove/modify/merge/divide:&lt;br /&gt;
&lt;br /&gt;
* mowing or navigation areas&lt;br /&gt;
* obstacles&lt;br /&gt;
* docking point&lt;br /&gt;
&lt;br /&gt;
Using all the nice tools from JOSM.&lt;br /&gt;
&lt;br /&gt;
Following rules for this:&lt;br /&gt;
&lt;br /&gt;
* mowing and navigation areas need to be in counter clockwise direction&lt;br /&gt;
* obstacles need to be in clockwise direction&lt;br /&gt;
&lt;br /&gt;
definition of the feature is in the value of gpx:name tag of each way like this:&lt;br /&gt;
&lt;br /&gt;
* mowing_area_0_area&lt;br /&gt;
* mowing_area_0_obstacle&lt;br /&gt;
* mowing_area_1_area&lt;br /&gt;
* navigation_area_0_area&lt;br /&gt;
* docking_point&lt;br /&gt;
&lt;br /&gt;
[[File:JOSM gpsname tag information.png|none|thumb|area boundary JOSM gps:name tag information]]&lt;br /&gt;
Here you can see an added obstacle.&lt;br /&gt;
&lt;br /&gt;
=== export as GPX and convert back to openmower map.bag ===&lt;br /&gt;
Right click on the gpx layer and &amp;quot;save as&amp;quot;. Select gpx format.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source ./devel/setup.bash&amp;lt;/code&amp;gt; in your &amp;lt;code&amp;gt;open_mower_ros&amp;lt;/code&amp;gt; folder to get the MapArea custom ros message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coordinates in the script. I use the roof mounted RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs in the python script.&lt;br /&gt;
&lt;br /&gt;
Run following python script and put the output_map.bag where your ros.bag was.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;gpx_to_bag.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x gpx_to_bag.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert gpx files to openmower ros map.bag&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import utm&lt;br /&gt;
import rosbag&lt;br /&gt;
import math&lt;br /&gt;
from geometry_msgs.msg import Polygon, Point32, Pose&lt;br /&gt;
from mower_map.msg import MapArea  # Imports the custom message from its package&lt;br /&gt;
from tf.transformations import quaternion_from_euler&lt;br /&gt;
&lt;br /&gt;
# Base point&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_file = open(&#039;map_bag_osm.gpx&#039;, &#039;r&#039;)&lt;br /&gt;
gpx = gpxpy.parse(gpx_file)&lt;br /&gt;
&lt;br /&gt;
mowing_area_dict = {}&lt;br /&gt;
navigation_area_dict = {}&lt;br /&gt;
base_point = []&lt;br /&gt;
docking_points = []&lt;br /&gt;
&lt;br /&gt;
for track in gpx.tracks:&lt;br /&gt;
	print(&amp;quot;reading from gpx: &amp;quot;, track.name)&lt;br /&gt;
	# get points&lt;br /&gt;
	point_list = []&lt;br /&gt;
	for segment in track.segments:&lt;br /&gt;
		print(&amp;quot;Point count:{}&amp;quot;.format(len(segment.points)))&lt;br /&gt;
		for point in segment.points:&lt;br /&gt;
			# print(f&#039;Point at ({point.latitude},{point.longitude}) -&amp;gt; {point.elevation}&#039;)&lt;br /&gt;
			x, y, zone, ut = utm.from_latlon(point.latitude, point.longitude)&lt;br /&gt;
			point_list.append(Point32(x=x-x0, y=y-y0, z=0.0))&lt;br /&gt;
	# store in deciated dict&lt;br /&gt;
	if &amp;quot;mowing&amp;quot; in track.name:&lt;br /&gt;
		mowing_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;navigation&amp;quot; in track.name:&lt;br /&gt;
		navigation_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;base&amp;quot; in track.name:&lt;br /&gt;
		base_point = point_list&lt;br /&gt;
	elif &amp;quot;docking&amp;quot; in track.name:&lt;br /&gt;
		docking_points  = point_list&lt;br /&gt;
&lt;br /&gt;
docking_pose = Pose()&lt;br /&gt;
if len(docking_points) == 2:&lt;br /&gt;
	yaw = math.atan2(docking_points[1].y - docking_points[0].y, docking_points[1].x - docking_points[0].x)&lt;br /&gt;
	quaternion = quaternion_from_euler(0,0,yaw)&lt;br /&gt;
	&lt;br /&gt;
	docking_pose.position.x = docking_points[0].x&lt;br /&gt;
	docking_pose.position.y = docking_points[0].y&lt;br /&gt;
	# Pose Orientation&lt;br /&gt;
	docking_pose.orientation.x = quaternion[0]&lt;br /&gt;
	docking_pose.orientation.y = quaternion[1]&lt;br /&gt;
	docking_pose.orientation.z = quaternion[2]&lt;br /&gt;
	docking_pose.orientation.w = quaternion[3]&lt;br /&gt;
		&lt;br /&gt;
&lt;br /&gt;
def sort_dict_return_MapAreas(area_dict):&lt;br /&gt;
	areas = {}&lt;br /&gt;
	for track_name, point_list in sorted(area_dict.items()): # store sorted polygons to MapArea&lt;br /&gt;
		map_area = MapArea()&lt;br /&gt;
		print(&amp;quot;converting:&amp;quot;, track_name)&lt;br /&gt;
		area_number = track_name.split(&amp;quot;_&amp;quot;)[2]&lt;br /&gt;
		poly = Polygon(points=point_list)&lt;br /&gt;
		if &amp;quot;area&amp;quot; in track_name[-4:]:&lt;br /&gt;
			map_area.area = poly&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
		elif &amp;quot;obstacle&amp;quot; in track_name:&lt;br /&gt;
			map_area = areas[area_number]&lt;br /&gt;
			map_area.obstacles.append(poly)&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
	return areas&lt;br /&gt;
&lt;br /&gt;
mowing_areas_list = sort_dict_return_MapAreas(mowing_area_dict).values()&lt;br /&gt;
navigation_areas_list = sort_dict_return_MapAreas(navigation_area_dict).values()&lt;br /&gt;
&lt;br /&gt;
with rosbag.Bag(&#039;output_map.bag&#039;, &#039;w&#039;) as outbag:&lt;br /&gt;
	print(&#039;writing bag file&#039;)&lt;br /&gt;
	for mowing_area in mowing_areas_list:&lt;br /&gt;
		outbag.write(&amp;quot;mowing_areas&amp;quot;, mowing_area)&lt;br /&gt;
	for navigation_area in navigation_areas_list:	&lt;br /&gt;
		outbag.write(&amp;quot;navigation_areas&amp;quot;, navigation_area)&lt;br /&gt;
	outbag.write(&amp;quot;docking_point&amp;quot;, docking_pose)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=322</id>
		<title>Modify map using drone footage</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=322"/>
		<updated>2023-05-11T07:59:17Z</updated>

		<summary type="html">&lt;p&gt;CSobel: added overview image, remove duplicate content&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Introduction ===&lt;br /&gt;
This tutorial shows how to use an orthophoto (generated by taking aerial pictures with a drone) and modify the open mower map&lt;br /&gt;
&lt;br /&gt;
[[File:Openmower map boundary orthophoto.png|none|thumb|openmower map boundary with orthophoto layer]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Generate an orthophoto ===&lt;br /&gt;
I took about 50 pictures with a drone ca. 10m above ground and generated an orthophoto using OpenDroneMap WebODM&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can read about the process here: https://docs.opendronemap.org/tutorials/&lt;br /&gt;
&lt;br /&gt;
OpenDroneMap WebODM runs in a docker container and can be found here: https://www.opendronemap.org/webodm/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can export an orthophoto .tif format there&lt;br /&gt;
&lt;br /&gt;
=== convert map.bag into GPX ===&lt;br /&gt;
Obviously back up your map.bag&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I wrote a python script to convert the map area coordinates into utm coordinates. I ran this on the robots raspberry pi. In reads the map.bag from .ros folder and exports a .gpx format file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Following python packages are needed&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pip install utm gpxpy&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coodinates in the script. I use the roof mouned RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs and run the python script.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert openmower ros map.bag to gpx file&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
from codecs import latin_1_decode&lt;br /&gt;
import rosbag&lt;br /&gt;
import utm&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import math&lt;br /&gt;
import os&lt;br /&gt;
from tf.transformations import euler_from_quaternion&lt;br /&gt;
&lt;br /&gt;
bag = rosbag.Bag(&#039;/home/pi/.ros/map.bag&#039;)&lt;br /&gt;
# bag = rosbag.Bag(&#039;output.bag&#039;)&lt;br /&gt;
&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
&lt;br /&gt;
topics = bag.get_type_and_topic_info()[1].keys()&lt;br /&gt;
print(topics)&lt;br /&gt;
&lt;br /&gt;
gpx = gpxpy.gpx.GPX()&lt;br /&gt;
gpx.name = &#039;open_mower_map&#039;&lt;br /&gt;
gpx.description = &#039;Map data from openmower map.bag&#039;&lt;br /&gt;
&lt;br /&gt;
# base point (e.g. gps antenna)&lt;br /&gt;
# base_point = gpxpy.gpx.GPXWaypoint(latitude=lat0, longitude=lon0, name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
# gpx.waypoints.append(base_point)&lt;br /&gt;
&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
gpx.tracks.append(gpx_track)&lt;br /&gt;
gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
gpx_track.segments.append(gpx_segment)&lt;br /&gt;
gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat0, lon0, elevation=0))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Loop through areas and obstacles&lt;br /&gt;
area_counter = 0&lt;br /&gt;
&lt;br /&gt;
for area_type in [&#039;mowing_areas&#039;, &#039;navigation_areas&#039;]:&lt;br /&gt;
    for topic, msg, t in bag.read_messages(topics=[area_type]):&lt;br /&gt;
        print(&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        # Create track in GPX:&lt;br /&gt;
        gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        gpx.tracks.append(gpx_track)&lt;br /&gt;
        # Create segment in GPX track:&lt;br /&gt;
        gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
        gpx_track.segments.append(gpx_segment)&lt;br /&gt;
&lt;br /&gt;
        # outline&lt;br /&gt;
        for point in msg.area.points:&lt;br /&gt;
            # Create points:&lt;br /&gt;
            lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
            gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
&lt;br /&gt;
        # obstacles&lt;br /&gt;
        obstacle_counter = 0&lt;br /&gt;
        for obst in msg.obstacles:&lt;br /&gt;
            print(&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            # Create track in GPX:&lt;br /&gt;
            gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            gpx.tracks.append(gpx_track)&lt;br /&gt;
            # Create segment in GPX track:&lt;br /&gt;
            gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
            gpx_track.segments.append(gpx_segment)&lt;br /&gt;
            for point in obst.points:&lt;br /&gt;
                # Create points:&lt;br /&gt;
                lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
                gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
            obstacle_counter += 1&lt;br /&gt;
        area_counter += 1&lt;br /&gt;
&lt;br /&gt;
# docking point&lt;br /&gt;
for topic, msg, t in bag.read_messages(topics=[&#039;docking_point&#039;]):&lt;br /&gt;
    # print(msg)&lt;br /&gt;
    print(&amp;quot;parsing docking point&amp;quot;)&lt;br /&gt;
    quat_list = [msg.orientation.x, msg.orientation.y, msg.orientation.z, msg.orientation.w]&lt;br /&gt;
    (roll, pitch, yaw) = euler_from_quaternion(quat_list)&lt;br /&gt;
    # print((roll, pitch, yaw))&lt;br /&gt;
    x2 = msg.position.x + math.cos(yaw)&lt;br /&gt;
    y2 = msg.position.y + math.sin(yaw)&lt;br /&gt;
&lt;br /&gt;
    lat1, lon1 = utm.to_latlon(x0 + msg.position.x, y0 + msg.position.y, zone, ut)&lt;br /&gt;
    lat2, lon2 = utm.to_latlon(x0 + x2,y0 + y2, zone, ut)&lt;br /&gt;
&lt;br /&gt;
    gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;docking_point&amp;quot;)&lt;br /&gt;
    gpx.tracks.append(gpx_track)&lt;br /&gt;
    gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
    gpx_track.segments.append(gpx_segment)&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat1, lon1, elevation=0))&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat2, lon2, elevation=0))&lt;br /&gt;
&lt;br /&gt;
print(&#039;Created GPX file&#039;)&lt;br /&gt;
with open(&amp;quot;map_bag.gpx&amp;quot;, &amp;quot;w&amp;quot;) as f:&lt;br /&gt;
    f.write(gpx.to_xml())&lt;br /&gt;
&lt;br /&gt;
bag.close()&lt;br /&gt;
print(&#039;Done&#039;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== load GPX file into JOSM ===&lt;br /&gt;
Install the Java OpenStreetMap Editor from:&lt;br /&gt;
&lt;br /&gt;
https://josm.openstreetmap.de/wiki/Download&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start JOSM and install the PicLayer Plugin. For this go to &#039;&#039;Edit-&amp;gt;Preferences-&amp;gt;Plugins&#039;&#039; and search for PicLayer and tick the checkbox.&lt;br /&gt;
&lt;br /&gt;
Open your .gpx file, you shoud see the recorded boundaries&lt;br /&gt;
[[File:JOSM imported GPX file.png|none|thumb|Boundaries visible in JOSM from imported GPX file]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Right click on the Layer output.gpx and select &amp;quot;convert to data layer&amp;quot;. Proceed without simplifying. Now every node should be visible.&lt;br /&gt;
&lt;br /&gt;
=== overlay and align with an orthophoto ===&lt;br /&gt;
&lt;br /&gt;
* select &#039;&#039;Imagery-&amp;gt;More...-&amp;gt;New picture layer from file...&#039;&#039; and open your orthophoto .tif file&lt;br /&gt;
* you can now use the PicLayer tools from the toolbar to align you image layer. When all is aligned, right click on the picture layer and save the picture calibration for later use.&lt;br /&gt;
&lt;br /&gt;
[[File:JOSM with orthophoto.png|none|thumb|mowing area boundaries in JOSM with orthophoto]]&lt;br /&gt;
&lt;br /&gt;
=== modify the boundaries ===&lt;br /&gt;
you can now add/remove/modify/merge/divide:&lt;br /&gt;
&lt;br /&gt;
* mowing or navigation areas&lt;br /&gt;
* obstacles&lt;br /&gt;
* docking point&lt;br /&gt;
&lt;br /&gt;
Using all the nice tools from JOSM.&lt;br /&gt;
&lt;br /&gt;
Following rules for this:&lt;br /&gt;
&lt;br /&gt;
* mowing and navigation areas need to be in counter clockwise direction&lt;br /&gt;
* obstacles need to be in clockwise direction&lt;br /&gt;
&lt;br /&gt;
definition of the feature is in the value of gpx:name tag of each way like this:&lt;br /&gt;
&lt;br /&gt;
* mowing_area_0_area&lt;br /&gt;
* mowing_area_0_obstacle&lt;br /&gt;
* mowing_area_1_area&lt;br /&gt;
* navigation_area_0_area&lt;br /&gt;
* docking_point&lt;br /&gt;
&lt;br /&gt;
[[File:JOSM gpsname tag information.png|none|thumb|area boundary JOSM gps:name tag information]]&lt;br /&gt;
Here you can see an added obstacle.&lt;br /&gt;
&lt;br /&gt;
=== export as GPX and convert back to openmower map.bag ===&lt;br /&gt;
Right click on the gpx layer and &amp;quot;save as&amp;quot;. Select gpx format.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source ./devel/setup.bash&amp;lt;/code&amp;gt; in your &amp;lt;code&amp;gt;open_mower_ros&amp;lt;/code&amp;gt; folder to get the MapArea custom ros message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coordinates in the script. I use the roof mounted RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs in the python script.&lt;br /&gt;
&lt;br /&gt;
Run following python script and put the output_map.bag where your ros.bag was.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;gpx_to_bag.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x gpx_to_bag.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert gpx files to openmower ros map.bag&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import utm&lt;br /&gt;
import rosbag&lt;br /&gt;
import math&lt;br /&gt;
from geometry_msgs.msg import Polygon, Point32, Pose&lt;br /&gt;
from mower_map.msg import MapArea  # Imports the custom message from its package&lt;br /&gt;
from tf.transformations import quaternion_from_euler&lt;br /&gt;
&lt;br /&gt;
# Base point&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_file = open(&#039;map_bag_osm.gpx&#039;, &#039;r&#039;)&lt;br /&gt;
gpx = gpxpy.parse(gpx_file)&lt;br /&gt;
&lt;br /&gt;
mowing_area_dict = {}&lt;br /&gt;
navigation_area_dict = {}&lt;br /&gt;
base_point = []&lt;br /&gt;
docking_points = []&lt;br /&gt;
&lt;br /&gt;
for track in gpx.tracks:&lt;br /&gt;
	print(&amp;quot;reading from gpx: &amp;quot;, track.name)&lt;br /&gt;
	# get points&lt;br /&gt;
	point_list = []&lt;br /&gt;
	for segment in track.segments:&lt;br /&gt;
		print(&amp;quot;Point count:{}&amp;quot;.format(len(segment.points)))&lt;br /&gt;
		for point in segment.points:&lt;br /&gt;
			# print(f&#039;Point at ({point.latitude},{point.longitude}) -&amp;gt; {point.elevation}&#039;)&lt;br /&gt;
			x, y, zone, ut = utm.from_latlon(point.latitude, point.longitude)&lt;br /&gt;
			point_list.append(Point32(x=x-x0, y=y-y0, z=0.0))&lt;br /&gt;
	# store in deciated dict&lt;br /&gt;
	if &amp;quot;mowing&amp;quot; in track.name:&lt;br /&gt;
		mowing_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;navigation&amp;quot; in track.name:&lt;br /&gt;
		navigation_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;base&amp;quot; in track.name:&lt;br /&gt;
		base_point = point_list&lt;br /&gt;
	elif &amp;quot;docking&amp;quot; in track.name:&lt;br /&gt;
		docking_points  = point_list&lt;br /&gt;
&lt;br /&gt;
docking_pose = Pose()&lt;br /&gt;
if len(docking_points) == 2:&lt;br /&gt;
	yaw = math.atan2(docking_points[1].y - docking_points[0].y, docking_points[1].x - docking_points[0].x)&lt;br /&gt;
	quaternion = quaternion_from_euler(0,0,yaw)&lt;br /&gt;
	&lt;br /&gt;
	docking_pose.position.x = docking_points[0].x&lt;br /&gt;
	docking_pose.position.y = docking_points[0].y&lt;br /&gt;
	# Pose Orientation&lt;br /&gt;
	docking_pose.orientation.x = quaternion[0]&lt;br /&gt;
	docking_pose.orientation.y = quaternion[1]&lt;br /&gt;
	docking_pose.orientation.z = quaternion[2]&lt;br /&gt;
	docking_pose.orientation.w = quaternion[3]&lt;br /&gt;
		&lt;br /&gt;
&lt;br /&gt;
def sort_dict_return_MapAreas(area_dict):&lt;br /&gt;
	areas = {}&lt;br /&gt;
	for track_name, point_list in sorted(area_dict.items()): # store sorted polygons to MapArea&lt;br /&gt;
		map_area = MapArea()&lt;br /&gt;
		print(&amp;quot;converting:&amp;quot;, track_name)&lt;br /&gt;
		area_number = track_name.split(&amp;quot;_&amp;quot;)[2]&lt;br /&gt;
		poly = Polygon(points=point_list)&lt;br /&gt;
		if &amp;quot;area&amp;quot; in track_name[-4:]:&lt;br /&gt;
			map_area.area = poly&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
		elif &amp;quot;obstacle&amp;quot; in track_name:&lt;br /&gt;
			map_area = areas[area_number]&lt;br /&gt;
			map_area.obstacles.append(poly)&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
	return areas&lt;br /&gt;
&lt;br /&gt;
mowing_areas_list = sort_dict_return_MapAreas(mowing_area_dict).values()&lt;br /&gt;
navigation_areas_list = sort_dict_return_MapAreas(navigation_area_dict).values()&lt;br /&gt;
&lt;br /&gt;
with rosbag.Bag(&#039;output_map.bag&#039;, &#039;w&#039;) as outbag:&lt;br /&gt;
	print(&#039;writing bag file&#039;)&lt;br /&gt;
	for mowing_area in mowing_areas_list:&lt;br /&gt;
		outbag.write(&amp;quot;mowing_areas&amp;quot;, mowing_area)&lt;br /&gt;
	for navigation_area in navigation_areas_list:	&lt;br /&gt;
		outbag.write(&amp;quot;navigation_areas&amp;quot;, navigation_area)&lt;br /&gt;
	outbag.write(&amp;quot;docking_point&amp;quot;, docking_pose)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:Openmower_map_boundary_orthophoto.png&amp;diff=321</id>
		<title>File:Openmower map boundary orthophoto.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:Openmower_map_boundary_orthophoto.png&amp;diff=321"/>
		<updated>2023-05-11T07:58:37Z</updated>

		<summary type="html">&lt;p&gt;CSobel: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;openmower map boundary with orthophoto layer&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=320</id>
		<title>Modify map using drone footage</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Modify_map_using_drone_footage&amp;diff=320"/>
		<updated>2023-05-11T07:54:14Z</updated>

		<summary type="html">&lt;p&gt;CSobel: initial version&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Introduction ===&lt;br /&gt;
This tutorial shows how to use an orthophoto (generated by taking aerial pictures with a drone) and modify the open mower map.&lt;br /&gt;
&lt;br /&gt;
* Generate an orthophoto using aerial drone footage and opendronemaps WebODM&lt;br /&gt;
* convert the openmower map.bag into GPX format&lt;br /&gt;
* load the GPX file into JOSM&lt;br /&gt;
* overlay and align with an orthophoto&lt;br /&gt;
* modify the boundaries&lt;br /&gt;
* export as GPX&lt;br /&gt;
* convert the GPX back to openmower map.bag&lt;br /&gt;
&lt;br /&gt;
=== Generate an orthophoto ===&lt;br /&gt;
I took about 50 pictures with a drone ca. 10m above ground and generated an orthophoto using OpenDroneMap WebODM&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can read about the process here: https://docs.opendronemap.org/tutorials/&lt;br /&gt;
&lt;br /&gt;
OpenDroneMap WebODM runs in a docker container and can be found here: https://www.opendronemap.org/webodm/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can export an orthophoto .tif format there&lt;br /&gt;
&lt;br /&gt;
=== convert map.bag into GPX ===&lt;br /&gt;
Obviously back up your map.bag&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I wrote a python script to convert the map area coordinates into utm coordinates. I ran this on the robots raspberry pi. In reads the map.bag from .ros folder and exports a .gpx format file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Following python packages are needed&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pip install utm gpxpy&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coodinates in the script. I use the roof mouned RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs and run the python script.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x bag_to_gpx_tracks_utm.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert openmower ros map.bag to gpx file&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
from codecs import latin_1_decode&lt;br /&gt;
import rosbag&lt;br /&gt;
import utm&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import math&lt;br /&gt;
import os&lt;br /&gt;
from tf.transformations import euler_from_quaternion&lt;br /&gt;
&lt;br /&gt;
bag = rosbag.Bag(&#039;/home/pi/.ros/map.bag&#039;)&lt;br /&gt;
# bag = rosbag.Bag(&#039;output.bag&#039;)&lt;br /&gt;
&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
&lt;br /&gt;
topics = bag.get_type_and_topic_info()[1].keys()&lt;br /&gt;
print(topics)&lt;br /&gt;
&lt;br /&gt;
gpx = gpxpy.gpx.GPX()&lt;br /&gt;
gpx.name = &#039;open_mower_map&#039;&lt;br /&gt;
gpx.description = &#039;Map data from openmower map.bag&#039;&lt;br /&gt;
&lt;br /&gt;
# base point (e.g. gps antenna)&lt;br /&gt;
# base_point = gpxpy.gpx.GPXWaypoint(latitude=lat0, longitude=lon0, name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
# gpx.waypoints.append(base_point)&lt;br /&gt;
&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;base_point&amp;quot;)&lt;br /&gt;
gpx.tracks.append(gpx_track)&lt;br /&gt;
gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
gpx_track.segments.append(gpx_segment)&lt;br /&gt;
gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat0, lon0, elevation=0))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Loop through areas and obstacles&lt;br /&gt;
area_counter = 0&lt;br /&gt;
&lt;br /&gt;
for area_type in [&#039;mowing_areas&#039;, &#039;navigation_areas&#039;]:&lt;br /&gt;
    for topic, msg, t in bag.read_messages(topics=[area_type]):&lt;br /&gt;
        print(&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        # Create track in GPX:&lt;br /&gt;
        gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_area&amp;quot;.format(area_type[:-1], area_counter))&lt;br /&gt;
        gpx.tracks.append(gpx_track)&lt;br /&gt;
        # Create segment in GPX track:&lt;br /&gt;
        gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
        gpx_track.segments.append(gpx_segment)&lt;br /&gt;
&lt;br /&gt;
        # outline&lt;br /&gt;
        for point in msg.area.points:&lt;br /&gt;
            # Create points:&lt;br /&gt;
            lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
            gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
&lt;br /&gt;
        # obstacles&lt;br /&gt;
        obstacle_counter = 0&lt;br /&gt;
        for obst in msg.obstacles:&lt;br /&gt;
            print(&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            # Create track in GPX:&lt;br /&gt;
            gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;{}_{}_obstacle_{}&amp;quot;.format(area_type[:-1], area_counter, obstacle_counter))&lt;br /&gt;
            gpx.tracks.append(gpx_track)&lt;br /&gt;
            # Create segment in GPX track:&lt;br /&gt;
            gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
            gpx_track.segments.append(gpx_segment)&lt;br /&gt;
            for point in obst.points:&lt;br /&gt;
                # Create points:&lt;br /&gt;
                lat, lon = utm.to_latlon(x0 + point.x, y0 + point.y, zone, ut)&lt;br /&gt;
                gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=0))&lt;br /&gt;
            obstacle_counter += 1&lt;br /&gt;
        area_counter += 1&lt;br /&gt;
&lt;br /&gt;
# docking point&lt;br /&gt;
for topic, msg, t in bag.read_messages(topics=[&#039;docking_point&#039;]):&lt;br /&gt;
    # print(msg)&lt;br /&gt;
    print(&amp;quot;parsing docking point&amp;quot;)&lt;br /&gt;
    quat_list = [msg.orientation.x, msg.orientation.y, msg.orientation.z, msg.orientation.w]&lt;br /&gt;
    (roll, pitch, yaw) = euler_from_quaternion(quat_list)&lt;br /&gt;
    # print((roll, pitch, yaw))&lt;br /&gt;
    x2 = msg.position.x + math.cos(yaw)&lt;br /&gt;
    y2 = msg.position.y + math.sin(yaw)&lt;br /&gt;
&lt;br /&gt;
    lat1, lon1 = utm.to_latlon(x0 + msg.position.x, y0 + msg.position.y, zone, ut)&lt;br /&gt;
    lat2, lon2 = utm.to_latlon(x0 + x2,y0 + y2, zone, ut)&lt;br /&gt;
&lt;br /&gt;
    gpx_track = gpxpy.gpx.GPXTrack(name=&amp;quot;docking_point&amp;quot;)&lt;br /&gt;
    gpx.tracks.append(gpx_track)&lt;br /&gt;
    gpx_segment = gpxpy.gpx.GPXTrackSegment()&lt;br /&gt;
    gpx_track.segments.append(gpx_segment)&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat1, lon1, elevation=0))&lt;br /&gt;
    gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat2, lon2, elevation=0))&lt;br /&gt;
&lt;br /&gt;
print(&#039;Created GPX file&#039;)&lt;br /&gt;
with open(&amp;quot;map_bag.gpx&amp;quot;, &amp;quot;w&amp;quot;) as f:&lt;br /&gt;
    f.write(gpx.to_xml())&lt;br /&gt;
&lt;br /&gt;
bag.close()&lt;br /&gt;
print(&#039;Done&#039;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== load GPX file into JOSM ===&lt;br /&gt;
Install the Java OpenStreetMap Editor from:&lt;br /&gt;
&lt;br /&gt;
https://josm.openstreetmap.de/wiki/Download&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start JOSM and install the PicLayer Plugin. For this go to &#039;&#039;Edit-&amp;gt;Preferences-&amp;gt;Plugins&#039;&#039; and search for PicLayer and tick the checkbox.&lt;br /&gt;
&lt;br /&gt;
Open your .gpx file, you shoud see the recorded boundaries&lt;br /&gt;
[[File:JOSM imported GPX file.png|none|thumb|Boundaries visible in JOSM from imported GPX file]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Right click on the Layer output.gpx and select &amp;quot;convert to data layer&amp;quot;. Proceed without simplifying. Now every node should be visible.&lt;br /&gt;
&lt;br /&gt;
=== overlay and align with an orthophoto ===&lt;br /&gt;
&lt;br /&gt;
* select &#039;&#039;Imagery-&amp;gt;More...-&amp;gt;New picture layer from file...&#039;&#039; and open your orthophoto .tif file&lt;br /&gt;
* you can now use the PicLayer tools from the toolbar to align you image layer. When all is aligned, right click on the picture layer and save the picture calibration for later use.&lt;br /&gt;
&lt;br /&gt;
[[File:JOSM with orthophoto.png|none|thumb|mowing area boundaries in JOSM with orthophoto]]&lt;br /&gt;
&lt;br /&gt;
=== modify the boundaries ===&lt;br /&gt;
you can now add/remove/modify/merge/divide:&lt;br /&gt;
&lt;br /&gt;
* mowing or navigation areas&lt;br /&gt;
* obstacles&lt;br /&gt;
* docking point&lt;br /&gt;
&lt;br /&gt;
Using all the nice tools from JOSM.&lt;br /&gt;
&lt;br /&gt;
Following rules for this:&lt;br /&gt;
&lt;br /&gt;
* mowing and navigation areas need to be in counter clockwise direction&lt;br /&gt;
* obstacles need to be in clockwise direction&lt;br /&gt;
&lt;br /&gt;
definition of the feature is in the value of gpx:name tag of each way like this:&lt;br /&gt;
&lt;br /&gt;
* mowing_area_0_area&lt;br /&gt;
* mowing_area_0_obstacle&lt;br /&gt;
* mowing_area_1_area&lt;br /&gt;
* navigation_area_0_area&lt;br /&gt;
* docking_point&lt;br /&gt;
&lt;br /&gt;
[[File:JOSM gpsname tag information.png|none|thumb|area boundary JOSM gps:name tag information]]&lt;br /&gt;
Here you can see an added obstacle.&lt;br /&gt;
&lt;br /&gt;
=== export as GPX and convert back to openmower map.bag ===&lt;br /&gt;
Right click on the gpx layer and &amp;quot;save as&amp;quot;. Select gpx format.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;source /opt/ros/noetic/setup.bash&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to &amp;lt;code&amp;gt;source ./devel/setup.bash&amp;lt;/code&amp;gt; in your &amp;lt;code&amp;gt;open_mower_ros&amp;lt;/code&amp;gt; folder to get the MapArea custom ros message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Modify the base point coordinates in the script. I use the roof mounted RTK GPS antenna as base coordinates.&lt;br /&gt;
&lt;br /&gt;
Modify the input and output file paths to your needs in the python script.&lt;br /&gt;
&lt;br /&gt;
Run following python script and put the output_map.bag where your ros.bag was.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Create a file wit following content: &amp;lt;code&amp;gt;gpx_to_bag.py&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;chmod +x gpx_to_bag.py&amp;lt;/code&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
&lt;br /&gt;
#===========================================&lt;br /&gt;
# Christoph Sobel 11.05.2023&lt;br /&gt;
# script to convert gpx files to openmower ros map.bag&lt;br /&gt;
#===========================================&lt;br /&gt;
&lt;br /&gt;
import gpxpy.gpx&lt;br /&gt;
import utm&lt;br /&gt;
import rosbag&lt;br /&gt;
import math&lt;br /&gt;
from geometry_msgs.msg import Polygon, Point32, Pose&lt;br /&gt;
from mower_map.msg import MapArea  # Imports the custom message from its package&lt;br /&gt;
from tf.transformations import quaternion_from_euler&lt;br /&gt;
&lt;br /&gt;
# Base point&lt;br /&gt;
lat0 = 53.00&lt;br /&gt;
lon0 = 8.00&lt;br /&gt;
# lat0 = float(os.getenv(&#039;OM_DATUM_LAT&#039;))&lt;br /&gt;
# lon0 = float(os.getenv(&#039;OM_DATUM_LONG&#039;))&lt;br /&gt;
print(&amp;quot;base_point&amp;quot;, lat0, &amp;quot; &amp;quot;, lon0)&lt;br /&gt;
x0, y0, zone, ut = utm.from_latlon(lat0, lon0)&lt;br /&gt;
print(&amp;quot;base_point in utm&amp;quot;, x0, &amp;quot; &amp;quot;, y0)&lt;br /&gt;
&lt;br /&gt;
gpx_file = open(&#039;map_bag_osm.gpx&#039;, &#039;r&#039;)&lt;br /&gt;
gpx = gpxpy.parse(gpx_file)&lt;br /&gt;
&lt;br /&gt;
mowing_area_dict = {}&lt;br /&gt;
navigation_area_dict = {}&lt;br /&gt;
base_point = []&lt;br /&gt;
docking_points = []&lt;br /&gt;
&lt;br /&gt;
for track in gpx.tracks:&lt;br /&gt;
	print(&amp;quot;reading from gpx: &amp;quot;, track.name)&lt;br /&gt;
	# get points&lt;br /&gt;
	point_list = []&lt;br /&gt;
	for segment in track.segments:&lt;br /&gt;
		print(&amp;quot;Point count:{}&amp;quot;.format(len(segment.points)))&lt;br /&gt;
		for point in segment.points:&lt;br /&gt;
			# print(f&#039;Point at ({point.latitude},{point.longitude}) -&amp;gt; {point.elevation}&#039;)&lt;br /&gt;
			x, y, zone, ut = utm.from_latlon(point.latitude, point.longitude)&lt;br /&gt;
			point_list.append(Point32(x=x-x0, y=y-y0, z=0.0))&lt;br /&gt;
	# store in deciated dict&lt;br /&gt;
	if &amp;quot;mowing&amp;quot; in track.name:&lt;br /&gt;
		mowing_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;navigation&amp;quot; in track.name:&lt;br /&gt;
		navigation_area_dict[track.name] = point_list&lt;br /&gt;
	elif &amp;quot;base&amp;quot; in track.name:&lt;br /&gt;
		base_point = point_list&lt;br /&gt;
	elif &amp;quot;docking&amp;quot; in track.name:&lt;br /&gt;
		docking_points  = point_list&lt;br /&gt;
&lt;br /&gt;
docking_pose = Pose()&lt;br /&gt;
if len(docking_points) == 2:&lt;br /&gt;
	yaw = math.atan2(docking_points[1].y - docking_points[0].y, docking_points[1].x - docking_points[0].x)&lt;br /&gt;
	quaternion = quaternion_from_euler(0,0,yaw)&lt;br /&gt;
	&lt;br /&gt;
	docking_pose.position.x = docking_points[0].x&lt;br /&gt;
	docking_pose.position.y = docking_points[0].y&lt;br /&gt;
	# Pose Orientation&lt;br /&gt;
	docking_pose.orientation.x = quaternion[0]&lt;br /&gt;
	docking_pose.orientation.y = quaternion[1]&lt;br /&gt;
	docking_pose.orientation.z = quaternion[2]&lt;br /&gt;
	docking_pose.orientation.w = quaternion[3]&lt;br /&gt;
		&lt;br /&gt;
&lt;br /&gt;
def sort_dict_return_MapAreas(area_dict):&lt;br /&gt;
	areas = {}&lt;br /&gt;
	for track_name, point_list in sorted(area_dict.items()): # store sorted polygons to MapArea&lt;br /&gt;
		map_area = MapArea()&lt;br /&gt;
		print(&amp;quot;converting:&amp;quot;, track_name)&lt;br /&gt;
		area_number = track_name.split(&amp;quot;_&amp;quot;)[2]&lt;br /&gt;
		poly = Polygon(points=point_list)&lt;br /&gt;
		if &amp;quot;area&amp;quot; in track_name[-4:]:&lt;br /&gt;
			map_area.area = poly&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
		elif &amp;quot;obstacle&amp;quot; in track_name:&lt;br /&gt;
			map_area = areas[area_number]&lt;br /&gt;
			map_area.obstacles.append(poly)&lt;br /&gt;
			areas[area_number] = map_area&lt;br /&gt;
	return areas&lt;br /&gt;
&lt;br /&gt;
mowing_areas_list = sort_dict_return_MapAreas(mowing_area_dict).values()&lt;br /&gt;
navigation_areas_list = sort_dict_return_MapAreas(navigation_area_dict).values()&lt;br /&gt;
&lt;br /&gt;
with rosbag.Bag(&#039;output_map.bag&#039;, &#039;w&#039;) as outbag:&lt;br /&gt;
	print(&#039;writing bag file&#039;)&lt;br /&gt;
	for mowing_area in mowing_areas_list:&lt;br /&gt;
		outbag.write(&amp;quot;mowing_areas&amp;quot;, mowing_area)&lt;br /&gt;
	for navigation_area in navigation_areas_list:	&lt;br /&gt;
		outbag.write(&amp;quot;navigation_areas&amp;quot;, navigation_area)&lt;br /&gt;
	outbag.write(&amp;quot;docking_point&amp;quot;, docking_pose)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_gpsname_tag_information.png&amp;diff=319</id>
		<title>File:JOSM gpsname tag information.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_gpsname_tag_information.png&amp;diff=319"/>
		<updated>2023-05-11T07:41:05Z</updated>

		<summary type="html">&lt;p&gt;CSobel: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;areas with gpx:name tag for featue definition&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_with_orthophoto.png&amp;diff=318</id>
		<title>File:JOSM with orthophoto.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_with_orthophoto.png&amp;diff=318"/>
		<updated>2023-05-11T07:39:49Z</updated>

		<summary type="html">&lt;p&gt;CSobel: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mowing area boundaries in JOSM with orthophoto&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=317</id>
		<title>File:JOSM imported GPX file.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=File:JOSM_imported_GPX_file.png&amp;diff=317"/>
		<updated>2023-05-11T07:36:45Z</updated>

		<summary type="html">&lt;p&gt;CSobel: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Boundaries visible in JOSM from imported GPX file&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
	<entry>
		<id>https://wiki.openmower.de/index.php?title=Running_an_OpenMower_simulation&amp;diff=156</id>
		<title>Running an OpenMower simulation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openmower.de/index.php?title=Running_an_OpenMower_simulation&amp;diff=156"/>
		<updated>2022-06-04T22:36:18Z</updated>

		<summary type="html">&lt;p&gt;CSobel: corrected launchfile name for running the simulation, added info about button modification&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
You can test the OpenMower software stack yourself by running a simulation where you can record a map and then have it mowed in a simulation on-screen. &lt;br /&gt;
&lt;br /&gt;
It&#039;s a bit rough around the edges, but it&#039;s a good way to see the software in action without hardware.&lt;br /&gt;
&lt;br /&gt;
This mini-HOWTO is meant to save you a bit of detective work and get you started, not to be a comprehensive guide to the simulation.&lt;br /&gt;
&lt;br /&gt;
== Prerequisites ==&lt;br /&gt;
You need the OpenMower software &#039;&#039;&#039;on a platform that has ROS and a graphical interface&#039;&#039;&#039; for rviz to be able to run and show the map. Ubuntu 20 on a desktop is a good choice, possibly on VM, VirtualBox is an excellent choice, if you do not already have a running Ubuntu 20 desktop environment.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Install ROS on Ubuntu&#039;&#039;&#039;: http://wiki.ros.org/noetic/Installation/Ubuntu - use the full install&lt;br /&gt;
** Note: make sure that you have the ROS environment set up in your terminal by &amp;lt;code&amp;gt;echo &amp;quot;source /opt/ros/noetic/setup.bash&amp;quot; &amp;gt;&amp;gt; ~/.bashrc&amp;lt;/code&amp;gt;  and log out and back in.&lt;br /&gt;
* &#039;&#039;&#039;Install the OpenMower software:&#039;&#039;&#039; Use the instructions here: [[Setting up ROS on a Raspberry Pi (intended to be the Rover -- OpenMower)]]&lt;br /&gt;
** Make sure that you have had &amp;lt;code&amp;gt;catkin_make&amp;lt;/code&amp;gt; succeed (may take a few tries, often 4 or more)&lt;br /&gt;
&lt;br /&gt;
You need to have a game controller for controlling the simulated bot.&lt;br /&gt;
&lt;br /&gt;
== Making sure the OpenMower environment is sourced ==&lt;br /&gt;
&#039;&#039;Whenever you are doing anything in a terminal with OpenMower&#039;&#039;, you need to make sure that ROS knows your OpenMower workspace.&lt;br /&gt;
&lt;br /&gt;
In every terminal that you use: &amp;lt;code&amp;gt;source ~/OpenMower/ROS/devel/setup.sh&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Running the simulation ==&lt;br /&gt;
&lt;br /&gt;
=== Recording a map ===&lt;br /&gt;
[[File:VirtualBox enable USB.png|thumb|Enable USB ports on VirtualBox]]&lt;br /&gt;
Attach your joystick to a USB port. If you are on a VirtualBox, make sure that the USB port of your joystick / gamepad is mapped into your Ubuntu environment (see illustration).&lt;br /&gt;
&lt;br /&gt;
Open a terminal and execute&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;roslaunch ~/OpenMower/ROS/src/open_mower_ros/open_mower/launch/sim_record_area.launch&amp;lt;/code&amp;gt;&lt;br /&gt;
[[File:Simulation running.png|thumb|OpenMower simulation running]]&lt;br /&gt;
This will launch rviz where you can see the Rover and the map.&lt;br /&gt;
&lt;br /&gt;
Use your joystick to record a map and a base. On my gamepad, the btns are&lt;br /&gt;
&lt;br /&gt;
* 1 - enable movement&lt;br /&gt;
* 2 - start path recording&lt;br /&gt;
* 4 - end path recording&lt;br /&gt;
* 3 - register base&lt;br /&gt;
&lt;br /&gt;
Then exit rviz and the map will be recorded in &amp;lt;code&amp;gt;~/.ros/map.bag&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to record a new map, delete the old one, &amp;lt;code&amp;gt;rm ~/.ros/map.bag&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the simulation ===&lt;br /&gt;
&amp;lt;code&amp;gt;roslaunch ~/OpenMower/ROS/src/open_mower_ros/open_mower/launch/sim_mower_logic.launch&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will launch rviz with the map and rqt_reconfigure where you can control various parameters of the simulation.&lt;br /&gt;
&lt;br /&gt;
You will need to press &amp;lt;code&amp;gt;manual_start_mowing&amp;lt;/code&amp;gt; in rqt_reconfigure to start the simulation. You will probably need to zoom out, find your area and then zoom in again on that in rviz.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics / debugging / issues ==&lt;br /&gt;
&lt;br /&gt;
=== Joystick ===&lt;br /&gt;
[[File:Which joystick to use.png|thumb|Which joystick to use]]&lt;br /&gt;
Your joystick needs to be transmitting messages on the topic /joy. To test that it does:&lt;br /&gt;
&lt;br /&gt;
* Run &amp;lt;code&amp;gt;roslaunch ~/OpenMower/ROS/src/open_mower_ros/open_mower/launch/sim_record_area.launch&amp;lt;/code&amp;gt;&lt;br /&gt;
* In a new terminal run &amp;lt;code&amp;gt;rostopic echo /joy&amp;lt;/code&amp;gt;&lt;br /&gt;
You should see messages from the joystick streaming on screen. Test that pressing buttons etc. is reflected in the messages.&lt;br /&gt;
&lt;br /&gt;
If not:&lt;br /&gt;
&lt;br /&gt;
* Check which joysticks your Ubuntu installation knows: &amp;lt;code&amp;gt;ls -l /dev/input/js*&amp;lt;/code&amp;gt;&lt;br /&gt;
* Try to add/adjust wich joystick the recorder uses in sim_record_area.launch to use the correct joystick, see illustration here.&lt;br /&gt;
&lt;br /&gt;
===Buttons===&lt;br /&gt;
The control buttons can be edited here:&lt;br /&gt;
*movement activation button&lt;br /&gt;
**File: &amp;lt;code&amp;gt;~/OpenMower/ROS/src/open_mower_ros/open_mower/launch/sim_record_area.launch&amp;lt;/code&amp;gt;&lt;br /&gt;
**Line 17: &amp;lt;code&amp;gt;&amp;lt;param name=&amp;quot;~enable_turbo_button&amp;quot; value=&amp;quot;4&amp;quot;/&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
*area and base recording buttons&lt;br /&gt;
**File: &amp;lt;code&amp;gt;~/OpenMower/ROS/src/open_mower_ros/mower_area_recorder/src/area_recorder.cpp&amp;lt;/code&amp;gt;&lt;br /&gt;
**Lines 61 ff.&lt;br /&gt;
&lt;br /&gt;
==Additional notes / info==&lt;br /&gt;
&lt;br /&gt;
===rviz and OpenMower on different machines ===&lt;br /&gt;
This is all running on one platform - the OpenMower simulation AND the graphical interface, hence the choice of desktop to have a graphical environment. In the real situation, the Rover with the OpenMower software is running on a headless Pi. If you still want to have the ability to see paths and maps (which is possible) in rviz, you need to do that on a separate desktop environment. That is possible. You can use the environment that you set up here, but the Rover and on the Desktop must be on the same network and need a little setup in your &amp;lt;code&amp;gt;~/.bashrc&amp;lt;/code&amp;gt; to see each other as being part of the same ROS installation.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;On Rover, in &amp;lt;code&amp;gt;~/.bashrc&amp;lt;/code&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export ROS_HOSTNAME=192.168.1.126 # replace with your ip for your Rover&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export ROS_MASTER_URI=&amp;lt;nowiki&amp;gt;http://192.168.1.126:11311&amp;lt;/nowiki&amp;gt; # same as above&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;On Desktop, in &amp;lt;code&amp;gt;~/.bashrc&amp;lt;/code&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export ROS_HOSTNAME=192.168.1.123 # replace with your ip for your Desktop environment&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export ROS_MASTER_URI=&amp;lt;nowiki&amp;gt;http://192.168.1.126:11311&amp;lt;/nowiki&amp;gt; # eplace with your ip for your Rover&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Note on network visibility and guests on VirtualBox====&lt;br /&gt;
To make your Ubuntu installation running as guest in VirtualBox be able to have an externally accessible ip number and be able to talk to the Rover, you must&lt;br /&gt;
&lt;br /&gt;
#use a cabled network (not Wifi!!) to your computer that acts as host for VirtualBox and your Ubuntu guest OS&lt;br /&gt;
#use a &amp;lt;code&amp;gt;Bridged Adaptor&amp;lt;/code&amp;gt; to the cabled interface for the network setup in VirtualBox for your Ubuntu host.&lt;/div&gt;</summary>
		<author><name>CSobel</name></author>
	</entry>
</feed>