航点数据结构设计:从零搭建无人车的“记忆单元”

做无人车开发,说白了就是让车知道“我在哪”、“要去哪”、“怎么去”。而航点,就是这三个问题的核心载体。今天我们来聊聊航点的数据结构设计——这是整个任务系统的基石。

我记得刚入行那会儿,有个项目因为航点数据格式没统一,导致导航模块和动作执行模块各说各话,联调时出了不少问题。从那以后,我对数据建模这件事就格外上心。

航点类:无人车的“坐标+指令”组合

一个航点,至少得包含三样东西:位置、朝向、动作。位置告诉车去哪,朝向告诉车怎么停,动作告诉车到了之后干什么。

我个人习惯把航点类设计成这样:

from dataclasses import dataclass
from enum import Enum
from typing import Optional

class ActionType(Enum):
    """动作类型枚举"""
    STOP = "stop"           # 停车
    LOAD = "load"           # 装载
    UNLOAD = "unload"       # 卸载
    WAIT = "wait"           # 等待
    CHARGE = "charge"       # 充电
    CUSTOM = "custom"       # 自定义动作

@dataclass
class Waypoint:
    """航点数据类"""
    x: float                # 横坐标(米)
    y: float                # 纵坐标(米)
    yaw: float              # 朝向角(弧度)
    action: ActionType      # 到达后的动作
    speed: Optional[float] = None  # 通过该点的目标速度
    tolerance: float = 0.5  # 到达判定阈值(米)

你可能会问:为什么用 dataclass?嗯,这里有个小故事。我之前用普通类写航点,每次都要手写 __init__、__repr__、__eq__,代码又臭又长。dataclass 自动帮你生成这些,省心不少。

核心要点:航点不只是坐标,它是“位置+行为”的复合体。设计时要把动作类型作为一等公民,而不是事后补丁。

为什么用 dataclass?我的实战体会

Python 3.7 引入的 dataclass,简直就是为数据建模量身定做的。我总结了几点好处:

  • 代码量减少 60%:不用写模板代码,专注业务逻辑
  • 类型注解清晰:IDE 自动补全,减少低级错误
  • 不可变可选:设置 frozen=True 可以防止意外修改
  • 开箱即用的序列化:配合 asdict() 方法,转字典超方便

举个例子,我在项目中经常需要批量创建航点:

from dataclasses import asdict

# 创建一条路径
path = [
    Waypoint(0.0, 0.0, 0.0, ActionType.STOP),
    Waypoint(10.0, 0.0, 0.0, ActionType.WAIT, speed=2.0),
    Waypoint(10.0, 5.0, 1.57, ActionType.UNLOAD),
    Waypoint(0.0, 5.0, 3.14, ActionType.LOAD),
]

# 转成字典列表,方便存储
path_dicts = [asdict(wp) for wp in path]

小技巧:如果你需要频繁比较航点是否相等,可以在 dataclass 里加上 order=True 参数,这样就能直接排序了。

序列化与反序列化:让航点“活”起来

航点数据不能只活在内存里,得能存能读。JSON 和 YAML 是两种最常用的格式。我一般这么选:

格式 优点 缺点 我的使用场景
JSON 解析快、兼容性好 不支持注释 运行时配置、网络传输
YAML 可读性强、支持注释 解析稍慢 人工编辑的航点文件

JSON 序列化实战

import json
from dataclasses import asdict

def waypoints_to_json(waypoints: list[Waypoint]) -> str:
    """航点列表转JSON字符串"""
    data = []
    for wp in waypoints:
        wp_dict = asdict(wp)
        wp_dict['action'] = wp.action.value  # 枚举转字符串
        data.append(wp_dict)
    return json.dumps(data, indent=2, ensure_ascii=False)

def json_to_waypoints(json_str: str) -> list[Waypoint]:
    """JSON字符串转航点列表"""
    data = json.loads(json_str)
    waypoints = []
    for item in data:
        item['action'] = ActionType(item['action'])  # 字符串转枚举
        waypoints.append(Waypoint(**item))
    return waypoints

# 使用示例
json_str = waypoints_to_json(path)
print(json_str)
# 输出:
# [
#   {
#     "x": 0.0,
#     "y": 0.0,
#     "yaw": 0.0,
#     "action": "stop",
#     "speed": null,
#     "tolerance": 0.5
#   },
#   ...
# ]

注意:枚举类型在 JSON 里没有原生支持。我踩过这个坑——直接序列化枚举会报错。解决办法就是手动转成字符串,反序列化时再转回来。

YAML 序列化实战

YAML 的可读性确实好,适合人工编辑。我曾经用 YAML 管理过一套包含 200 多个航点的测试路线,编辑起来比 JSON 舒服多了。

import yaml

def waypoints_to_yaml(waypoints: list[Waypoint]) -> str:
    """航点列表转YAML字符串"""
    data = []
    for wp in waypoints:
        wp_dict = asdict(wp)
        wp_dict['action'] = wp.action.value
        data.append(wp_dict)
    return yaml.dump(data, default_flow_style=False, allow_unicode=True)

def yaml_to_waypoints(yaml_str: str) -> list[Waypoint]:
    """YAML字符串转航点列表"""
    data = yaml.safe_load(yaml_str)
    waypoints = []
    for item in data:
        item['action'] = ActionType(item['action'])
        waypoints.append(Waypoint(**item))
    return waypoints

# 使用示例
yaml_str = waypoints_to_yaml(path)
print(yaml_str)
# 输出:
# - action: stop
#   speed: null
#   tolerance: 0.5
#   x: 0.0
#   y: 0.0
#   yaw: 0.0
# - action: wait
#   speed: 2.0
#   tolerance: 0.5
#   x: 10.0
#   y: 0.0
#   yaw: 0.0

我的习惯:开发阶段用 YAML,方便调试和修改。部署到实车时转成 JSON,追求解析性能。

知识体系总览

下面这张图,把航点数据结构的核心脉络梳理清楚了:

航点数据结构设计知识体系 Waypoint 航点类 位置 (x, y) 朝向 (yaw) 动作 (ActionType) 数据建模:Python dataclass(自动生成 __init__、__repr__、__eq__) JSON 序列化(运行时、网络传输) YAML 序列化(人工编辑、调试) JSON → Waypoint 反序列化 YAML → Waypoint 反序列化 核心原则:位置 + 朝向 + 动作 = 完整航点

避坑指南:我踩过的三个坑

做航点数据结构设计,有几个地方特别容易出问题。我把自己踩过的坑分享给你:

  1. 浮点数精度问题:我曾经用 == 比较两个航点的坐标,结果因为浮点数精度,明明相同的点被判为不同。后来统一用 tolerance 阈值比较,再没出过问题。
  2. 枚举序列化遗漏:有一次部署时忘了处理枚举类型,结果 JSON 解析直接报错。现在我在序列化函数里都会加个类型检查。
  3. YAML 缩进错误:YAML 对缩进敏感,团队里有人用 tab 有人用空格,导致文件解析失败。后来统一用 2 空格缩进,并在 CI 里加了格式检查。

总结一下:航点数据结构看似简单,但设计得好不好,直接影响到后续的任务规划、路径执行、数据存储。用 dataclass 建模,配合 JSON/YAML 序列化,这套组合拳我用了好几年,稳得很。

好了,航点数据结构就聊到这。代码都在上面了,你可以直接拿去用。有什么问题,欢迎交流。


公众号:蓝海资料掘金营,微信deep3321