前言:地图开发中的坐标系挑战
在WebGIS开发中,不同数据源往往采用不同的坐标系,这给地图叠加显示带来了巨大挑战。本文将深入探讨如何使用Leaflet实现多坐标系地图的完美叠加,并分享在实际项目中的最佳实践。
开发痛点:你是否遇到过WGS84坐标系的底图与GCJ02坐标系的数据无法对齐的问题?或者在叠加百度地图、高德地图时出现位置偏移?
01|坐标系基础:从WGS84到GCJ02
主流坐标系解析
| 坐标系 | 全称 | 使用场景 | 特点 |
|---|---|---|---|
| WGS84 | World Geodetic System 1984 | GPS、国际通用 | 国际标准,未经加密 |
| GCJ02 | 国测局坐标系 | 高德、腾讯地图 | 加入随机偏移 |
| BD09 | 百度坐标系 | 百度地图 | 二次加密偏移 |
| CGCS2000 | 中国大地坐标系 | 官方测绘 | 与WGS84接近 |
Leaflet中的坐标系处理机制
Leaflet默认使用WGS84坐标系(EPSG:4326),但实际应用中我们经常需要处理多种坐标系:
// Leaflet默认投影设置
const map = L.map('map').setView([39.9042, 116.4074], 13);
// WGS84坐标系底图
const osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
});
// 问题:直接叠加GCJ02数据会出现偏移
const gcj02Marker = L.marker([39.9042, 116.4074]).addTo(map);
// 实际位置会有50-700米的偏移 !02|坐标转换算法实现
核心转换函数
/**
* WGS84转GCJ02坐标系转换
* @param {number} lng - 经度
* @param {number} lat - 纬度
* @returns {Object} 转换后的坐标
*/
function wgs84ToGcj02(lng, lat) {
const PI = Math.PI;
const X_PI = (PI * 3000.0) / 180.0;
const A = 6378245.0;
const EE = 0.00669342162296594323;
function transformLat(lng, lat) {
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 * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
return ret;
}
function transformLng(lng, lat) {
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 * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
return ret;
}
// 判断是否在中国境外
if (lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271) {
return { lng: lng, lat: lat };
}
let dlat = transformLat(lng - 105.0, lat - 35.0);
let dlng = transformLng(lng - 105.0, lat - 35.0);
const radlat = lat / 180.0 * 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) * PI);
dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI);
return {
lng: lng + dlng,
lat: lat + dlat
};
}
/**
* GCJ02转WGS84
*/
function gcj02ToWgs84(lng, lat) {
const gcj = wgs84ToGcj02(lng, lat);
return {
lng: lng * 2 - gcj.lng,
lat: lat * 2 - gcj.lat
};
}