3、地图与路网:加载不同地图、理解OpenDRIVE路网结构、在编辑器中可视化路网

好,咱们进入第三章。这一章我打算聊聊地图和路网。说实话,这是整个场景编辑的基石。你想想看,没有地图,车往哪儿开?没有路网,车怎么知道该走哪条道?

我在做第一个完整的仿真项目时,就吃过地图的亏。当时随便加载了一个地图就开始写逻辑,结果车一直往绿化带上撞。后来才发现,地图的路网根本没加载对。嗯,从那以后,我每次都会先花十分钟把路网可视化出来看一眼。

3.1 加载不同地图

Carla 自带了几张官方地图,比如 Town01 到 Town10。每张地图的侧重点都不一样。我个人习惯把地图分成三类:

地图 特点 适合场景
Town01 / Town02 基础网格布局,路网简单 新手练习、基础功能测试
Town03 / Town04 有环岛、高架、多车道 复杂交通流、变道测试
Town05 / Town06 高速公路、匝道、长直路 高速场景、紧急避让
Town07 / Town10 乡村、工业区、不规则路口 非结构化道路测试

加载地图的代码其实很简单。但我要提醒你一点——切换地图时,一定要先销毁之前的客户端连接。我曾经犯过这个错,连续加载三次地图后,内存直接爆了。

import carla

# 连接到服务器
client = carla.Client('localhost', 2000)
client.set_timeout(10.0)

# 加载地图
world = client.load_world('Town03')

# 检查是否加载成功
print(world.get_map().name)

你看,就这么几行。但实际项目中,我一般会加一个重试机制。因为服务器偶尔会卡住,尤其是地图比较大的时候。

小技巧: 如果你在加载地图时遇到 "timeout" 错误,可以先把超时时间设长一点,比如 30 秒。Town06 这种大地图加载确实慢。

3.2 理解 OpenDRIVE 路网结构

好,地图加载完了。但地图到底是什么?说白了,Carla 里的地图就是一份 OpenDRIVE 文件。这是一种 XML 格式的路网描述标准。

我第一次打开 OpenDRIVE 文件时,整个人是懵的。满屏的 XML 标签,什么 <road><lane><geometry>,完全不知道谁是谁。后来我总结了一个规律——你只需要关注三个核心概念:

  • 道路(Road):路网的基本单元,每条路都有一个唯一的 ID
  • 车道(Lane):道路上的每一条车道,有左车道、右车道之分
  • 连接(Junction / Connection):道路之间的连接关系,比如路口、匝道

举个例子。你有一条路,ID 是 10。这条路有 3 条左车道和 3 条右车道。在 OpenDRIVE 里,它会这样描述:

<road name="Main Street" length="200.0" id="10" junction="-1">
  <lanes>
    <laneSection s="0.0">
      <left>
        <lane id="-1" type="driving">
          <width a="3.5" b="0.0" c="0.0" d="0.0"/>
        </lane>
        <lane id="-2" type="driving">
          <width a="3.5" b="0.0" c="0.0" d="0.0"/>
        </lane>
      </left>
      <right>
        <lane id="1" type="driving">
          <width a="3.5" b="0.0" c="0.0" d="0.0"/>
        </lane>
      </right>
    </laneSection>
  </lanes>
</road>

注意看,左车道的 ID 是负数,右车道是正数。这是 OpenDRIVE 的一个约定。我当时不知道这个,写代码时一直拿正数去查左车道,结果查出来全是空的。

注意: 车道 ID 的符号很重要。左车道是负数,右车道是正数。中心线是 0。这个搞错了,你的路径规划会全乱套。

3.3 在编辑器中可视化路网

光看 XML 文件太抽象了。我建议你直接在 Carla 的编辑器中把路网可视化出来。这样一眼就能看出问题。

Carla 提供了一个调试工具,叫 carla.DebugHelper。你可以用它画出路网的所有车道线。代码大概是这样:

# 获取地图和调试对象
carla_map = world.get_map()
debug = world.debug

# 获取所有路点
waypoints = carla_map.generate_waypoints(2.0)

# 画出每个路点的位置
for wp in waypoints:
    debug.draw_point(
        wp.transform.location,
        size=0.1,
        color=carla.Color(0, 255, 0),
        life_time=0.0
    )

这段代码跑起来后,你会看到地图上出现密密麻麻的绿色小点。每个点代表一个路点,间隔 2 米。我个人习惯把间隔设成 1 米,这样路网看起来更连续。

但光画点还不够。我一般还会画出车道边界线。这样能直观地看到每条车道的走向:

# 画出车道边界
for wp in waypoints:
    # 获取左边界和右边界
    left_boundary = wp.get_left_lane()
    right_boundary = wp.get_right_lane()
    
    if left_boundary:
        debug.draw_line(
            wp.transform.location,
            left_boundary.transform.location,
            thickness=0.1,
            color=carla.Color(255, 0, 0),
            life_time=0.0
        )

红色线就是左边界。跑起来后,你会看到整个路网像一张红色的网铺在地图上。嗯,这时候你就能一眼看出哪些地方的路网有问题了。

核心要点: 可视化路网不是为了好看,而是为了验证。我每次加载一个新地图,第一件事就是跑一遍可视化代码。如果路网出现断点或者错位,那一定是 OpenDRIVE 文件有问题,或者地图加载不完整。

3.4 实战:加载自定义地图

官方地图用多了,你肯定会想用自己的地图。Carla 支持加载自定义的 OpenDRIVE 文件。方法很简单:

# 加载自定义 OpenDRIVE 文件
with open('my_map.xodr', 'r') as f:
    opendrive_content = f.read()

# 使用自定义路网
world = client.generate_opendrive_world(
    opendrive_content,
    carla.OpendriveGenerationParameters(
        vertex_distance=2.0,
        max_road_length=500.0,
        wall_height=0.0,
        additional_width=0.0,
        smooth_junctions=True,
        enable_mesh_visibility=True
    )
)

这里有几个参数我要特别说一下:

  • vertex_distance:路网生成的精度。越小越精细,但生成时间也越长。我一般设 2.0
  • max_road_length:单条道路的最大长度。超过这个值会被截断
  • smooth_junctions:是否平滑路口。建议打开,不然路口会很难看

我曾经用这个功能导入过一个从网上下载的 OpenDRIVE 文件。结果生成出来的路网全是断的。后来发现是文件里的坐标单位不对——它用的是厘米,Carla 默认用米。所以,导入前一定要检查单位

避坑指南: 我曾经导入一个 100MB 的 OpenDRIVE 文件,结果 Carla 直接卡死了。后来发现文件里包含了太多不必要的细节。建议导入前先用工具简化一下路网,去掉那些弯弯曲曲的小路。

好了,这一章的内容就这些。地图加载、路网结构、可视化,这三步走完,你就能对场景有一个全局的把握。下一章我们会聊天气和光照,那个更有意思。