第三章 航点任务文件解析
做无人车开发,最基础也最绕不开的一步,就是跟航点文件打交道。说白了,航点文件就是无人车的「导航剧本」——车要去哪、到了做什么、动作顺序怎么排,全写在这里面。
我个人习惯把航点文件比作「给车的便签条」。你想想看,如果给朋友写个便签:「先去A点,停3秒,再去B点,拍照」,朋友一看就懂。但给无人车写,就得用机器能读懂的语言——CSV或者JSON。
3.1 解析CSV/JSON格式的航点文件
先说说这两种格式。CSV就像Excel表格的简化版,一行一个航点,逗号分隔。JSON则是键值对结构,层次更清晰。
CSV格式示例:
waypoint_id,latitude,longitude,altitude,action,speed
WP001,39.9042,116.4074,50.0,hold,0.5
WP002,39.9142,116.4174,55.0,take_photo,0.3
WP003,39.9242,116.4274,60.0,land,0.2
JSON格式示例:
{
"mission_name": "巡检任务_20240115",
"waypoints": [
{
"id": "WP001",
"lat": 39.9042,
"lon": 116.4074,
"alt": 50.0,
"action": "hold",
"params": {"duration": 3.0}
},
{
"id": "WP002",
"lat": 39.9142,
"lon": 116.4174,
"alt": 55.0,
"action": "take_photo",
"params": {"count": 2}
}
]
}
我的经验:实际项目中,我更喜欢JSON。为什么?因为CSV一旦字段顺序变了,解析就崩。JSON有明确的键名,容错性好得多。不过CSV也有优势——用Excel打开就能编辑,现场调试时很方便。
3.2 使用pandas读取航点数据
Python里处理CSV/JSON,pandas是首选。代码量少,功能强。我刚开始做无人车时,还傻傻地用csv模块一行行读,后来发现pandas一行搞定。
读取CSV:
import pandas as pd
def load_waypoints_csv(file_path):
"""
读取CSV格式的航点文件
"""
try:
df = pd.read_csv(file_path)
print(f"成功加载 {len(df)} 个航点")
return df
except FileNotFoundError:
print(f"错误:文件 {file_path} 不存在")
return None
except pd.errors.EmptyDataError:
print("错误:文件为空")
return None
读取JSON:
def load_waypoints_json(file_path):
"""
读取JSON格式的航点文件
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# 提取航点列表
waypoints = data.get('waypoints', [])
df = pd.DataFrame(waypoints)
print(f"成功加载 {len(df)} 个航点")
return df
except json.JSONDecodeError as e:
print(f"JSON解析错误:{e}")
return None
except FileNotFoundError:
print(f"错误:文件 {file_path} 不存在")
return None
注意:pandas读取JSON时,如果JSON结构嵌套较深,需要用json_normalize()展开。我踩过这个坑——有一次读取嵌套的航点参数,直接报错,后来才发现需要展平。
3.3 异常处理与数据校验
嗯,这里要重点说说。文件解析最怕什么?怕数据不干净。我在项目里遇到过:经纬度写反了、高度字段是字符串、航点ID重复……各种奇葩问题。
数据校验清单:
- 必填字段检查:经纬度、航点ID不能为空
- 数值范围校验:纬度[-90, 90],经度[-180, 180]
- 类型检查:高度必须是数字,不能是"50米"这种字符串
- 唯一性检查:航点ID不能重复
- 动作参数校验:hold动作必须有duration参数
完整的校验函数:
def validate_waypoints(df):
"""
航点数据校验
返回:(是否通过, 错误列表)
"""
errors = []
# 1. 检查必填字段
required_cols = ['latitude', 'longitude', 'waypoint_id']
for col in required_cols:
if col not in df.columns:
errors.append(f"缺少必填字段:{col}")
return False, errors
# 2. 检查空值
if df[required_cols].isnull().any().any():
errors.append("存在空值的航点")
# 3. 经纬度范围校验
lat_out = df[(df['latitude'] < -90) | (df['latitude'] > 90)]
if len(lat_out) > 0:
errors.append(f"纬度越界:{lat_out['waypoint_id'].tolist()}")
lon_out = df[(df['longitude'] < -180) | (df['longitude'] > 180)]
if len(lon_out) > 0:
errors.append(f"经度越界:{lon_out['waypoint_id'].tolist()}")
# 4. 航点ID唯一性
if df['waypoint_id'].duplicated().any():
dup_ids = df[df['waypoint_id'].duplicated()]['waypoint_id'].tolist()
errors.append(f"航点ID重复:{dup_ids}")
# 5. 高度字段类型
if 'altitude' in df.columns:
if not pd.api.types.is_numeric_dtype(df['altitude']):
errors.append("高度字段不是数值类型")
return len(errors) == 0, errors
我曾经踩过的坑:有一次现场测试,车到了航点就是不执行动作。排查了半天,发现是CSV里动作字段多了个空格——"hold "而不是"hold"。从那以后,我所有解析代码都加了strip()去空格。
完整的解析流程:
def parse_mission_file(file_path):
"""
统一的航点文件解析入口
支持CSV和JSON格式
"""
# 判断文件格式
if file_path.endswith('.csv'):
df = load_waypoints_csv(file_path)
elif file_path.endswith('.json'):
df = load_waypoints_json(file_path)
else:
print("不支持的文件格式,仅支持CSV/JSON")
return None
if df is None:
return None
# 数据清洗
# 去掉字段前后的空格
df.columns = df.columns.str.strip()
# 校验
is_valid, errors = validate_waypoints(df)
if not is_valid:
print("数据校验失败:")
for err in errors:
print(f" - {err}")
return None
print("航点文件解析成功,数据校验通过")
return df
小技巧:我习惯在解析完成后,把航点数据可视化一下——用matplotlib画个散点图,看看航点分布是否合理。有一次发现航点连起来是个「8」字形,明显是经纬度写反了。肉眼一看就发现问题,比查数据快多了。
最后说一句,文件解析看似简单,但它是整个无人车任务的「入口」。入口不牢,后面全白搭。我见过太多因为航点文件格式问题导致任务失败的案例了。所以,花时间把解析和校验做好,绝对值得。