Robot Dokümantasyonu/Rehberler

Hedefleri Haritada Göster

Bu rehberde robotun haritasını ve tanımlı tüm hedefleri alıp Python ile tek bir matplotlib penceresinde birlikte çizmeyi göreceksiniz. Betiği çalıştırdığınızda arka planda robotun doluluk ızgarası (occupancy grid) haritası, üzerinde de her hedef etiketli bir işaretle görünür.

Gereksinimler:

  • Robota erişimi olan bir API anahtarı ve robotun ID'si (bkz. Başlangıç)
  • Python 3.8 veya üstü
  • Bağımlılıkların kurulumu (bir kez): pip install requests matplotlib pillow
#!/usr/bin/env python3
"""
Visualize robot targets on the map.

Requirements:
    pip install requests matplotlib pillow

Usage:
    ROBOT_ID=<robot id> API_KEY=<api key> python visualize_targets.py
"""

import base64
import io
import os
import sys

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import requests
from PIL import Image

# ── configuration ────────────────────────────────────────────────────────────
ROBOT_ID = os.getenv("ROBOT_ID", "")
API_KEY = os.getenv("API_KEY", "")
ROBOT_URL = os.getenv("ROBOT_URL", f"https://robots.saharobotik.com/api/v1/{ROBOT_ID}/robot")
HEADERS = {"x-api-key": API_KEY}
# ─────────────────────────────────────────────────────────────────────────────


def fetch_map() -> dict:
    resp = requests.get(f"{ROBOT_URL}/mapping/map", headers=HEADERS, timeout=10)
    resp.raise_for_status()
    return resp.json()


def fetch_targets() -> list:
    resp = requests.get(f"{ROBOT_URL}/targets", headers=HEADERS, timeout=10)
    resp.raise_for_status()
    return resp.json()


def world_to_pixel(wx: float, wy: float, origin: dict, resolution: float, height: int):
    """Convert world coordinates (meters) to image pixel coordinates."""
    px = (wx - origin["x"]) / resolution
    py = height - (wy - origin["y"]) / resolution  # flip y because image origin is top-left
    return px, py


def main():
    if not ROBOT_ID or not API_KEY:
        print("Set the ROBOT_ID and API_KEY environment variables")
        sys.exit(1)

    print(f"Robot URL : {ROBOT_URL}")

    # 1 ── fetch map ──────────────────────────────────────────────────────────
    print("[1/2] Fetching map...")
    try:
        map_data = fetch_map()
    except Exception as exc:
        print(f"  ✗ Could not fetch map: {exc}")
        sys.exit(1)

    resolution  = map_data["resolution"]                            # meters / pixel
    img_height  = map_data["height"]                               # pixels
    origin      = map_data.get("origin", {"x": 0.0, "y": 0.0, "z": 0.0})
    image_bytes = base64.b64decode(map_data["map_png_base64"])
    map_image   = Image.open(io.BytesIO(image_bytes))
    print(f"  ✓ Map loaded  ({map_data['width']} × {img_height} px, {resolution} m/px)")

    # 2 ── fetch targets ──────────────────────────────────────────────────────
    print("[2/2] Fetching targets...")
    try:
        targets = fetch_targets()
    except Exception as exc:
        print(f"  ✗ Could not fetch targets: {exc}")
        sys.exit(1)
    print(f"  ✓ {len(targets)} target(s) found")

    # 3 ── plot ───────────────────────────────────────────────────────────────
    colors = plt.cm.tab10.colors

    DPI = 100
    MAX_INCHES = 22
    MIN_INCHES = 16
    img_w = map_data["width"]

    # derive figure size from actual image pixels, capped to screen-safe range
    aspect = img_height / img_w if img_w else 1
    fw = max(MIN_INCHES, min(img_w / DPI, MAX_INCHES))
    fh = max(MIN_INCHES * aspect, min(fw * aspect, MAX_INCHES))

    fig, ax = plt.subplots(figsize=(fw, fh), dpi=DPI)
    ax.imshow(map_image, cmap="gray", origin="upper")

    # lock axes to image bounds so out-of-range targets don't shrink the map
    ax.set_xlim(0, img_w)
    ax.set_ylim(img_height, 0)  # imshow origin=upper → y increases downward

    ax.set_title(f"Robot Targets on Map  ({len(targets)} targets)", fontsize=14, pad=12)
    ax.set_xlabel("X (pixels)")
    ax.set_ylabel("Y (pixels)")

    for i, target in enumerate(targets):
        wx    = float(target.get("px") or 0)
        wy    = float(target.get("py") or 0)
        name  = target.get("name", "?")
        ttype = target.get("type", "default")

        px, py = world_to_pixel(wx, wy, origin, resolution, img_height)
        color  = colors[i % len(colors)]

        ax.plot(
            px, py,
            "o",
            markersize=10,
            color=color,
            markeredgecolor="white",
            markeredgewidth=1.5,
        )
        ax.annotate(
            f"{name}\n({ttype})",
            xy=(px, py),
            xytext=(8, 8),
            textcoords="offset points",
            fontsize=8,
            color=color,
            bbox=dict(boxstyle="round,pad=0.2", fc="white", alpha=0.7),
        )

    if targets:
        legend_handles = [
            mpatches.Patch(color=colors[i % len(colors)], label=t.get("name", "?"))
            for i, t in enumerate(targets)
        ]
        ax.legend(handles=legend_handles, loc="upper right", fontsize=7, title="Targets")

    plt.tight_layout()
    plt.show()


if __name__ == "__main__":
    main()

Betik adım adım şunları yapar:

  1. Haritayı alır — GET /mapping/map isteğini gönderir, base64 PNG'yi çözer, resolution (metre/piksel) ve origin (haritanın sol alt köşesinin metre cinsinden konumu) değerlerini okur.
  2. Hedefleri alır — GET /targets isteğiyle tüm hedefleri metre cinsinden konumlarıyla (px, py) alır.
  3. Koordinatları dönüştürür — her hedefin metre cinsinden konumunu, haritanın origin ve resolution değerleriyle piksel koordinatına çevirir.
  4. Çizer — doluluk haritasını gri tonlu arka plan olarak gösterir; her hedefi adı ve türü yazan renkli bir nokta olarak çizer. Sağ üst köşede bir lejant yer alır.