# 中国地图坐标系转换详解:从WGS-84到GCJ-02再到BD-09

# 引言

在地理信息系统(GIS)和地图应用程序中,不同的地图服务提供商使用不同的坐标系。这意味着,如果您正在使用多个地图服务,您可能需要在不同的坐标系之间进行转换。本文将重点介绍中国地区常见的三种坐标系:WGS-84、GCJ-02(火星坐标)和BD-09(百度坐标)以及它们之间的转换。

# 坐标系简介

# WGS-84

WGS84:World Geodetic System 1984,是为GPS全球定位系统使用而建立的坐标系统。通过遍布世界的卫星观测站观测到的坐标建立,其初次WGS84的精度为1-2m,在1994年1月2号,通过10个观测站在GPS测量方法上改正,得到了WGS84(G730),G表示由GPS测量得到,730表示为GPS时间第730个周。1996年,National Imagery and Mapping Agency (NIMA) 为美国国防部 (U.S.Departemt of Defense, DoD)做了一个新的坐标系统。这样实现了新的WGS版本:WGS(G873)。其因为加入了USNO站和北京站的改正,其东部方向加入了31-39cm 的改正。所有的其他坐标都有在1分米之内的修正。第三次精化:WGS84(G1150),于2002年1月20日启用。

# GCJ-02

火星坐标系,也叫国测局坐标系(GCJ02),正式名称为「地形图非线性保密处理技术」。是由中国国家测绘局2002年制订的地理信息系统的坐标系统。我国规定国内出版的各种地图系统(包括电子形式),必须至少采用“GCJ02”对地理位置进行首次加密。所以在中国所有的地图必须使用“GCJ02”对地理位置进行首次加密。比如高德、腾讯都在用这个坐标系。它主要目的不是对GPS定位进行偏移,而是对高精度地图进行偏移,降低其精度。定位信息不是重中之重,高精度地图则是国家机密。而对定位进行偏移只不过是为了与脱密后的地图保持同步性,从而不影响导航等公开领域的应用。目标是通过保密处理,对高精度数据进行偏移,使其成为低精度数据,从而降低国外导弹精准打击的准度,以保护我国、我军的重要基础设施。

# BD-09

BD-09 是百度地图使用的坐标系。在GCJ02坐标系基础上再次加密。其中BD09LL表示百度经纬度坐标,BD09MC表示百度墨卡托米制坐标。

# 转换算法实现

核心代码如下:

# WGS84和火星坐标(GCJ02)互转

	/**
     * WGS-84 转 GCJ-02
     *
     * @param lng WGS-84经度
     * @param lat WGS-84纬度
     * @return GCJ-02 坐标系中的经度和纬度
     */
    public static double[] wgs84togcj02(double lng, double lat) {
        if (outOfChina(lng, lat)) {
            return new double[]{lng, lat};
        } else {
            double dlat = transformlat(lng - 105.0, lat - 35.0);
            double dlng = transformlng(lng - 105.0, lat - 35.0);
            double radlat = lat / 180.0 * PI;
            double magic = Math.sin(radlat);
            magic = 1 - ee * magic * magic;
            double sqrtmagic = Math.sqrt(magic);
            dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
            dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
            double mglat = lat + dlat;
            double mglng = lng + dlng;
            return new double[]{mglng, mglat};
        }
    }

    /**
     * GCJ-02 转换为 WGS-84
     *
     * @param lng GCJ-02经度
     * @param lat GCJ-02纬度
     * @return WGS-84 坐标系中的经度和纬度
     */
    public static double[] gcj02towgs84(double lng, double lat) {
        if (outOfChina(lng, lat)) {
            return new double[]{lng, lat};
        } else {
            double dlat = transformlat(lng - 105.0, lat - 35.0);
            double dlng = transformlng(lng - 105.0, lat - 35.0);
            double radlat = lat / 180.0 * PI;
            double magic = Math.sin(radlat);
            magic = 1 - ee * magic * magic;
            double sqrtmagic = Math.sqrt(magic);
            dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
            dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
            double mglat = lat + dlat;
            double mglng = lng + dlng;
            return new double[]{lng * 2 - mglng, lat * 2 - mglat};
        }
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

# 火星坐标(GCJ02)和百度坐标(BD09)互转

    /**
     * 百度坐标系 (BD-09) 转 火星坐标系 (GCJ-02)
     * 即 百度 转 谷歌、高德
     *
     * @param bdLng 百度经度
     * @param bdLat 百度纬度
     * @return GCJ-02 坐标系中的经度和纬度
     */
    public static double[] bd09togcj02(double bdLng, double bdLat) {
        // 经纬度偏移量
        double x = bdLng - 0.0065;
        double y = bdLat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);
        double ggLng = z * Math.cos(theta);
        double ggLat = z * Math.sin(theta);
        return new double[]{ggLng, ggLat};
    }

    /**
     * 火星坐标系 (GCJ-02) 转 百度坐标系 (BD-09)
     * 即 谷歌、高德 转 百度
     *
     * @param lng GCJ-02经度
     * @param lat GCJ-02纬度
     * @return BD-09 坐标系中的经度和纬度
     */
    public static double[] gcj02tobd09(double lng, double lat) {
        double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
        double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
        double bdLng = z * Math.cos(theta) + 0.0065;
        double bdLat = z * Math.sin(theta) + 0.006;
        return new double[]{bdLng, bdLat};
    }
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

# 测试方法

这里在百度拾取坐标系统 (opens new window)中设置一个点

image-20230910221414884

使用转换工具测试

    public static void main(String[] args) {
        double baiduLon = 121.699506;
        double baiduLat = 31.316136;
        double[] gcj02Point = bd09togcj02(baiduLon, baiduLat);
        double[] wgs84Point = gcj02towgs84(gcj02Point[0], gcj02Point[1]);
        System.out.println("百度原坐标:" + baiduLon + "---" + baiduLat);
        System.out.println("百度转wgs84坐标:" + wgs84Point[0] + "---" + wgs84Point[1]);
    }
1
2
3
4
5
6
7
8

image-20230910221616783

叠加测试结果,位置基本上是一致的,偏差很小

image-20230910221655585

# 仓库代码地址

代码地址 (opens new window)

请点个star关注一下,后面还会持续分享干货的。

image-20230910222847292

上次更新时间: 2023年9月10日星期日晚上10点30分