关于LBS坐标系与精度的问题

来源:互联网 发布:eai 数据黑盒 编辑:程序博客网 时间:2024/06/03 12:30

关于LBS坐标系与精度的问题

@(JAVA)[java]
大部分内容来源于:
http://www.jianshu.com/p/f8224779ca63

(一)坐标系问题

App定位遇到的第一个坑是坐标系问题。目前常见的坐标系有三种:地球坐标(WGS84,国际公认坐标),火星坐标(GCJ02,国家标准,适用于高德百度地图大陆+港澳部分、Google地图大陆部分),百度坐标(BD09,适用于百度地图大陆+港澳台部分)。坐标系需要和地图关连才有意义,只有正确匹配地图坐标系的坐标才能在该地图上完美标识位置,否则就会存在偏移。另外对于旅行类App而言,经常需要根据用户当前位置查询周围酒店或者其他POI信息,并且按距离排序,如果坐标系不匹配,就会由于坐标系偏移产生排序问题。

iOS系统上通过定位服务CLLocation相关接口获取定位信息时,获取的经纬度坐标系是WGS84地球坐标,如果直接将该坐标系在iOS系统地图中打点,会发现存在偏移,因为iOS系统地图查看国内时使用的是高德地图数据(这里有另一个坑,详见下文),因此只接受GCJ02火星坐标。如果使用高德或者百度iOS定位SDK中的接口,是可以直接获得火星偏移后的坐标的,由于App Size问题,携程App没有集成第三方SDK,而是通过近似偏移算法直接做偏移(自行Google『transform From WGS To GCJ』)。然而如果在iOS系统地图中获取当前位置,同时在国内,那么获取到的坐标系直接是GCJ02火星坐标系,这点需要小心。

Android系统上通常使用高德或者百度定位SDK获取定位信息。高德SDK没有坐标系参数设定,在大陆和港澳地区获取的坐标系即为GCJ02坐标系,在台湾和海外地区都是WGS84坐标系;百度SDK可以自行设定坐标系参数,即返回WGS84坐标系,还是GCJ02坐标系或者BD09坐标系(注意BD09坐标系只适用于百度地图),如果设定的是GCJ02坐标系,它在大陆+港澳台地区获取的坐标系都是GCJ02坐标系。

海外地图(非大陆和非港澳台地区)是没有火星坐标或者百度坐标之说,都是标准的WGS84地球坐标系。

基本分析结论:【以下结果是最吻合事实的情况,但未完全确认】

1、通过百度抓取到的经纬度都是百度坐标系。
2、ADX发送过来的经纬度是火星坐标系。
因此做法是:将百度抓取过来的数据转为火星坐标系,然后用二者匹配(即数据预处理时多加一步即可)

(二)精度问题

第二个常见的坑是定位精度问题,经常有用户或者Boss反馈,为什么两台一样的手机,获取的当前位置不一样?我明明在这个位置,为什么定位却显示在附件另一个位置,相差那么远?

这类问题的根源是手机不同定位方式导致的,通常手机定位方式有三种:

  1. GPS:根据系统GPS模块获取经纬度,精度10-100米左右,限制是容易受环境影响,在室内几乎不起作用。

  2. 基站:根据运营商基站位置计算经纬度,精度1000-3000米左右,限制是定位较慢,精度差。

  3. WIFI:根据周围WIFI路由器位置计算经纬度,精度100-200米左右,限制是受周围WIFI数量和分布影响,需要打开手机WIFI开关。

(三)坐标系变换代码

完整代码请见:https://github.com/lujinhong/lujinhong-commons/blob/master/lujinhong-commons-java/src/main/java/com/lujinhong/commons/java/lbs/LBSTransformer2.java

package com.lujinhong.commons.java.lbs;import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.util.Scanner;/** * AUTHOR: LUJINHONG * CREATED ON: 17/2/21 22:10 * PROJECT NAME: aplus_dmp * DESCRIPTION: 百度坐标系、火星坐标系、地球坐标系之间互相转换。 */public class LBSTransformer2 {    private static final double LAT_OFFSET_0(double x, double y) {        return -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));    }    private static final double LAT_OFFSET_1(double x, double y) {        return (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;    }    private static final double LAT_OFFSET_2(double x, double y) {        return (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;    }    private static final double LAT_OFFSET_3(double x, double y) {        return (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;    }    private static final double LON_OFFSET_0(double x, double y) {        return 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));    }    private static final double LON_OFFSET_1(double x, double y) {        return (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;    }    private static final double LON_OFFSET_2(double x, double y) {        return (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;    }    private static final double LON_OFFSET_3(double x, double y) {        return (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;    }    private static double RANGE_LON_MAX = 137.8347;    private static double RANGE_LON_MIN = 72.004;    private static double RANGE_LAT_MAX = 55.8271;    private static double RANGE_LAT_MIN = 0.8293;    private static double jzA = 6378245.0;    private static double jzEE = 0.00669342162296594323;    public static double transformLat(double x, double y) {        double ret = LAT_OFFSET_0(x, y);        ret += LAT_OFFSET_1(x, y);        ret += LAT_OFFSET_2(x, y);        ret += LAT_OFFSET_3(x, y);        return ret;    }    public static double transformLon(double x, double y) {        double ret = LON_OFFSET_0(x, y);        ret += LON_OFFSET_1(x, y);        ret += LON_OFFSET_2(x, y);        ret += LON_OFFSET_3(x, y);        return ret;    }    public static boolean outOfChina(double lat, double lon) {        if (lon < RANGE_LON_MIN || lon > RANGE_LON_MAX)            return true;        if (lat < RANGE_LAT_MIN || lat > RANGE_LAT_MAX)            return true;        return false;    }    public static LatLng gcj02Encrypt(double ggLat, double ggLon) {        LatLng resPoint = new LatLng();        double mgLat;        double mgLon;        if (outOfChina(ggLat, ggLon)) {            resPoint.latitude = ggLat;            resPoint.longitude = ggLon;            return resPoint;        }        double dLat = transformLat(ggLon - 105.0, ggLat - 35.0);        double dLon = transformLon(ggLon - 105.0, ggLat - 35.0);        double radLat = ggLat / 180.0 * Math.PI;        double magic = Math.sin(radLat);        magic = 1 - jzEE * magic * magic;        double sqrtMagic = Math.sqrt(magic);        dLat = (dLat * 180.0) / ((jzA * (1 - jzEE)) / (magic * sqrtMagic) * Math.PI);        dLon = (dLon * 180.0) / (jzA / sqrtMagic * Math.cos(radLat) * Math.PI);        mgLat = ggLat + dLat;        mgLon = ggLon + dLon;        resPoint.latitude = mgLat;        resPoint.longitude = mgLon;        return resPoint;    }    public static LatLng gcj02Decrypt(double gjLat, double gjLon) {        LatLng gPt = gcj02Encrypt(gjLat, gjLon);        double dLon = gPt.longitude - gjLon;        double dLat = gPt.latitude - gjLat;        LatLng pt = new LatLng();        pt.latitude = gjLat - dLat;        pt.longitude = gjLon - dLon;        return pt;    }    public static LatLng bd09Decrypt(double bdLat, double bdLon) {        LatLng gcjPt = new LatLng();        double x = bdLon - 0.0065, y = bdLat - 0.006;        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI);        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI);        gcjPt.longitude = z * Math.cos(theta);        gcjPt.latitude = z * Math.sin(theta);        return gcjPt;    }    public static LatLng bd09Encrypt(double ggLat, double ggLon) {        LatLng bdPt = new LatLng();        double x = ggLon, y = ggLat;        double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI);        double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI);        bdPt.longitude = z * Math.cos(theta) + 0.0065;        bdPt.latitude = z * Math.sin(theta) + 0.006;        return bdPt;    }    /**     * @param location 世界标准地理坐标(WGS-84)     * @return 中国国测局地理坐标(GCJ-02)<火星坐标>     * @brief 世界标准地理坐标(WGS-84) 转换成 中国国测局地理坐标(GCJ-02)<火星坐标>     * <p>     * ####只在中国大陆的范围的坐标有效,以外直接返回世界标准坐标     */    public static LatLng wgs84ToGcj02(LatLng location) {        return gcj02Encrypt(location.latitude, location.longitude);    }    /**     * @param location 中国国测局地理坐标(GCJ-02)     * @return 世界标准地理坐标(WGS-84)     * @brief 中国国测局地理坐标(GCJ-02) 转换成 世界标准地理坐标(WGS-84)     * <p>     * ####此接口有1-2米左右的误差,需要精确定位情景慎用     */    public static LatLng gcj02ToWgs84(LatLng location) {        return gcj02Decrypt(location.latitude, location.longitude);    }    /**     * @param location 世界标准地理坐标(WGS-84)     * @return 百度地理坐标(BD-09)     * @brief 世界标准地理坐标(WGS-84) 转换成 百度地理坐标(BD-09)     */    public static LatLng wgs84ToBd09(LatLng location) {        LatLng gcj02Pt = gcj02Encrypt(location.latitude, location.longitude);        return bd09Encrypt(gcj02Pt.latitude, gcj02Pt.longitude);    }    /**     * @param location 中国国测局地理坐标(GCJ-02)<火星坐标>     * @return 百度地理坐标(BD-09)     * @brief 中国国测局地理坐标(GCJ-02)<火星坐标> 转换成 百度地理坐标(BD-09)     */    public static LatLng gcj02ToBd09(LatLng location) {        return bd09Encrypt(location.latitude, location.longitude);    }    /**     * @param location 百度地理坐标(BD-09)     * @return 中国国测局地理坐标(GCJ-02)<火星坐标>     * @brief 百度地理坐标(BD-09) 转换成 中国国测局地理坐标(GCJ-02)<火星坐标>     */    public static LatLng bd09ToGcj02(LatLng location) {        return bd09Decrypt(location.latitude, location.longitude);    }    /**     * @param location 百度地理坐标(BD-09)     * @return 世界标准地理坐标(WGS-84)     * @brief 百度地理坐标(BD-09) 转换成 世界标准地理坐标(WGS-84)     * <p>     * ####此接口有1-2米左右的误差,需要精确定位情景慎用     */    public static LatLng bd09ToWgs84(LatLng location) {        LatLng gcj02 = bd09ToGcj02(location);        return gcj02Decrypt(gcj02.latitude, gcj02.longitude);    }    public static class LatLng {        public double latitude;        public double longitude;        public LatLng(double latitude, double longitude) {            this.latitude = latitude;            this.longitude = longitude;        }        public LatLng() {        }        public double getLatitude() {            return latitude;        }        public void setLatitude(double latitude) {            this.latitude = latitude;        }        public double getLongitude() {            return longitude;        }        public void setLongitude(double longitude) {            this.longitude = longitude;        }    }    private static final String TAB_SEPERATOR = "\t";    public static void main(String[] args) throws IOException {        double lat = 23.117500452966457;        double lng = 113.4247365;        LatLng ll = null;        //假设是世界坐标系        ll = wgs84ToBd09(new LatLng(lat, lng));        System.out.println(ll.getLatitude() + "\t" + ll.getLongitude());        ll = wgs84ToGcj02(new LatLng(lat, lng));        System.out.println(ll.getLatitude() + "\t" + ll.getLongitude());        //假设是火星坐标系        ll = gcj02ToWgs84(new LatLng(lat, lng));        System.out.println(ll.getLatitude() + "\t" + ll.getLongitude());        ll = gcj02ToBd09(new LatLng(lat, lng));        System.out.println(ll.getLatitude() + "\t" + ll.getLongitude());        //假设是百度        ll = bd09ToWgs84(new LatLng(lat, lng));        System.out.println(ll.getLatitude() + "\t" + ll.getLongitude());        ll = bd09ToGcj02(new LatLng(lat, lng));        System.out.println(ll.getLatitude() + "\t" + ll.getLongitude());    }}
原创粉丝点击