ECharts 经纬度转换完全指南
文档类型: 工具指南
难度等级: ⭐⭐
源码版本: ECharts 5.x
本文行数: 约380行
📋 目录
🎯 坐标系基础
WGS84坐标系
全球定位系统(GPS)使用的标准坐标系:
typescript
interface WGS84Coord {
longitude: number; // 经度 [-180, 180]
latitude: number; // 纬度 [-90, 90]
}
// 示例: 北京天安门
const beijing: WGS84Coord = {
longitude: 116.4074,
latitude: 39.9042
};1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
GCJ02坐标系 (火星坐标系)
中国国测局制定的加密坐标系,对WGS84进行偏移:
typescript
// 高德地图、腾讯地图使用
const gcj02Coord = {
longitude: 116.4104, // 与WGS84有偏差
latitude: 39.9063
};1
2
3
4
5
2
3
4
5
BD09坐标系
百度地图使用的坐标系,在GCJ02基础上再次加密:
typescript
// 百度地图使用
const bd09Coord = {
longitude: 116.4164, // 与GCJ02又有偏差
latitude: 39.9113
};1
2
3
4
5
2
3
4
5
🌍 常用坐标系统对比
| 坐标系 | 别名 | 使用场景 | 偏差范围 |
|---|---|---|---|
| WGS84 | GPS坐标 | 国际标准 | 0m |
| GCJ02 | 火星坐标 | 高德、腾讯 | 50-700m |
| BD09 | 百度坐标 | 百度地图 | 额外偏移 |
🔄 坐标转换方法
WGS84转GCJ02
typescript
function wgs84ToGcj02(wgLon: number, wgLat: number): [number, number] {
const a = 6378245.0;
const ee = 0.00669342162296594323;
if (outOfChina(wgLon, wgLat)) {
return [wgLon, wgLat];
}
let dLat = transformLat(wgLon - 105.0, wgLat - 35.0);
let dLon = transformLon(wgLon - 105.0, wgLat - 35.0);
const radLat = wgLat / 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);
const mgLat = wgLat + dLat;
const mgLon = wgLon + dLon;
return [mgLon, mgLat];
}
function outOfChina(lon: number, lat: number): boolean {
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
function transformLat(x: number, y: number): number {
let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
}
function transformLon(x: number, y: number): number {
let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
}
// 使用
const [gcjLon, gcjLat] = wgs84ToGcj02(116.4074, 39.9042);
console.log(`GCJ02: ${gcjLon}, ${gcjLat}`);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
GCJ02转BD09
typescript
function gcj02ToBd09(gcjLon: number, gcjLat: 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);
const bdLon = z * Math.cos(theta) + 0.0065;
const bdLat = z * Math.sin(theta) + 0.006;
return [bdLon, bdLat];
}
// 使用
const [bdLon, bdLat] = gcj02ToBd09(116.4104, 39.9063);
console.log(`BD09: ${bdLon}, ${bdLat}`);1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
使用第三方库
bash
npm install coordtransform1
typescript
import { wgs84togcj02, gcj02tobd09 } from 'coordtransform';
// WGS84 -> GCJ02
const gcj02 = wgs84togcj02(116.4074, 39.9042);
// GCJ02 -> BD09
const bd09 = gcj02tobd09(gcj02[0], gcj02[1]);
console.log(`GCJ02: ${gcj02}`);
console.log(`BD09: ${bd09}`);1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
💻 实战案例
多地图平台兼容
typescript
class MultiMapAdapter {
/**
* 根据不同地图平台转换坐标
*/
static transformCoordinate(
lng: number,
lat: number,
platform: 'amap' | 'baidu' | 'tencent'
): [number, number] {
// 假设原始数据是WGS84
const [gcjLng, gcjLat] = wgs84ToGcj02(lng, lat);
switch (platform) {
case 'amap':
case 'tencent':
return [gcjLng, gcjLat]; // 高德、腾讯使用GCJ02
case 'baidu':
return gcj02ToBd09(gcjLng, gcjLat); // 百度使用BD09
default:
return [lng, lat];
}
}
/**
* 在ECharts中使用
*/
static prepareDataForECharts(
rawData: Array<{name: string; lng: number; lat: number}>,
mapPlatform: string
) {
return rawData.map(item => {
const [lng, lat] = this.transformCoordinate(
item.lng,
item.lat,
mapPlatform as any
);
return {
name: item.name,
value: [lng, lat]
};
});
}
}
// 使用
const rawLocations = [
{ name: '北京', lng: 116.4074, lat: 39.9042 },
{ name: '上海', lng: 121.4737, lat: 31.2304 }
];
// 为高德地图准备数据
const amapData = MultiMapAdapter.prepareDataForECharts(rawLocations, 'amap');
// 为百度地图准备数据
const baiduData = MultiMapAdapter.prepareDataForECharts(rawLocations, 'baidu');
// 在ECharts中使用
chart.setOption({
geo: {
map: 'china'
},
series: [{
type: 'scatter',
coordinateSystem: 'geo',
data: amapData // 或 baiduData
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
地址解析 (Geocoding)
typescript
class Geocoder {
private apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
/**
* 地址转坐标 (使用高德地图API)
*/
async geocode(address: string): Promise<[number, number] | null> {
const url = `https://restapi.amap.com/v3/geocode/geo?key=${this.apiKey}&address=${encodeURIComponent(address)}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.status === '1' && data.geocodes.length > 0) {
const location = data.geocodes[0].location; // "经度,纬度"
const [lng, lat] = location.split(',').map(Number);
return [lng, lat];
}
return null;
} catch (error) {
console.error('地址解析失败:', error);
return null;
}
}
/**
* 批量地址解析
*/
async batchGeocode(addresses: string[]): Promise<Array<{address: string; coords: [number, number] | null}>> {
const results = [];
for (const address of addresses) {
const coords = await this.geocode(address);
results.push({ address, coords });
// 避免请求过快
await new Promise(resolve => setTimeout(resolve, 100));
}
return results;
}
}
// 使用
const geocoder = new Geocoder('your-api-key');
async function initMap() {
const locations = await geocoder.batchGeocode([
'北京市朝阳区建国门',
'上海市浦东新区陆家嘴',
'广州市天河区珠江新城'
]);
const chartData = locations
.filter(loc => loc.coords !== null)
.map((loc, index) => ({
name: loc.address,
value: loc.coords!
}));
chart.setOption({
series: [{
type: 'scatter',
coordinateSystem: 'geo',
data: chartData
}]
});
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
🎯 最佳实践总结
✅ DO - 推荐做法
明确数据来源的坐标系
typescript// 注释说明坐标系类型 const coords = [116.4074, 39.9042]; // WGS841
2统一内部使用一种坐标系
typescript// 建议内部统一使用GCJ02 const internalCoords = wgs84ToGcj02(lng, lat);1
2缓存转换结果
typescriptconst cache = new Map<string, [number, number]>();1
❌ DON'T - 避免做法
- 避免混用不同坐标系typescript
// ❌ 不好 - 混用 data: [ [116.4074, 39.9042], // WGS84 [116.4164, 39.9113] // BD09 ] // ✅ 好 - 统一 data: allCoords.map(c => convertToGCJ02(c))1
2
3
4
5
6
7
8
🔗 相关资源
✅ 地图地理模块完成!
