Java实现经纬度坐标转换

一、坐标系统简介

坐标系统,是描述物质存在的空间位置(坐标)的参照系,通过定义特定基准及其参数形式来实现。

坐标是描述位置的一组数值,按坐标的维度一般分为一维坐标(公路里程碑)和二维坐标(笛卡尔平面直角坐标、高斯平面直角坐标)、三维坐标(大地坐标、空间直角坐标)。

为了描述或确定位置,必须建立坐标系统,坐标只有存在于某个坐标系统才有实际的意义与具体的位置。

地球是一个球体,球面上的位置,是以经纬度来表示,它称为“球面坐标系统”或“地理坐标系统”。
在球面上计算角度距离十分麻烦,而且地图是印刷在平面纸张上,要将球面上的物体画到纸上,就必须展平,这种将球面转化为平面的过程,称为“投影”。

1、经纬度坐标系

经纬度坐标系是一种地理坐标系统,用于描述地球表面上任意位置的坐标。它是基于地球的自转和赤道的划分而建立的。

  • 经度(Longitude):表示地球表面上一个点相对于本初子午线的东西方向的位置。经度的度量单位是度(°),范围从0°到180°,以东经为正值,西经为负值。本初子午线位于英国伦敦的皇家格林尼治天文台,它被定义为经度0°。
  • 纬度(Latitude):表示地球表面上一个点相对于赤道的北南方向的位置。纬度的度量单位也是度(°),范围从0°到90°,以北纬为正值,南纬为负值。赤道位于纬度0°。

经纬度坐标系统是全球通用的地理坐标系统。

经纬度坐标系统使用经度和纬度的组合来确定地球表面上的特定位置。一个点的经纬度坐标表示为两个数值的组合,例如:40°N,120°E 表示北纬40度,东经120度的位置。

2、坐标系统

(1)WGS84(World Geodetic System 1984,GPS标准)

  • 定义:WWGS84,全称“世界大地坐标系统1984”,是一个国际广泛接受的地心地固坐标系统,也是全球定位系统(GPS)的标准坐标系。WGS84是基于地球椭球体模型,提供全球统一的地理坐标框架,是开放和透明的,适用于全球范围内的导航、定位和地图制作。
  • 历史:经历了多次精化,包括WGS84(G730)、WGS84(G873)和WGS84(G1150)。
  • 参数:长半轴为6378137.0米,扁率为1/298.257223563。
  • 应用场景:全球范围内的GPS定位、地图绘制等。

(2)GCJ-02(国测局坐标系,也被称为火星坐标系)

  • 定义:GCJ-02,全称“中国国测局坐标系统”,也称为“火星坐标”或“火星加密算法”。它是中国国家测绘局制定的一种地理坐标系,用于对中国大陆的地理位置进行偏移加密处理。
  • 特点:它是中国政府为了国家安全而对公开的WGS84坐标数据进行了加密处理,使得在未授权的情况下难以直接使用全球定位系统(GPS)获得精确的位置信息。相对于WGS84坐标系进行了加密处理,用于保护国家安全。
  • 应用场景:在国内的地图服务、导航系统、地理信息系统等应用中得到广泛使用,例如高德地图、腾讯地图等。手机上的地图导航软件利用GCJ-02坐标系实现了高精度的定位和导航功能。

(3)BD-09(Baidu Coordinate System)

  • 定义:BD-09是百度地图使用的一种坐标系。
  • BD-09是百度地图使用的坐标系统,它是在GCJ-02的基础上进行的二次加密。
  • 特点:由于百度地图在中国提供服务,它需要遵守GCJ-02的加密规则,但为了增强定位精度和防止第三方直接解密GCJ-02坐标,百度在其服务中采用了更复杂的加密算法。即基于GCJ-02坐标系进行了加密偏移,提供了更好的数据保护性能。因此,从WGS84到BD-09,需要经过两次转换,先由WGS84转为GCJ-02,然后再转为BD-09。
  • 应用场景:主要用于中国境内各种位置服务应用,如百度地图的定位和导航服务。

(4)CGCS2000(中国2000国家大地坐标系)

  • 定义:CGCS2000,全称“2000国家大地坐标系统”,是中国最新的地心地固坐标系统,替代了之前的北京54和西安80坐标系。以ITRF 97为参考框架,以2000.0作为参考历元。
  • 特点:原点设定在地球的质量中心,Z轴指向IERS参考极,X轴和Y轴通过右手规则确定。
  • CGCS2000基于地球椭球体模型,与国际标准兼容,尤其与北斗卫星导航系统配合使用时,提供高精度的定位服务。它是中国自主的全球定位系统,与WGS84类似,但更适合中国的地理特性。
  • 与WGS84的关系:在定义上与WGS84非常相似,包括原点、尺度和定向。但在扁率上的差异会导致椭球面上的纬度和高度产生微小的变化。
  • 应用场景:作为国家基础坐标系,用于各种测绘和地理信息系统工作。

3、坐标转换简介

在地图应用中,不同的地图服务商通常使用不同的坐标系,坐标转换就是将一个地图服务商的坐标系转换为另一个地图服务商的坐标系,以便在不同的地图上显示相同的位置信息。

GPS(谷歌地图)|高德|百度地图对坐标系统的使用:

  • WGS84:地理坐标系统,GPS仪器记录的经纬度信息。Google Earth采用,Google Map中国范围外使用,高德地图中国范围外使用。
  • GCJ-02:投影坐标系统,火星坐标系,中国国家测绘局制定的坐标系统,由 WGS-84加密后的坐标。适用于高德地图。
  • BD-09:投影坐标系统,百度坐标系,GCJ-02加密后的坐标系,只适用于百度地图。
  • CS2000:中国2000国家大地坐标系统,与WG-S84类似,只适用于北斗卫星。

注意:

  • WGS84、GCJ-02和BD-09之间通过转换算法或者API可以实现互转。
  • GCS2000与GCJ-02和BD-09之间没有直接的转换关系,通常需要将 GCS2000转换为 WGS84,然后通过这个中间坐标系(WGS84)来进行间接转换。
  • 在国内是不允许直接用 WGS84坐标系标注经纬度的,必须经过加密后才能用。所以必须至少使用 GCJ-02坐标系,或者使用在GCJ-02加密后再进行加密的 BD-09坐标系。

不同地图服务商有提供其丰富的 API文档功能,包括经纬度坐标转换功能。有的地图服务商API需要收费。

二、地图经纬度转换工具类

对于 CGCS2000 需要引入 proj4j依赖:

<dependency>
  <groupId>org.locationtech.proj4j</groupId>
  <artifactId>proj4j</artifactId>
  <version>1.3.0</version>
</dependency>
<dependency>
  <groupId>org.locationtech.proj4j</groupId>
  <artifactId>proj4j-epsg</artifactId>
  <version>1.3.0</version>
</dependency>
/**
 * 坐标转换工具类
 * <p>
 * 参考文章-实现的Java版本:https://github.com/wandergis/coordtransform
 */
@Slf4j
public class CoordinateTransformUtil {


    static double x_pi = 3.14159265358979324 * 3000.0 / 180.0;
    // π
    static double pi = 3.1415926535897932384626;
    // 长半轴
    static double a = 6378245.0;
    // 扁率
    static double ee = 0.00669342162296594323;

    /**
     * WGS84 转 GCJ-02
     *
     * @param lng WGS84经度
     * @param lat WGS84纬度
     * @return
     */
    public static Coordinate wgs84ToGcj02(double lng, double lat) {
        if (outOfChina(lng, lat)) {
            return new Coordinate(lng, lat);
        }
        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 Coordinate(mgLng, mgLat);
    }

    /**
     * GCJ-02 转 WGS84
     *
     * @param lng GCJ-02经度
     * @param lat GCJ-02纬度
     * @return
     */
    public static Coordinate gcj02ToWgs84(double lng, double lat) {
        if (outOfChina(lng, lat)) {
            return new Coordinate(lng, lat);
        }
        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 Coordinate(lng * 2 - mgLng, lat * 2 - mgLat);
    }

    /**
     * GCJ-02 转 BD-09
     *
     * @param lng GCJ-02经度
     * @param lat GCJ-02纬度
     * @return
     */
    public static Coordinate 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 bd_lng = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new Coordinate(bd_lng, bd_lat);
    }


    /**
     * BD-09 转 GCJ-02
     *
     * @param lng BD-09经度
     * @param lat BD-09纬度
     */
    public static Coordinate bd09ToGcj02(double lng, double lat) {
        double x = lng - 0.0065;
        double y = lat - 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 gg_lng = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        return new Coordinate(gg_lng, gg_lat);
    }

    /**
     * BD-09 转 WGS84
     *
     * @param lng BD-09经度
     * @param lat BD-09纬度
     * @return
     */
    public static Coordinate bd09ToWgs84(double lng, double lat) {
        Coordinate gcj02 = bd09ToGcj02(lng, lat);
        Coordinate wgs84 = gcj02ToWgs84(gcj02.longitude, gcj02.latitude);
        return wgs84;
    }


    /**
     * WGS84 转 BD-09
     *
     * @param lng WGS84经度
     * @param lat WGS84纬度
     * @return
     */
    public static Coordinate wgs84ToBd09(double lng, double lat) {
        Coordinate gcj02 = wgs84ToGcj02(lng, lat);
        Coordinate bd09 = gcj02ToBd09(gcj02.longitude, gcj02.latitude);
        return bd09;
    }


    /**
     * 纬度转换
     *
     * @param lng
     * @param lat
     * @return
     */
    public static double transformlat(double lng, double lat) {
        double 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;
    }

    /**
     * 经度转换
     *
     * @param lng
     * @param lat
     * @return
     */
    public static double transformlng(double lng, double lat) {
        double 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;
    }

    /**
     * 判断是否在国内,不在国内不做偏移
     *
     * @param lng
     * @param lat
     * @return
     */
    public static boolean outOfChina(double lng, double lat) {
        if (lng < 72.004 || lng > 137.8347) {
            return true;
        } else if (lat < 0.8293 || lat > 55.8271) {
            return true;
        }
        return false;
    }

    /**
     * 坐标类
     */
    @Data
    public static class Coordinate {
        /**
         * 经度
         */
        private double longitude;

        /**
         * 维度
         */
        private double latitude;

        public Coordinate(double longitude, double latitude) {
            // 保留6位小数,四舍五入模式
            //BigDecimal latBigDecimal = new BigDecimal(latitude).setScale(6, RoundingMode.HALF_UP);
            //BigDecimal lonBigDecimal = new BigDecimal(longitude).setScale(6, RoundingMode.HALF_UP);
            //this.longitude = lonBigDecimal.doubleValue();
            //this.latitude = latBigDecimal.doubleValue();
            this.longitude = longitude;
            this.latitude = latitude;
        }
    }

    // 定义CGCS2000的坐标系
    private final static String CGCS2000 = "EPSG:4490";
    // 定义WGS84的坐标系
    final static String WGS84 = "EPSG:4326";

    /**
     * CGCS2000 转 WGS84
     *
     * @param lng CGCS2000经度
     * @param lat CGCS2000纬度
     * @return
     */
    public static Coordinate cgcs2000ToWgs84(double lng, double lat) {

        CRSFactory crsFactory = new CRSFactory();
        // 创建CGCS2000的坐标参考系统
        CoordinateReferenceSystem sourceCRS = crsFactory.createFromName(CGCS2000);
        // 创建WGS84的坐标参考系统
        CoordinateReferenceSystem targetCRS = crsFactory.createFromName(WGS84);

        // 定义坐标转换器
        CoordinateTransformFactory ctFactory = new CoordinateTransformFactory();
        // 创建转换器
        CoordinateTransform transform = ctFactory.createTransform(sourceCRS, targetCRS);
        // 执行坐标转换
        ProjCoordinate srcCoord = new ProjCoordinate(lng, lat);
        ProjCoordinate targetCoord = new ProjCoordinate();
        transform.transform(srcCoord, targetCoord);
        // 4. 输出转换后的正常经纬度坐标
        return new Coordinate(targetCoord.x, targetCoord.y);
    }

    /**
     * WGS84 转 CGCS2000
     *
     * @param lng WGS84经度
     * @param lat WGS84纬度
     * @return
     */
    public static Coordinate wgs84ToCgcs2000(double lng, double lat) {
        CRSFactory crsFactory = new CRSFactory();
        // 定义源和目标投影
        CoordinateReferenceSystem sourceCRS = crsFactory.createFromName(WGS84);
        CoordinateReferenceSystem targetCRS = crsFactory.createFromName(CGCS2000);

        // 定义坐标转换器
        CoordinateTransformFactory ctFactory = new CoordinateTransformFactory();
        // 创建转换器
        CoordinateTransform transform = ctFactory.createTransform(sourceCRS, targetCRS);
        // 执行坐标转换
        ProjCoordinate srcCoord = new ProjCoordinate(lng, lat);
        ProjCoordinate targetCoord = new ProjCoordinate();
        transform.transform(srcCoord, targetCoord);
        // 输出转换后的正常经纬度坐标
        return new Coordinate(targetCoord.x, targetCoord.y);
    }

    public static void main(String[] args) {
        double GPSLon = 108.876152;
        double GPSLat = 34.226685;
        CoordinateTransformUtil.Coordinate wgs84ToGcj02 = CoordinateTransformUtil.wgs84ToGcj02(GPSLon, GPSLat);
        CoordinateTransformUtil.Coordinate wgs84ToBd09 = CoordinateTransformUtil.wgs84ToBd09(GPSLon, GPSLat);
        log.info("GPS wgs84ToGcj02 : longitude={}, latitude={}", wgs84ToGcj02.longitude, wgs84ToGcj02.latitude);
        log.info("GPS wgs84ToBd09  : longitude={}, latitude={}", wgs84ToBd09.longitude, wgs84ToBd09.latitude);

        double aMapLon2 = 108.880753;
        double aMapLat2 = 34.225075;
        CoordinateTransformUtil.Coordinate gcj02ToWgs84 = CoordinateTransformUtil.gcj02ToWgs84(aMapLon2, aMapLat2);
        CoordinateTransformUtil.Coordinate gcj02ToBd09 = CoordinateTransformUtil.gcj02ToBd09(aMapLon2, aMapLat2);
        log.info("高德 gcj02ToWgs84 : longitude={}, latitude={}", gcj02ToWgs84.longitude, gcj02ToWgs84.latitude);
        log.info("高德 gcj02ToBd09  : longitude={}, latitude={}", gcj02ToBd09.longitude, gcj02ToBd09.latitude);

        double baiduLon3 = 108.887314;
        double baiduLat3 = 34.230897;
        CoordinateTransformUtil.Coordinate bd09ToWgs84 = CoordinateTransformUtil.bd09ToWgs84(baiduLon3, baiduLat3);
        CoordinateTransformUtil.Coordinate bd09ToGcj02 = CoordinateTransformUtil.bd09ToGcj02(baiduLon3, baiduLat3);
        log.info("百度 gcj02ToWgs84 : longitude={}, latitude={}", bd09ToWgs84.longitude, bd09ToWgs84.latitude);
        log.info("百度 gcj02ToBd09  : longitude={}, latitude={}", bd09ToGcj02.longitude, bd09ToGcj02.latitude);

        /**
         * CGCS2000
         */
        double CGCS2000Lon4 = 108.887314;
        double CGCS2000Lat4 = 34.230897;
        CoordinateTransformUtil.Coordinate CGCS2000ToWgs84 = CoordinateTransformUtil.cgcs2000ToWgs84(CGCS2000Lon4, CGCS2000Lat4);
        CoordinateTransformUtil.Coordinate CGCS2000ToBd09 = CoordinateTransformUtil.wgs84ToCgcs2000(CGCS2000Lon4, CGCS2000Lat4);
        log.info("中国2000 CGCS2000ToWgs84 : longitude={}, latitude={}", CGCS2000ToWgs84.longitude, CGCS2000ToWgs84.latitude);
        log.info("中国2000 CGCS2000ToBd09  : longitude={}, latitude={}", CGCS2000ToBd09.longitude, CGCS2000ToBd09.latitude);
    }

}

在这里插入图片描述

参考文章:

– 求知若饥,虚心若愚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值