坐标转换模块 java版(附带标准墨卡托坐标)

本项目github地址:https://github.com/kzccat/coordtransform_java
gitee地址:https://gitee.com/ogisosetsuna_kong/coordtransform_java

提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换。

额外提供了百度坐标到通用墨卡托坐标的转换方式,方便各位应用百度1-18级地图进行像素点到坐标的转化。

无其他依赖。

需要js版本可以移步:https://github.com/wandergis/coordtransform

python版本:https://github.com/wandergis/coordTransform_py

go语言社区版本:https://github.com/qichengzx/coordtransform

坐标系简介

坐标系

解释

使用地图

WGS84

地球坐标系,国际上通用的坐标系。设备一般包含GPS芯片或者北斗芯片获取的经纬度为WGS84地理坐标系,最基础的坐标,谷歌地图在非中国地区使用的坐标系。

GPS/谷歌地图卫星

GCJ02

火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。并要求在中国使用的地图产品使用的都必须是加密后的坐标,而这套WGS84加密后的坐标就是gcj02。

腾讯(搜搜)地图,阿里云地图,高德地图,谷歌国内地图

BD09

百度坐标系,百度在GCJ02的基础上进行了二次加密,官方解释是为了进一步保护用户隐私(我差点就信了)

百度地图

墨卡托坐标

墨卡托投影以整个世界范围,赤道作为标准纬线,本初子午线作为中央经线,两者交点为坐标原点,向东向北为正,向西向南为负。南北极在地图的正下、上方,而东西方向处于地图的正右、左。

小众坐标系

类似于百度地图,在GCJ02基础上使用自己的加密算法进行二次加密的坐标系

搜狗地图、图吧地图 等

你可以通过这个工具类将上述坐标系进行互相转换。

百度地图地图投影采用的依然是Web Mercator投影,地图瓦片的切片规则遵循TMS标准,瓦片坐标原点在经纬度为0的附近,但却做了一定的偏移处理,经测算此偏移量约为(-865,15850),即地图瓦片(0, 0)是从Web Mercator投影坐标系的(-865,15850)点开始的。

顺便提供百度地图的地图等级从18级到1级
 18级,1个像素代表1米,17级,1个像素代表2米,16级代表4米,依此类推。

方法说明

GCJ02toBD09(double lng_GCJ, double lat_GCJ) # 火星坐标系->百度坐标系

BD09toGCJ02(double lng_BD, double lat_BD)# 百度坐标系->火星坐标系

WGS84toGCJ02(double lng_wgs, double lat_wgs) # WGS84坐标系->火星坐标系

GCJ02toWGS84(double lng_gcj, double lat_gcj) # 火星坐标系->WGS84坐标系

WGS84toMercator(lng, lat) # WGS84坐标系->标准墨卡托坐标系

bdtoMercator(double lng,double lat) # 百度坐标系->标准墨卡托坐标系

具体代码

/**
 *
 * -----------------------------------------------------------------------------------------
 * 坐标系    |解释                                                              |使用地图
 * -----------------------------------------------------------------------------------------
 * WGS84    |地球坐标系,国际上通用的坐标系。设备一般包含GPS芯片或者北斗芯片获取          |GPS/谷歌地图卫星
 *          |的经纬度为WGS84地理坐标系,最基础的坐标,谷歌地图在非中国地区使用的坐标系     |
 * -----------------------------------------------------------------------------------------
 * GCJ02    |火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。                 |腾讯(搜搜)地图,
 *          |并要求在中国使用的地图产品使用的都必须是加密后的坐标,                      |阿里云地图,高德地图,
 *          |而这套WGS84加密后的坐标就是gcj02。                                    |谷歌国内地图
 * -----------------------------------------------------------------------------------------
 * BD09     |百度坐标系,百度在GCJ02的基础上进行了二次加密,                           |百度地图
 *          |官方解释是为了进一步保护用户隐私(我差点就信了)                           |
 * -----------------------------------------------------------------------------------------
 * 小众坐标系 |类似于百度地图,在GCJ02基础上使用自己的加密算法进行二次加密的坐标系           |搜狗地图、图吧地图 等
 * -----------------------------------------------------------------------------------------
 * 墨卡托坐标  |墨卡托投影以整个世界范围,赤道作为标准纬线,本初子午线作为中央经线,
 *           |两者交点为坐标原点,向东向北为正,向西向南为负。
 *           |南北极在地图的正下、上方,而东西方向处于地图的正右、左。
 *
 * 你可以通过这个工具类将上述坐标系进行互相转换。
 *
 * 百度地图地图投影采用的依然是Web Mercator投影,地图瓦片的切片规则遵循TMS标准,瓦片坐标原点在经纬度为0的附近,
 * 但却做了一定的偏移处理,经测算此偏移量约为(-865,15850),
 * 即地图瓦片(0, 0)是从Web Mercator投影坐标系的(-865,15850)点开始的。
 *
 * 顺便提供百度地图的地图等级从18级到1级
 * 18级,1个像素代表1米,17级,1个像素代表2米,16级代表4米,依此类推
 * Author: kong
 */
public class Coordtransform {

    public static double baiduChange = (Math.PI * 3000.0) / 180.0;
    public static double ee = 0.00669342162296594323;  //偏心率平方
    public static double a = 6378245.0;//  # 长半轴

    /**
     *     百度坐标系(BD-09)转火星坐标系(GCJ-02)
     *     百度——>谷歌、高德
     *
     * @param lng_BD 百度坐标经度
     * @param lat_BD 百度坐标纬度
     * @return 转换后的坐标列表形式
     */
    public static double[] BD09toGCJ02(double lng_BD, double lat_BD){
        double[] GCJ02 = new double[2];

        double x = lng_BD - 0.0065;
        double y = lat_BD - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * baiduChange);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * baiduChange);
        double gg_lng = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        GCJ02[0] = gg_lng;
        GCJ02[1] = gg_lat;

        return GCJ02;
    }

    /**
     *     火星坐标系(GCJ-02)转百度坐标系(BD-09)
     *     谷歌、高德——>百度
     * @param lng_GCJ
     * @param lat_GCJ
     * @return 转换后的坐标列表形式
     */
    public static double[] GCJ02toBD09(double lng_GCJ, double lat_GCJ) {
        double[] BD09 = new double[2];
//        """
//                实现GCJ02向BD09坐标系的转换
//                :param lng: GCJ02坐标系下的经度
//                :param lat: GCJ02坐标系下的纬度
//                :return: 转换后的BD09下经纬度
//                """
        double z = Math.sqrt(lng_GCJ * lng_GCJ + lat_GCJ * lat_GCJ) + 0.00002 * Math.sin(lat_GCJ * Math.PI);
        double theta = Math.atan2(lat_GCJ, lng_GCJ) + 0.000003 * Math.cos(lng_GCJ * Math.PI);
        double bd_lng = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        BD09[0] = bd_lng;
        BD09[1] = bd_lat;
        return BD09;
    }
    /**
     *     GCJ02(火星坐标系)转GPS84
     * @param lng_gcj 火星坐标系的经度
     * @param lat_gcj 火星坐标系纬度
     * @return 转换后的坐标列表形式
     */
    public static double[] GCJ02toWGS84(double lng_gcj, double lat_gcj){

        double[] wgs84 = new double[2];
        if (outOfChina(lng_gcj, lat_gcj)) {
            return new double[]{lng_gcj,lat_gcj};
        }
//       if out_of_china(lng, lat):
//       return [lng, lat]
        double dlat = transformlat(lng_gcj - 105.0, lat_gcj - 35.0);
        double dlng = transformlng(lng_gcj - 105.0, lat_gcj - 35.0);
        double radlat = lat_gcj / 180.0 * Math.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) * Math.PI);
        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * Math.PI);
        double mglat = lat_gcj + dlat;
        double mglng = lng_gcj + dlng;
        return new double[]{lng_gcj * 2 - mglng, lat_gcj * 2 - mglat};
    }

    /**
     *     GPS84转GCJ02(火星坐标系)
     * @param lng_wgs WGS84坐标系的经度
     * @param lat_wgs WGS84坐标系纬度
     * @return 转换后的GCJ02下经纬度
     */
    public static double[] WGS84toGCJ02(double lng_wgs, double lat_wgs) {
        if (outOfChina(lng_wgs, lat_wgs)) {
            return new double[]{lng_wgs,lat_wgs};
        }
        double[] GCJ02 = new double[2];

        double dlat = transformlat(lng_wgs - 105.0, lat_wgs - 35.0);
        double dlng = transformlng(lng_wgs - 105.0, lat_wgs - 35.0);
        double radlat = lat_wgs / 180.0 * Math.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) * Math.PI);
        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * Math.PI);
        double gcj_lng = lat_wgs + dlat;
        double gcj_lat = lng_wgs + dlng;
        GCJ02[0] = gcj_lng;
        GCJ02[1] = gcj_lat;
        return GCJ02;
    }
    /**
     *     GPS84 转 墨卡托坐标
     * @param lng GPS84的经度
     * @param lat GPS84纬度
     * @return 转换后的坐标列表形式
     */
    public static double[] WGS84toMercator(double lng,double lat){
        double x = lng * 20037508.342789/180;
        double y = Math.log(Math.tan((90+lat)*Math.PI/360))/(Math.PI/180);
        y = y *20037508.342789/180;
        return new double[]{x, y};
    }


    /**
     * 百度坐标系转成通用墨卡托坐标
     *
     * @param lng
     * @param lat
     * @return
     */
    public static double[] bdtoMercator(double lng,double lat){
        double[] gcj02 = BD09toGCJ02(lng, lat);
        double[] wgs84 = GCJ02toWGS84(gcj02[0], gcj02[1]);
        double[] mercator = WGS84toMercator(wgs84[0], wgs84[1]);
        return mercator;
    }



    private 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 * Math.PI) + 20.0 *
                Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 *
                Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 *
                Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    private 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 * Math.PI) + 20.0 *
                Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 *
                Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 *
                Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 判断是否在国内,不在国内不做偏移
     * @param lng
     * @param lat
     * @return
     */
    private static boolean outOfChina(double lng, double lat) {
        return  !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
    }

    public static void main(String[] args) {

    }