【Agorversev1.1数据转换】Agorverse高清地图转OpenStreetMap及SUMO路网
文章目录
- Agorverse高清地图转OpenStreetMap及SUMO路网
- 1. Agorverse osm转换说明
- 2. 转换源码
- 3. 处理效果
- 4. SUMO-Carla联合仿真
Agorverse高清地图转OpenStreetMap及SUMO路网
1. Agorverse osm转换说明
根据作者的描述,其高清地图的osm文件与标准osm的区别在于以下几点:
在 OpenStreetMap (OSM) 中,“Node”是指一个兴趣点,或者是某条线性特征(例如道路)的组成点。在 OSM 中,Node 可以包含标签(tags),例如:
- natural:如果是自然特征,标明类型(如山顶等)。
- man_made:如果是人工设施,标明类型(如水塔、信号塔等)。
- amenity:如果是服务设施(如酒吧、餐馆、回收中心等),标明类型。
在 OSM 中,“Way”通常是由一系列有序的“Nodes”组成的道路中心线。OSM 中的 Way 通常表示线性或多边形特征,例如道路、溪流、森林或湖泊。Way 至少包含两个或更多的 Node,并可能具有以下标签:
- highway:道路的类别(如高速公路、主干道、次干道等)。
- maxspeed:道路的最高限速(单位:km/h)。
- ref:道路的参考编号。
- oneway:是否为单行道(布尔值)。
然而,在 Argoverse 中,“Way”对应的是车道段的中心线。一个 Argoverse Way 包含以下 9 个属性:
- id:整数,唯一的车道 ID,作为此“Way”的标识符。
- has_traffic_control:布尔值,表示是否有交通管制。
- turn_direction:字符串,表示转向方向(“RIGHT”、“LEFT”或“NONE”)。
- is_intersection:布尔值,表示是否为交叉路口。
- l_neighbor_id:整数,左侧相邻车道的唯一 ID。
- r_neighbor_id:整数,右侧相邻车道的唯一 ID。
- predecessors:整数列表或 None,表示前序车道。
- successors:整数列表或 None,表示后续车道。
- centerline_node_ids:列表,包含中心线的 Node IDs。
在 Argoverse 中,一个 LaneSegment 对象由一个 Way 和两个或更多的 Node 组合而成。
2. 转换源码
转换源码如下:
from pyproj import Proj, transform
# 假设原始坐标是 UTM Zone 33N,投影信息需要根据具体数据集调整
proj_utm = Proj(proj="utm", zone=33, ellps="WGS84", south=False)
proj_wgs84 = Proj(proj="latlong", datum="WGS84")
# Load the Argoverse XML file
input_file = "/home/moresweet/Downloads/hd_maps/map_files/pruned_argoverse_PIT_10314_vector_map.xml"
output_file = "/home/moresweet/Downloads/hd_maps/map_files/converted_osm_file.osm"
import xml.etree.ElementTree as ET
import datetime
from concurrent.futures import ThreadPoolExecutor
import math
# 经纬度参考点 (假设参考点在赤道和本初子午线交汇处)
REFERENCE_LAT = 0.0
REFERENCE_LON = 0.0
# 将笛卡尔坐标转换为 WGS84 经纬度
def convert_coordinate(x, y):
earth_radius = 6378137 # 地球半径,单位为米
lon = REFERENCE_LON + (x / (earth_radius * math.cos(math.pi * REFERENCE_LAT / 180))) * (180 / math.pi)
lat = REFERENCE_LAT + (y / earth_radius) * (180 / math.pi)
return lon, lat
# 格式化 XML 节点为美观的缩进格式
def prettify_xml(elem, level=0):
indent = " "
i = "\n" + level * indent
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + indent
if not elem.tail or not elem.tail.strip():
elem.tail = i
for child in elem:
prettify_xml(child, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
return ET.tostring(elem, encoding="unicode")
# 计算边界 (bounds)
def calculate_bounds(nodes):
coords = [convert_coordinate(float(n.attrib["x"]), float(n.attrib["y"])) for n in nodes]
lons, lats = zip(*coords)
return min(lons), min(lats), max(lons), max(lats)
# 处理单个节点
def process_node(node, id_offset):
x, y = float(node.attrib["x"]), float(node.attrib["y"])
lon, lat = convert_coordinate(x, y)
node_id = str(int(node.attrib["id"]) + id_offset)
return ET.Element(
"node",
id=node_id, lat=str(lat), lon=str(lon),
visible="true", version="1", changeset="1",
timestamp=datetime.datetime.now().isoformat(),
user="converter", uid="1"
)
# 处理单个路径
def process_way(way, id_offset):
osm_way = ET.Element(
"way",
{
"id": way.attrib["lane_id"],
"visible": "true",
"version": "1",
"changeset": "1",
"timestamp": datetime.datetime.now().isoformat(),
"user": "converter",
"uid": "1"
}
)
for nd in way.findall("nd"):
ref_id = str(int(nd.attrib["ref"]) + id_offset)
ET.SubElement(osm_way, "nd", {"ref": ref_id})
for tag in way.findall("tag"):
key, value = tag.attrib["k"], tag.attrib["v"]
if key == "turn_direction":
key = "turn"
elif key == "has_traffic_control":
key = "traffic_control"
elif key == "is_intersection":
key = "junction" if value.lower() == "true" else "no_junction"
if key not in {"highway", "building"}:
key = "highway"
value = "service"
ET.SubElement(osm_way, "tag", {"k": key, "v": value})
return osm_way
# 主函数
def convert_argoverse_to_osm_with_id_fix(input_file, output_file, id_offset=1):
# 解析输入文件
tree = ET.parse(input_file)
root = tree.getroot()
# 创建 OSM 根节点
osm_root = ET.Element(
"osm",
version="0.6",
generator="argoverse_to_osm_converter",
copyright="OpenStreetMap and contributors",
attribution="http://www.openstreetmap.org/copyright",
license="http://opendatacommons.org/licenses/odbl/1-0/"
)
# 转换节点
nodes = root.findall("node")
with ThreadPoolExecutor() as executor:
converted_nodes = list(executor.map(lambda n: process_node(n, id_offset), nodes))
# 添加 <bounds>
min_lon, min_lat, max_lon, max_lat = calculate_bounds(nodes)
ET.SubElement(osm_root, "bounds", {
"minlat": str(min_lat),
"minlon": str(min_lon),
"maxlat": str(max_lat),
"maxlon": str(max_lon)
})
# 添加 <node> 元素
osm_root.extend(converted_nodes)
# 转换路径 (Way)
ways = root.findall("way")
with ThreadPoolExecutor() as executor:
converted_ways = list(executor.map(lambda w: process_way(w, id_offset), ways))
osm_root.extend(converted_ways)
# 保存格式化的 XML
with open(output_file, "w", encoding="utf-8") as f:
f.write(prettify_xml(osm_root))
print(f"Converted OSM file saved to {output_file}")
# 示例调用
convert_argoverse_to_osm_with_id_fix(
input_file, output_file, id_offset=1
)
转换为SUMO所用的文档格式:
netconvert --osm-files converted_osm_file.osm -o argoverse_sumo.net.xml
python /usr/share/sumo/tools/randomTrips.py -n argoverse_sumo.net.xml -r argoverse_sumo.rou.xml --allow-fringe
polyconvert --net-file argoverse_sumo.net.xml --osm-files converted_osm_file.osm -o argoverse_sumo.poly.xml
sumo-gui argoverse_sumo.sumocfg
其中convert.sumocfg的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<input>
<net-file value="bbb.net.xml"/>
<route-files value="bbb.rou.xml"/>
<additional-files value="bbb.poly.xml"/>
</input>
<time>
<begin value="0"/>
<end value="120"/>
</time>
</configuration>
3. 处理效果
最终效果:转换的高精地图与原始版本一致。QGIS、JOSM、SUMO、Agorverse-api得到的结果均一致。
4. SUMO-Carla联合仿真
cd ~/carla/Co-Simulation/Sumo
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
moresweet@moresweet-System-Product-Name:~/carla$ ./CarlaUE4.sh
moresweet@moresweet-System-Product-Name:~/carla/PythonAPI/util$ python config.py --map Town01
load map 'Town01'.
moresweet@moresweet-System-Product-Name:~/carla/Co-Simulation/Sumo$ python run_synchronization.py ./examples/Town01.sumocfg --sumo-gui
INFO: Starting new sumo server...
INFO: Remember to press the play button to start the simulation
Retrying in 1 seconds
问题:
python run_synchronization.py ./examples/Town01.sumocfg --sumo-gui
INFO: Starting new sumo server...
INFO: Remember to press the play button to start the simulation
Retrying in 1 seconds
Traceback (most recent call last):
File "run_synchronization.py", line 319, in <module>
synchronization_loop(arguments)
File "run_synchronization.py", line 237, in synchronization_loop
args.sumo_port, args.sumo_gui, args.client_order)
File "/home/moresweet/carla/Co-Simulation/Sumo/sumo_integration/sumo_simulation.py", line 336, in __init__
self.net = _get_sumo_net(cfg_file)
File "/home/moresweet/carla/Co-Simulation/Sumo/sumo_integration/sumo_simulation.py", line 304, in _get_sumo_net
sumo_net = traci.sumolib.net.readNet(net_file)
AttributeError: module 'traci' has no attribute 'sumolib'