1. 无人机地图基础:坐标系与投影

做无人机地图开发,第一个绕不开的就是坐标系。我刚开始接触这个领域时,也被 WGS84、UTM、Web Mercator 这些术语搞得头晕。说白了,它们就是描述地球位置的「语言」——不同场景下得用不同的语言,否则你的无人机航迹就会飞到天上去。

1.1 三个核心坐标系

先记住一个结论:WGS84 是地理坐标系,UTM 是投影坐标系,Web Mercator 是显示坐标系。三者的关系,我画了张图帮你理清。

坐标系与投影核心关系 WGS84 地理坐标系(经纬度) UTM 投影坐标系(米) Web Mercator 显示坐标系(像素) 投影 伪投影(显示用) 应用场景 GPS 原始数据 → WGS84 | 距离计算/规划 → UTM | 地图显示 → Web Mercator GPS 定位 无人机位置原始输出 航迹规划 距离、面积、路径计算 地图渲染 瓦片加载、航迹叠加 核心原则:数据源用 WGS84,计算用 UTM,显示用 Web Mercator

WGS84 — 全球定位的基准

WGS84 是目前 GPS 系统使用的坐标系。无人机飞控输出的经纬度,就是 WGS84 坐标。它的单位是度(°),描述的是地球表面上的点。

  • 优点:全球统一,所有 GPS 设备都支持
  • 缺点:经纬度不是等距单位——1°经度在赤道和北极的长度不一样
  • 典型数据:39.9042°N, 116.4074°E(北京天安门)
我的经验:千万别直接用 WGS84 经纬度算距离。我见过有人用经纬度差值做路径规划,结果在北极附近飞出了「诡异」的航线。用 Haversine 公式或者转成 UTM 再算。

UTM — 局部精确计算

UTM 把地球分成 60 个投影带,每个带 6° 经度宽。它将球面投影到平面,单位是米。做无人机航迹规划时,我习惯用 UTM 算距离和面积。

特性 WGS84 UTM
单位 度(°) 米(m)
适用场景 GPS 定位、数据交换 距离计算、路径规划
精度 全球统一,但不等距 带内精度高(<1m)
中国常用带号 — 49N~53N(东经 108°~132°)
注意:UTM 在带边界附近误差较大。如果你的无人机航线跨越两个 UTM 带,记得做带间转换。我曾经在新疆的项目中踩过这个坑——航线跨了 3 个带,直接算出来的航点偏移了十几米。

Web Mercator — 地图显示的标准

Web Mercator(EPSG:3857)是 Google Maps、OSM、高德等在线地图使用的投影。它本质上是把 WGS84 经纬度「伪投影」到平面,单位是米,但只在显示层面使用。

  • 特点:保持角度不变(等角),但面积变形严重——格陵兰看起来和非洲一样大
  • 用途:地图瓦片渲染、航迹叠加显示
  • 转换公式:x = lon * 20037508.34 / 180,y = ln(tan(45° + lat/2)) * 20037508.34 / 180

1.2 地图瓦片原理

你想想看,一张全球地图有多大?如果直接加载,你的浏览器会直接崩溃。瓦片(Tile)就是解决这个问题的——把地图切成 256×256 像素的小方块,按层级组织,按需加载。

瓦片层级与编号

Web Mercator 的瓦片系统从第 0 级开始:

  • 第 0 级:1 张瓦片覆盖全球(256×256 像素)
  • 第 1 级:4 张瓦片(2×2)
  • 第 n 级:2n × 2n 张瓦片

每张瓦片有唯一的 z/x/y 编号。比如 14/12345/6789.png 表示第 14 级、x 方向第 12345 列、y 方向第 6789 行的瓦片。

瓦片加载的核心逻辑:

// 伪代码:根据经纬度和层级计算瓦片编号
function latLonToTile(lat, lon, zoom) {
    let x = Math.floor((lon + 180) / 360 * Math.pow(2, zoom));
    let y = Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom));
    return { z: zoom, x: x, y: y };
}

瓦片服务 URL 模板

不同的地图服务商,瓦片 URL 格式略有不同:

服务商 URL 模板
OpenStreetMap https://tile.openstreetmap.org/{z}/{x}/{y}.png
Google Maps https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}
高德地图 https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}
避坑指南:我曾经在项目中直接用了 Google 瓦片 URL,结果上线后被墙了。后来换成 OSM 和国内的高德瓦片做双源切换——国外用 OSM,国内用高德。记得做瓦片缓存,否则每次缩放都要重新请求,用户体验很差。

1.3 常见地图服务对比

做无人机地图开发,选对地图服务很重要。我整理了一张对比表:

特性 OpenStreetMap Google Maps 高德地图
数据源 开源社区 商业数据 国内测绘数据
国内精度 一般(偏远地区差) 较好 优秀(有偏移校正)
瓦片协议 完全开放 需 需
坐标系 WGS84 WGS84(显示用 Web Mercator) GCJ-02(火星坐标系)
适合场景 全球通用、原型开发 商业项目、国外飞行 国内飞行、高精度需求
重要提醒:国内地图服务(高德、百度)使用 GCJ-02 坐标系,与 WGS84 有几百米的偏移。如果你直接用 GPS 的 WGS84 坐标叠加到高德地图上,航迹会偏到隔壁小区。必须做坐标转换——我一般用 coordtransform 库处理。

1.4 实战:坐标系转换代码

下面是我在项目中常用的 WGS84 转 GCJ-02 代码片段:

// WGS84 转 GCJ-02(简化版)
function wgs84ToGcj02(lat, lon) {
    const a = 6378245.0; // 长半轴
    const ee = 0.00669342162296594323; // 偏心率平方
    
    let dLat = transformLat(lon - 105.0, lat - 35.0);
    let dLon = transformLon(lon - 105.0, lat - 35.0);
    let radLat = lat / 180.0 * Math.PI;
    let magic = Math.sin(radLat);
    magic = 1 - ee * magic * magic;
    let sqrtMagic = Math.sqrt(magic);
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
    dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
    
    return {
        lat: lat + dLat,
        lon: lon + dLon
    };
}
我的习惯:在无人机地面站开发中,我会在数据层统一用 WGS84 存储,只在显示层做转换。这样数据库里只有一套坐标,不会乱。航迹回放时,再根据当前地图服务动态转换。

嗯,这一章的内容就到这里。坐标系和瓦片原理是后续所有开发的基础——航迹规划、地图加载、实时显示,都离不开这些概念。你先把 WGS84、UTM、Web Mercator 的关系理清楚,后面写代码时就会顺手很多。