3. 站点拓扑与地图表达

AGV 调度系统里,“地图”并不是一张给人看的平面图,而是一套可计算的图模型。调度器要回答的问题——从 A 到 B 怎么走、哪台车能进这个区域、哪个工位正在被占用、电量低时去哪台充电桩——全部依赖地图中站点、路径、区域、工位、充电桩这五类对象的定义方式与它们之间的连接关系。本节先建立统一的图模型,再落到可直接编码的数据结构。

一、五类核心对象与统一图模型

把 AGV 运行环境抽象为一张带属性的有向图 G = (V, E):站点是顶点 V,路径是边 E,区域是顶点的集合划分,工位与充电桩则是带有特殊语义的站点。这样做的价值在于:路径规划算法(A*、Dijkstra)只需处理 V 和 E,而业务约束(区域限行、工位占用、充电排队)通过顶点与边的属性附加进来,算法层与业务层解耦。

五类对象的职责边界需要先明确:

  • 站点(Node):AGV 可以停稳并执行动作的最小位置单元,具有唯一 ID 与二维坐标,是路径的端点。
  • 路径(Edge):两个站点之间的可行驶连接,有方向、长度、限速、通行方向约束,是图模型的边。
  • 区域(Zone):一组站点的逻辑集合,用于表达限行、互斥、优先级等管理规则,本身不是物理位置。
  • 工位(Station):一种特殊站点,绑定作业类型(上料、下料、对接),具有占用状态与朝向。
  • 充电桩(Charger):一种特殊站点,绑定充电功率、桩位状态与排队策略。

下图给出这五类对象在同一张图上的关系:站点构成骨架,路径连接站点,区域圈定站点子集,工位与充电桩作为带语义的站点挂在骨架上。

站点拓扑图模型:站点 / 路径 / 区域 / 工位 / 充电桩 区域 Z1(装配区·限速 0.8 m/s) 区域 Z2(仓储区·允许双向) N1 N2 N3 工位 S1 上料·朝向 90° N4 N5 N6 充电桩 C1 7 kW·单桩位 跨区路径 E7(单向·需授权) 图元与数据结构对应 站点 Node:{id, x, y, zoneId, type} 路径 Edge:{from, to, length, speedLimit, dir} 工位 Station:{nodeId, opType, heading, occupied} 充电桩 Charger:{nodeId, power, slots, queuePolicy} 区域 Zone:{zoneId, nodeIds[], rules[]} 规则示例: Z1 限速 0.8 m/s;Z2 允许双向; E7 跨区需调度授权;S1 同时仅容 1 车。

二、站点与路径的数据结构

站点是图的最小单元,建议字段如下(以 JSON 表达,实际可用结构体或数据库表):

{
  "id": "N3",
  "x": 12.40, "y": 8.75,        // 单位:米,地图原点在左下角
  "zoneId": "Z1",
  "type": "normal",              // normal | station | charger | buffer
  "heading": 90,                 // 停靠朝向,单位:度,逆时针为正
  "capacity": 1                  // 可同时停靠的 AGV 数
}

路径描述两个站点之间的可行驶关系,关键字段是方向与代价:

{
  "id": "E7",
  "from": "N3", "to": "N4",
  "length": 6.20,                // 米,用于代价计算
  "speedLimit": 0.8,             // m/s,取两端区域限速的较小值
  "dir": "single",               // single | both
  "needAuth": true               // 跨区路径需调度授权
}

这里有一个容易出错的约定:方向定义。本文统一约定 from → to 表示允许的行驶方向,dir="both" 时反向也允许。若地图编辑器里画的是无向线段,导入时必须显式展开为两条有向边,否则 A* 会出现“有路走不通”的问题。坐标系同样需要先声明:x 向右为正、y 向上为正、朝向以 x 轴正方向为 0°、逆时针为正,全文与图示保持一致。

三、区域、工位与充电桩的语义扩展

区域不是物理对象,而是站点集合加规则集合。它的典型用途有三类:限速与限行(如装配区限速 0.8 m/s)、互斥占用(同一区域同时只允许 N 台车)、优先级调度(充电区任务优先于普通搬运)。数据结构上只需保存站点 ID 列表与规则列表,规则由调度器在路径代价计算和准入判断时读取。

{
  "zoneId": "Z1",
  "name": "装配区",
  "nodeIds": ["N1", "N2", "N3"],
  "rules": [
    {"type": "speedLimit", "value": 0.8},
    {"type": "maxAgv", "value": 2}
  ]
}

工位与充电桩本质是 type 特殊的站点,但需要额外状态字段。工位的 occupied 由调度器在任务分配时置位,作业完成后释放;充电桩的 slots 表示桩位数,queuePolicy 可取 fifo 或 nearest。把状态放在站点对象上而不是单独维护一张表,好处是路径规划时可以直接把“被占用工位”作为不可达顶点过滤掉,无需额外查询。

工程建议:站点 ID 使用“区域前缀 + 序号”(如 Z1-N03),路径 ID 使用“起点-终点”(如 N3-N4)。这样在日志和排障时,仅凭 ID 就能判断对象属于哪个区域、连接了哪两个点,大幅降低定位成本。

四、从图模型到可执行地图

把上述对象组装成一张可被调度器直接加载的地图,通常包含三个文件或三张表:nodes、edges、zones,工位与充电桩作为 nodes 中 type 非 normal 的记录,附带各自的扩展字段。加载后调度器构建邻接表:

adj[N3] = [ {to: N4, cost: 6.20/0.8, edge: E7, needAuth: true},
            {to: N2, cost: 3.10/0.8, edge: E3, needAuth: false} ]

其中 cost = length / speedLimit,即行驶时间,作为 A* 的边权。若路径 needAuth=true,在扩展该边前先向调度授权模块查询当前是否放行;若目标站点是工位且 occupied=true,则该顶点不可达。这样,路径规划、区域规则、工位占用、充电桩排队四类逻辑就统一收敛到“顶点可达性 + 边权 + 边准入”三个判断点上,实现清晰且易于测试。

验证地图是否正确,可以用一组最小检查:任取两个站点做一次路径搜索,确认返回路径的每一段都存在于 edges 中且方向一致;检查每个工位与充电桩的 nodeId 都能在 nodes 中找到;检查每个 zone 的 nodeIds 非空且不包含不存在的站点。这三项通过后,地图即可进入调度联调阶段。