3. 高精地图坐标系:WGS84坐标系、UTM投影、局部坐标系、坐标转换实战
坐标系这东西,说白了就是给地图上的每个点一个唯一的「身份证号」。做高精地图这几年,我见过太多因为坐标系搞错而翻车的案例。有一次,我们团队把WGS84和UTM的转换参数写反了,结果车道线偏移了整整两米——嗯,那天的路测数据直接报废。
今天咱们就把坐标系这件事彻底讲明白。你想想看,一辆自动驾驶车在路上跑,它需要知道自己在哪,周围的车道线在哪,红绿灯在哪。这些位置信息,背后全靠坐标系来支撑。
3.1 WGS84坐标系:全球通用的「地球坐标」
WGS84,全称World Geodetic System 1984。它是目前全球最通用的地理坐标系。GPS卫星发回来的原始数据,就是WGS84坐标。
WGS84用三个值来描述一个点:经度、纬度、海拔高度。经度范围-180°到180°,纬度范围-90°到90°。海拔高度是相对于WGS84椭球面的高度,不是海平面高度,这点要注意。
核心概念:WGS84是一个地心坐标系,原点在地球质心。Z轴指向北极,X轴指向本初子午线与赤道的交点。
我个人习惯把WGS84理解成「地球的经纬网」。你在手机地图上看到的坐标,绝大多数都是WGS84格式。比如天安门的坐标是(39.9042°N, 116.3974°E)。
但WGS84有个问题——它是个球面坐标。球面上的距离计算非常复杂,而且不适合做平面几何运算。你想想看,如果我要计算两条车道线是否平行,用经纬度算起来多麻烦?
避坑指南:我曾经在项目里直接用WGS84坐标计算距离,结果误差大得离谱。记住,WGS84只适合做定位和导航,不适合做局部几何计算。
3.2 UTM投影:把球面「拍扁」成平面
既然球面坐标不好用,那就把它投影到平面上。UTM(Universal Transverse Mercator)投影就是干这个的。
UTM把地球分成60个带,每个带宽度6度经度。中国主要覆盖UTM 43带到53带。每个带都有自己的中央经线,投影后中央经线是直线,变形最小。
| UTM带号 | 经度范围 | 覆盖区域 |
|---|---|---|
| 43 | 72°E - 78°E | 新疆西部 |
| 50 | 114°E - 120°E | 北京、天津 |
| 51 | 120°E - 126°E | 上海、江苏 |
UTM坐标用东向(Easting)和北向(Northing)表示,单位是米。东向是从中央经线往东的距离,北向是从赤道往北的距离。这样,任意两点之间的距离就可以直接用勾股定理算了。
我记得有一次做车道级导航,需要判断车辆是否在车道内。用UTM坐标后,计算变得非常简单——直接比较车辆位置和车道边界线的UTM坐标差值就行。
实用技巧:做高精地图时,我建议把WGS84作为存储格式,UTM作为计算格式。存储用经纬度,计算用米,各取所长。
3.3 局部坐标系:车辆周围的「小世界」
UTM虽然好用,但有个缺点——它覆盖范围太大,精度不够高。尤其是在城市峡谷、隧道等场景下,UTM的误差会累积。
这时候就需要局部坐标系了。局部坐标系,说白了就是以车辆为中心建立的一个小坐标系。通常用ENU(东-北-天)坐标系,原点在车辆质心,X轴指向东,Y轴指向北,Z轴指向天。
局部坐标系的好处是:
- 计算简单,全是直线距离
- 精度高,不受投影变形影响
- 适合做相对定位,比如「前方50米右转」
我做过一个项目,在隧道里用UTM定位,误差飘到10米以上。后来改用局部坐标系,以隧道入口为原点,用里程计和IMU推算位置,精度直接降到0.5米以内。
关键点:局部坐标系的生命周期很短。车辆一动,坐标系原点就变了。所以局部坐标系只适合做短距离、高精度的相对定位。
3.4 坐标转换实战:从WGS84到UTM再到局部
理论讲完了,咱们来点实战。下面这段代码展示了如何把WGS84坐标转成UTM,再转成局部坐标系。
import pyproj
import numpy as np
# 定义WGS84和UTM的投影
wgs84 = pyproj.CRS("EPSG:4326")
utm_zone = pyproj.CRS("EPSG:32650") # 北京地区用50带
# 创建转换器
transformer_wgs84_to_utm = pyproj.Transformer.from_crs(wgs84, utm_zone, always_xy=True)
# WGS84坐标(经度, 纬度)
lon, lat = 116.3974, 39.9042 # 天安门
# 转成UTM
easting, northing = transformer_wgs84_to_utm.transform(lon, lat)
print(f"UTM坐标: 东向={easting:.2f}m, 北向={northing:.2f}m")
# 定义局部坐标系原点(车辆当前位置)
origin_lon, origin_lat = 116.3974, 39.9042
origin_easting, origin_northing = transformer_wgs84_to_utm.transform(origin_lon, origin_lat)
# 计算局部坐标(ENU)
local_x = easting - origin_easting
local_y = northing - origin_northing
print(f"局部坐标: X={local_x:.2f}m, Y={local_y:.2f}m")
这段代码看起来简单,但实际项目中坑很多。我曾经犯过一个错误——忘记考虑UTM带号。北京用50带,上海用51带,如果搞混了,坐标会偏几万米。
重要提醒:做坐标转换时,一定要确认UTM带号是否正确。可以用在线工具验证一下,比如把转换后的坐标再转回WGS84,看看是否一致。
3.5 坐标系选择策略:什么时候用哪个?
根据我这几年的经验,总结了一套坐标系选择策略:
- 数据存储:用WGS84。通用性强,所有地图工具都支持。
- 全局路径规划:用UTM。计算距离方便,适合做几十公里级别的规划。
- 局部路径规划:用局部坐标系。精度高,适合做车道级导航。
- 传感器融合:用局部坐标系。激光雷达、摄像头、IMU的数据都在车辆周围,用局部坐标系最方便。
你想想看,如果一辆车要从北京开到上海,全局规划用UTM,到了上海后,局部规划用局部坐标系。这样既保证了全局的准确性,又保证了局部的精度。
我的习惯:在代码里统一用WGS84作为中间格式。所有传感器数据先转成WGS84,需要计算时再转成UTM或局部坐标系。这样数据流清晰,不容易出错。
好了,坐标系这部分就讲到这里。下一章咱们聊聊高精地图的数据格式——从NDS到OpenDRIVE,再到我们自己的自定义格式。到时候我会分享一些数据格式选型的实战经验。