> ## Documentation index
> Fetch the complete documentation index at: https://developer.rhombus.com/llms.txt
> Use it to discover all available pages before exploring further.

# 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.

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](https://developer.rhombus.com/robots/overview#conventions).

**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`](https://developer.rhombus.com/api-reference/robot-webservice/retrieve-the-robots-map-assignment-record) returns it:

```python 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](https://developer.rhombus.com/robots/maps-and-waypoints#keep-out-zones)).
- 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`](https://developer.rhombus.com/api-reference/robot-webservice/retrieve-the-list-of-available-robot-maps) with a `locationUuid` (or without one for the whole organization).

## Draw the map

[`getMapImage`](https://developer.rhombus.com/api-reference/robot-webservice/robots-map-image) 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 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`](https://developer.rhombus.com/api-reference/robot-webservice/get-the-robots-map-meta-data) 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 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`](https://developer.rhombus.com/robots/monitor#fleet-state) and draw them through `map_to_pixel`:

```python 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](https://developer.rhombus.com/robots/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`](https://developer.rhombus.com/api-reference/robot-webservice/robots-map-filter-image) returns its mask, a PGM image the same size as the map:

```python 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.

| Operation                                                                                                          | Body                                                                      |
| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| [`createRobotWaypoint`](https://developer.rhombus.com/api-reference/robot-webservice/create-a-standalone-waypoint) | `mapUuid`, `name`, `pose`                                                 |
| [`findRobotWaypointsByMap`](https://developer.rhombus.com/api-reference/robot-webservice/find-waypoints-by-map)    | `mapUuid`                                                                 |
| [`getRobotWaypoint`](https://developer.rhombus.com/api-reference/robot-webservice/get-a-waypoint-by-uuid)          | `mapUuid`, `waypointUuid`                                                 |
| [`updateRobotWaypoint`](https://developer.rhombus.com/api-reference/robot-webservice/update-a-waypoint)            | `waypointUuid`, and `name` or `pose` (a field you leave out is unchanged) |
| [`deleteRobotWaypoint`](https://developer.rhombus.com/api-reference/robot-webservice/delete-a-waypoint)            | `mapUuid`, `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](https://developer.rhombus.com/robots/live-socket)), then save its current pose:

```python 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](https://developer.rhombus.com/robots/overview#conventions):

```python 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`](https://developer.rhombus.com/api-reference/robot-webservice/find-routes-by-map)) and update those routes first.
