HarmonyOS 鸿蒙Next中坐标系转换:WGS84、GCJ-02、BD-09 相互转换
HarmonyOS 鸿蒙Next中坐标系转换:WGS84、GCJ-02、BD-09 相互转换 在开发地图应用时,经常遇到坐标偏移问题:
- GPS 定位返回的坐标(WGS84)在高德/腾讯地图上显示偏移 100-700 米
- 从百度地图获取的坐标在高德地图上显示位置不对
- 不同坐标系的数据如何统一处理?
原因分析
中国使用三种主要坐标系:
| 坐标系 | 别名 | 使用场景 | 特点 |
|---|---|---|---|
| WGS84 | GPS坐标 | GPS设备、国际地图 | 国际标准,无偏移 |
| GCJ-02 | 火星坐标系 | 高德、腾讯、谷歌中国 | 国测局加密,法定坐标系 |
| BD-09 | 百度坐标系 | 百度地图 | 在 GCJ-02 基础上二次加密 |
为什么会偏移?
出于国家安全考虑,中国要求所有地图服务必须使用 GCJ-02 坐标系。GPS 原始坐标(WGS84)经过非线性加密算法转换后才能在国内地图上正确显示。
GPS设备 → WGS84 → 加密 → GCJ-02 → 高德/腾讯地图
↓
二次加密
↓
BD-09 → 百度地图
解决方案
1. 创建坐标转换工具类
// common/utils/CoordinateConverter.ets
/**
* 坐标点接口
*/
export interface Coordinate {
latitude: number
longitude: number
}
/**
* 坐标转换工具类
* 支持 WGS84、GCJ-02、BD-09 三种坐标系相互转换
*/
export class CoordinateConverter {
// 椭球参数(克拉索夫斯基椭球)
private static readonly PI: number = 3.1415926535897932384626
private static readonly A: number = 6378245.0 // 长半轴
private static readonly EE: number = 0.00669342162296594323 // 偏心率平方
}
2. 判断是否在中国境内
只有中国境内的坐标才需要转换,境外坐标直接返回原值:
/**
* 判断坐标是否在中国境外
* 中国经纬度范围:经度 72°~137°,纬度 0°~55°
*/
private static outOfChina(lat: number, lng: number): boolean {
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271
}
3. 核心转换算法
/**
* 转换纬度(加密算法)
*/
private static transformLat(lng: number, lat: number): number {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat +
0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng))
ret += (20.0 * Math.sin(6.0 * lng * this.PI) +
20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0
ret += (20.0 * Math.sin(lat * this.PI) +
40.0 * Math.sin(lat / 3.0 * this.PI)) * 2.0 / 3.0
ret += (160.0 * Math.sin(lat / 12.0 * this.PI) +
320 * Math.sin(lat * this.PI / 30.0)) * 2.0 / 3.0
return ret
}
/**
* 转换经度(加密算法)
*/
private static transformLng(lng: number, lat: number): number {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng +
0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng))
ret += (20.0 * Math.sin(6.0 * lng * this.PI) +
20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0
ret += (20.0 * Math.sin(lng * this.PI) +
40.0 * Math.sin(lng / 3.0 * this.PI)) * 2.0 / 3.0
ret += (150.0 * Math.sin(lng / 12.0 * this.PI) +
300.0 * Math.sin(lng / 30.0 * this.PI)) * 2.0 / 3.0
return ret
}
4. WGS84 ↔ GCJ-02 转换
/**
* WGS84 转 GCJ-02(GPS坐标 → 火星坐标)
* 场景:GPS定位结果用于高德/腾讯地图显示
*
* @param wgsLat WGS84 纬度
* @param wgsLng WGS84 经度
* @returns GCJ-02 坐标
*/
static wgs84ToGcj02(wgsLat: number, wgsLng: number): Coordinate {
// 境外坐标不转换
if (this.outOfChina(wgsLat, wgsLng)) {
return { latitude: wgsLat, longitude: wgsLng }
}
let dLat = this.transformLat(wgsLng - 105.0, wgsLat - 35.0)
let dLng = this.transformLng(wgsLng - 105.0, wgsLat - 35.0)
const radLat = wgsLat / 180.0 * this.PI
let magic = Math.sin(radLat)
magic = 1 - this.EE * magic * magic
const sqrtMagic = Math.sqrt(magic)
dLat = (dLat * 180.0) / ((this.A * (1 - this.EE)) / (magic * sqrtMagic) * this.PI)
dLng = (dLng * 180.0) / (this.A / sqrtMagic * Math.cos(radLat) * this.PI)
return {
latitude: wgsLat + dLat,
longitude: wgsLng + dLng
}
}
/**
* GCJ-02 转 WGS84(火星坐标 → GPS坐标)
* 场景:从高德地图获取的坐标转换为标准GPS坐标
*
* @param gcjLat GCJ-02 纬度
* @param gcjLng GCJ-02 经度
* @returns WGS84 坐标
*/
static gcj02ToWgs84(gcjLat: number, gcjLng: number): Coordinate {
if (this.outOfChina(gcjLat, gcjLng)) {
return { latitude: gcjLat, longitude: gcjLng }
}
let dLat = this.transformLat(gcjLng - 105.0, gcjLat - 35.0)
let dLng = this.transformLng(gcjLng - 105.0, gcjLat - 35.0)
const radLat = gcjLat / 180.0 * this.PI
let magic = Math.sin(radLat)
magic = 1 - this.EE * magic * magic
const sqrtMagic = Math.sqrt(magic)
dLat = (dLat * 180.0) / ((this.A * (1 - this.EE)) / (magic * sqrtMagic) * this.PI)
dLng = (dLng * 180.0) / (this.A / sqrtMagic * Math.cos(radLat) * this.PI)
return {
latitude: gcjLat - dLat,
longitude: gcjLng - dLng
}
}
5. GCJ-02 ↔ BD-09 转换
/**
* GCJ-02 转 BD-09(火星坐标 → 百度坐标)
* 场景:高德地图坐标用于百度地图显示
*/
static gcj02ToBd09(gcjLat: number, gcjLng: number): Coordinate {
const z = Math.sqrt(gcjLng * gcjLng + gcjLat * gcjLat) +
0.00002 * Math.sin(gcjLat * this.PI * 3000.0 / 180.0)
const theta = Math.atan2(gcjLat, gcjLng) +
0.000003 * Math.cos(gcjLng * this.PI * 3000.0 / 180.0)
return {
latitude: z * Math.sin(theta) + 0.006,
longitude: z * Math.cos(theta) + 0.0065
}
}
/**
* BD-09 转 GCJ-02(百度坐标 → 火星坐标)
* 场景:百度地图坐标用于高德地图显示
*/
static bd09ToGcj02(bdLat: number, bdLng: number): Coordinate {
const x = bdLng - 0.0065
const y = bdLat - 0.006
const z = Math.sqrt(x * x + y * y) -
0.00002 * Math.sin(y * this.PI * 3000.0 / 180.0)
const theta = Math.atan2(y, x) -
0.000003 * Math.cos(x * this.PI * 3000.0 / 180.0)
return {
latitude: z * Math.sin(theta),
longitude: z * Math.cos(theta)
}
}
6. WGS84 ↔ BD-09 转换(组合转换)
/**
* WGS84 转 BD-09(GPS坐标 → 百度坐标)
* 场景:GPS定位结果用于百度地图显示
*/
static wgs84ToBd09(wgsLat: number, wgsLng: number): Coordinate {
// 先转 GCJ-02,再转 BD-09
const gcj02 = this.wgs84ToGcj02(wgsLat, wgsLng)
return this.gcj02ToBd09(gcj02.latitude, gcj02.longitude)
}
/**
* BD-09 转 WGS84(百度坐标 → GPS坐标)
* 场景:百度地图坐标转换为标准GPS坐标
*/
static bd09ToWgs84(bdLat: number, bdLng: number): Coordinate {
// 先转 GCJ-02,再转 WGS84
const gcj02 = this.bd09ToGcj02(bdLat, bdLng)
return this.gcj02ToWgs84(gcj02.latitude, gcj02.longitude)
}
7. 完整工具类代码
// common/utils/CoordinateConverter.ets
export interface Coordinate {
latitude: number
longitude: number
}
export class CoordinateConverter {
private static readonly PI: number = 3.1415926535897932384626
private static readonly A: number = 6378245.0
private static readonly EE: number = 0.00669342162296594323
private static outOfChina(lat: number, lng: number): boolean {
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271
}
private static transformLat(lng: number, lat: number): number {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat +
0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng))
ret += (20.0 * Math.sin(6.0 * lng * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0
ret += (20.0 * Math.sin(lat * this.PI) + 40.0 * Math.sin(lat / 3.0 * this.PI)) * 2.0 / 3.0
ret += (160.0 * Math.sin(lat / 12.0 * this.PI) + 320 * Math.sin(lat * this.PI / 30.0)) * 2.0 / 3.0
return ret
}
private static transformLng(lng: number, lat: number): number {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng +
0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng))
ret += (20.0 * Math.sin(6.0 * lng * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0
ret += (20.0 * Math.sin(lng * this.PI) + 40.0 * Math.sin(lng / 3.0 * this.PI)) * 2.0 / 3.0
ret += (150.0 * Math.sin(lng / 12.0 * this.PI) + 300.0 * Math.sin(lng / 30.0 * this.PI)) * 2.0 / 3.0
return ret
}
// WGS84 → GCJ-02
static wgs84ToGcj02(wgsLat: number, wgsLng: number): Coordinate {
if (this.outOfChina(wgsLat, wgsLng)) {
return { latitude: wgsLat, longitude: wgsLng }
}
let dLat = this.transformLat(wgsLng - 105.0, wgsLat - 35.0)
let dLng = this.transformLng(wgsLng - 105.0, wgsLat - 35.0)
const radLat = wgsLat / 180.0 * this.PI
let magic = Math.sin(radLat)
magic = 1 - this.EE * magic * magic
const sqrtMagic = Math.sqrt(magic)
dLat = (dLat * 180.0) / ((this.A * (1 - this.EE)) / (magic * sqrtMagic) * this.PI)
dLng = (dLng * 180.0) / (this.A / sqrtMagic * Math.cos(radLat) * this.PI)
return { latitude: wgsLat + dLat, longitude: wgsLng + dLng }
}
// GCJ-02 → WGS84
static gcj02ToWgs84(gcjLat: number, gcjLng: number): Coordinate {
if (this.outOfChina(gcjLat, gcjLng)) {
return { latitude: gcjLat, longitude: gcjLng }
}
let dLat = this.transformLat(gcjLng - 105.0, gcjLat - 35.0)
let dLng = this.transformLng(gcjLng - 105.0, gcjLat - 35.0)
const radLat = gcjLat / 180.0 * this.PI
let magic = Math.sin(radLat)
magic = 1 - this.EE * magic * magic
const sqrtMagic = Math.sqrt(magic)
dLat = (dLat * 180.0) / ((this.A * (1 - this.EE)) / (magic * sqrtMagic) * this.PI)
dLng = (dLng * 180.0) / (this.A / sqrtMagic * Math.cos(radLat) * this.PI)
return { latitude: gcjLat - dLat, longitude: gcjLng - dLng }
}
// GCJ-02 → BD-09
static gcj02ToBd09(gcjLat: number, gcjLng: number): Coordinate {
const z = Math.sqrt(gcjLng * gcjLng + gcjLat * gcjLat) +
0.00002 * Math.sin(gcjLat * this.PI * 3000.0 / 180.0)
const theta = Math.atan2(gcjLat, gcjLng) +
0.000003 * Math.cos(gcjLng * this.PI * 3000.0 / 180.0)
return { latitude: z * Math.sin(theta) + 0.006, longitude: z * Math.cos(theta) + 0.0065 }
}
// BD-09 → GCJ-02
static bd09ToGcj02(bdLat: number, bdLng: number): Coordinate {
const x = bdLng - 0.0065
const y = bdLat - 0.006
const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.PI * 3000.0 / 180.0)
const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.PI * 3000.0 / 180.0)
return { latitude: z * Math.sin(theta), longitude: z * Math.cos(theta) }
}
// WGS84 → BD-09
static wgs84ToBd09(wgsLat: number, wgsLng: number): Coordinate {
const gcj02 = this.wgs84ToGcj02(wgsLat, wgsLng)
return this.gcj02ToBd09(gcj02.latitude, gcj02.longitude)
}
// BD-09 → WGS84
static bd09ToWgs84(bdLat: number, bdLng: number): Coordinate {
const gcj02 = this.bd09ToGcj02(bdLat, bdLng)
return this.gcj02ToWgs84(gcj02.latitude, gcj02.longitude)
}
}
8. 使用示例
// 场景1: GPS定位结果用于高德地图
import geoLocationManager from '[@ohos](/user/ohos).geoLocationManager'
import { CoordinateConverter } from '../common/utils/CoordinateConverter'
// GPS 定位回调
const locationCallback = (location: geoLocationManager.Location) => {
console.info(`GPS原始坐标(WGS84): ${location.latitude}, ${location.longitude}`)
// 转换为 GCJ更多关于HarmonyOS 鸿蒙Next中坐标系转换:WGS84、GCJ-02、BD-09 相互转换的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS Next中,坐标系转换可通过@ohos.geoLocationManager模块实现。WGS84、GCJ-02、BD-09之间的转换主要依赖geoLocationManager.convert方法,传入源坐标、目标坐标系类型(如geoLocationManager.CoordinateSystemType.GCJ02)即可获取转换结果。系统已内置转换算法,无需引入第三方库。注意坐标参数需符合标准格式,转换精度符合国家测绘要求。
在HarmonyOS Next中处理坐标系转换,核心是理解三种坐标系的差异并实现精确算法。
1. 坐标系解析
- WGS84:GPS原始坐标系,国际标准
- GCJ-02:中国国测局加密坐标系(高德、腾讯使用)
- BD-09:百度在GCJ-02基础上二次加密的坐标系
2. 转换实现方案
方案A:使用系统定位能力
// 获取原始WGS84坐标
import geoLocationManager from '@ohos.geoLocationManager';
let location = await geoLocationManager.getCurrentLocation();
// 系统会自动处理坐标系适配
方案B:纯算法转换(需自行实现)
// WGS84转GCJ-02核心算法示例
class CoordinateConverter {
static transformWGS84ToGCJ02(wgsLat: number, wgsLon: number): [number, number] {
const a = 6378245.0; // 长半轴
const ee = 0.00669342162296594323; // 偏心率平方
// 偏移量计算逻辑
let dLat = this.transformLat(wgsLon - 105.0, wgsLat - 35.0);
let dLon = this.transformLon(wgsLon - 105.0, wgsLat - 35.0);
const radLat = wgsLat / 180.0 * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
const 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 [wgsLat + dLat, wgsLon + dLon];
}
// BD-09与GCJ-02转换(线性变换)
static transformGCJ02ToBD09(gcjLat: number, gcjLon: number): [number, number] {
const z = Math.sqrt(gcjLon * gcjLon + gcjLat * gcjLat) + 0.00002 * Math.sin(gcjLat * Math.PI * 3000.0 / 180.0);
const theta = Math.atan2(gcjLat, gcjLon) + 0.000003 * Math.cos(gcjLon * Math.PI * 3000.0 / 180.0);
return [z * Math.sin(theta) + 0.006, z * Math.cos(theta) + 0.0065];
}
}
3. 实际应用建议
地图SDK选择:
- 高德地图:直接使用GCJ-02坐标
- 百度地图:使用BD-09坐标
- 系统地图:自动适配
数据处理策略:
- 存储时统一为WGS84坐标系
- 显示时根据使用的地图SDK实时转换
- 跨平台数据交换时注明坐标系类型
性能优化:
- 批量坐标转换使用Worker线程
- 缓存常用区域的转换结果
- 使用Float32Array处理大量坐标数据
4. 精度控制 转换误差主要来自:
- 算法实现精度(建议使用double精度计算)
- 加密参数版本差异
- 不同地区的偏移参数
建议在实际使用区域进行精度验证,必要时加入区域化校正参数。
注意事项:
- 转换算法涉及国家安全规定,需确保合规使用
- 商业应用建议直接使用各厂商提供的地图SDK
- 海外应用通常只需处理WGS84坐标系
通过合理选择转换方案和地图SDK,可以有效解决HarmonyOS Next应用中的坐标系偏移问题。

