Skip to content

Recon Maps and Waypoints

Work with a Recon robot's map through the API: find the assigned map, draw it with the robot's position and keep-out zones, convert between image pixels and map coordinates, and save waypoints for routes.

On this page

Everything a robot does happens on its map: poses, waypoints and routes are all coordinates in the map's frame. This guide reads the map, draws it, and places waypoints on it. The examples use the robot_api helper from the overview.

You need an API key with read-only access to the robot's location to read the map, and admin access to it to save waypoints.

Find the robot's map

A robot builds its map during setup, and each robot has one assigned map. getMapAssignment returns it:

Python
assignment = robot_api("getMapAssignment", {"deviceUuid": ROBOT_UUID})
map_meta = assignment.get("mapMeta")
if not map_meta:
    raise SystemExit("This robot has no assigned map yet")
MAP_UUID = map_meta["uuid"]
keepouts = [f for f in assignment.get("mapFilters", []) if f["type"] == "KEEPOUT"]
  • mapMeta describes the map: uuid, locationUuid, resolution, origin and, when the map is placed on a floor plan, floorPlan.
  • mapFilters lists the keep-out zones applied on this map (below).
  • A robot with no assigned map returns neither. Routes and patrols need one.

To list every map at a location, for example to choose one before you create waypoints, call findMaps with a locationUuid (or without one for the whole organization).

Draw the map

getMapImage returns the robot's assigned map as a grayscale PGM image (image/x-portable-graymap): occupied space dark, free space light. Most image libraries read PGM:

Python
import io
from PIL import Image

response = requests.post(f"{API}/getMapImage", headers=HEADERS, json={"deviceUuid": ROBOT_UUID}, timeout=60)
response.raise_for_status()
map_image = Image.open(io.BytesIO(response.content)).convert("RGB")
map_image.save("map.png")

A robot with no assigned map answers 404.

Pixels and map coordinates

getMapMeta returns the two values that connect the image to the map's coordinates:

  • resolution: meters per pixel.
  • origin: the map position of the image's lower-left corner. Use its first two values, x and y.

Image rows count down from the top, while map y grows upward:

Python
meta = robot_api("getMapMeta", {"deviceUuid": ROBOT_UUID})
RES = meta["resolution"]
OX, OY = meta["origin"][0], meta["origin"][1]
HEIGHT = map_image.height

def map_to_pixel(x, y):
    return (x - OX) / RES, HEIGHT - (y - OY) / RES

def pixel_to_map(px, py):
    return px * RES + OX, (HEIGHT - py) * RES + OY

To show where the robot is and where it's going, read pose, goalPose and navPath (its planned path, a list of [x, y, z] points) from getFullState and draw them through map_to_pixel:

Python
from PIL import ImageDraw

state = robot_api("getFullState", {"deviceUuid": ROBOT_UUID, "force": False})["fullState"]
draw = ImageDraw.Draw(map_image)
path = [map_to_pixel(p[0], p[1]) for p in state.get("navPath") or []]
if len(path) > 1:
    draw.line(path, fill=(0, 128, 0), width=2)
if state.get("pose"):
    px, py = map_to_pixel(*state["pose"]["position"][:2])
    draw.ellipse([px - 4, py - 4, px + 4, py + 4], fill=(0, 102, 204))
map_image.save("map-with-robot.png")

For a dot that moves with the robot, the robot live socket streams the pose as it changes.

Keep-out zones

A keep-out zone is an area the robot never enters. Each one in mapFilters has a uuid and a name; getMapFilterImage returns its mask, a PGM image the same size as the map:

Python
for zone in keepouts:
    response = requests.post(
        f"{API}/getMapFilterImage",
        headers=HEADERS,
        json={"deviceUuid": ROBOT_UUID, "mapFilterUuid": zone["uuid"]},
        timeout=60,
    )
    response.raise_for_status()
    mask = Image.open(io.BytesIO(response.content))
    mask.save(f"keepout-{zone['name']}.png")

In the mask, 0 (black) is an area the robot never enters, 255 (white) has no effect, and values in between are areas the robot avoids when it can. Draw the black pixels over the map in a warning color, and don't place waypoints there.

Waypoints

A waypoint is a named pose on a map. Route stops can reference a waypoint instead of repeating its pose, so several routes can share the same spot.

OperationBody
createRobotWaypointmapUuid, name, pose
findRobotWaypointsByMapmapUuid
getRobotWaypointmapUuid, waypointUuid
updateRobotWaypointwaypointUuid, and name or pose (a field you leave out is unchanged)
deleteRobotWaypointmapUuid, waypointUuid

Save the spot where the robot stands

The most reliable pose is one the robot has reached: drive it to the spot (in the console or over the live socket), then save its current pose:

Python
state = robot_api("getFullState", {"deviceUuid": ROBOT_UUID, "force": False})["fullState"]
waypoint = robot_api("createRobotWaypoint", {
    "mapUuid": state["mapId"],
    "name": "Loading dock door",
    "pose": state["pose"],
})["waypoint"]
print(waypoint["uuid"])

Place one from the map

To let someone click a spot on the map image, convert the click with pixel_to_map and the chosen heading with heading_to_orientation from the overview:

Python
import math

x, y = pixel_to_map(412, 238)
waypoint = robot_api("createRobotWaypoint", {
    "mapUuid": MAP_UUID,
    "name": "North fence",
    "pose": {"position": [x, y, 0.0], "orientation": heading_to_orientation(math.pi / 2)},
})["waypoint"]

Check the clicked pixel against the map and the keep-out masks first: a waypoint in a wall or a keep-out zone is one the robot can't reach.

Before you delete one

Deleting a waypoint doesn't check whether routes use it, and a route stop whose waypoint is gone is skipped. Look for its uuid in the waypointRefUuid of each route's stops (findRobotRoutesByMap) and update those routes first.