2015年3月31日 星期二

驗證GOOGLE轉回來的經緯度正確與否

手上的專案一直都有把地址轉成經緯度,不過由於GOOGLE並沒有保證轉出來的經緯度一定是正確的,而地址的資料來源又有夠髒.......
累積了幾年資料後,某次把資料提供給其他單位參考,還被抓出一堆錯務的經緯度,有些甚至在靠近北極的海上......之後才把經緯度加上了查驗
做法:
1.取出要轉的地址
2.經GOOGLE轉出經緯度
3.把轉回來的經緯度再次利用GOOGLE轉回一組新地址
4.將原地址與經由GOOGLE轉回來的新地址比對,如果原地址的縣市與鄉鎮同時存在於新地址的前十二個字,則認定縣市鄉鎮正確
5.GOOGLE傳回來的地址中,大部分的”臺”都是用”台”,所以在比對的時候有把轉回來的新地址中”台”替換成”臺”再比對

其中的url,http://maps.google.com//maps/api/geocode/json?address=是可以直接用的,後面可以帶的參數有很多,比如說在查地址的時候可以限定回傳的國家,或是在邊界的時候怎麼拚斷,以及回傳文字的語言。

在經緯度反查地址的時候,台灣記得要加上&language=zh-tw,不然傳回來的地址會是英文的

免費的用戶每天有2500筆,企業用戶有10萬筆,但不管免費或企業,google每秒都有設查詢次數限制,要注意

下面是地址查經緯度的部分,經緯度查地址其實是一模一樣的東西,這是第一版,後來有把兩個函式合併

可以留意一下GOOGLE回傳的json中包含"location_type"這個欄位,這是GOOGLE的精準度,共分成四個等級,節錄如下
  • location_type 會儲存指定位置的其他相關資料,目前所支援的值如下:
    • "ROOFTOP" 表示傳回的結果是精準的地理編碼,因為結果中位置資訊的精確範圍已縮小至街道地址。
    • "RANGE_INTERPOLATED" 表示傳回的結果反映的是插入在兩個精確定點之間 (例如十字路口) 的約略位置 (通常會在街道上)。如果 Geocoder 無法取得街道地址的精確定點地理編碼,就會傳回插入的結果。
    • "GEOMETRIC_CENTER" 表示傳回的結果是結果的幾何中心,例如折線 (例如街道) 和多邊形 (區域)。
    • "APPROXIMATE" 表示傳回的結果是約略位置。
節錄自https://developers.google.com/maps/documentation/geocoding/?hl=zh-tw,這是google的geocoding API說明文件,大力推薦先看,當中有更多欄位說明與使用參考

以我個人的經驗,即使是最差的APPROXIMATE,縣市鄉鎮都有可能是對的,所以如果只是要求到縣市鄉鎮對,第四等級的經緯度還是可以用,但如果需要畫圖,比如說經銷商分布圖、公車站牌密度圖或是熱區,則不建議使用第三跟第四等級,因為他們會是幾何與約略位置,我的經驗是他們會集中在"某一點",造成錯誤的熱區或密度結果

/// <summary>
    /// 地址查經緯度
    /// </summary>
    /// <param name="StrAddress">地址</param>
    /// <param name="GoogleResult">回傳結果</param>
    /// <param name="location_type">精準度</param>
    /// <returns></returns>
    public static double[] getLatLng(string StrAddress, out string GoogleResult, out string location_type)
    {
        string address = "";
        string StrClientID = "填購買的google id";
        string keyString = "填購買的google key";
        //google地理服務的網址,下面這串網址就能直接用了
        string urlString = "http://maps.google.com//maps/api/geocode/json?address=" + address + "&sensor=false&client=" + StrClientID;
        //下面這步是企業用戶登入時要作的sign in
        string StrCryptoKey = Sign(urlString, keyString);

        var url = String.Format(StrCryptoKey);
        string result = String.Empty;
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
        using (var response = request.GetResponse())
        using (StreamReader sr = new StreamReader(response.GetResponseStream()))
        {
            //Json格式: 請參考http://code.google.com/intl/zh-TW/apis/maps/documentation/geocoding/
            result = sr.ReadToEnd();
        }
        //將Json字串轉成物件
        GoogleGeocodingAPI.RootObject rootObj = JsonConvert.DeserializeObject<GoogleGeocodingAPI.RootObject>(result);
        double lat, lng;
        if (rootObj.status == "OK")
        {
            GoogleResult = "OK";
            //從results開始往下找
            lat = rootObj.results[0].geometry.location.lat;//緯度
            lng = rootObj.results[0].geometry.location.lng;//經度
            location_type = rootObj.results[0].geometry.location_type.ToString();
        }
        else
        {
            GoogleResult = rootObj.status;
            lat = 0;
            lng = 0;
            location_type = "";
        }
        double[] latLng = { lat, lng };
        return latLng;
    }

下面是比對,想法是只要抓前半段地址比對就好,不需要抓到整個地址,再者由於在臺灣,GOOGLE傳回來的地址前面會有三碼郵遞區號跟"台灣"兩字,所以抓前12個字來比

如果縣市跟鄉鎮都命中就認為是對的

    /// <summary>
    /// 比對縣市鄉鎮有沒有包含在反查回來的地址
    /// </summary>
    /// <param name="city">縣市</param>
    /// <param name="area">鄉鎮</param>
    /// <param name="FixedAddress">地址</param>
    /// <returns></returns>
    public static bool CheckAddressCorrect(string city, string area, string FixedAddress)
    {
        bool result = false;
        string address = "";
        
        if (FixedAddress.Length > 12)
        { address = FixedAddress.Substring(0, 12); }
        else
        { address = FixedAddress; }

        //GOOGLE傳回來的地址會是"台" 不知到甚麼時候才會改回"臺",所以替換掉
        FixedAddress = FixedAddress.Replace('台', '臺'); 

        //高雄市三民區因業務需要,有分成三民東西區,替換掉
        if (city == "高雄市" && area == "三民東區")
            area = area.Replace("三民東區", "三民區");
        if (city == "高雄市" &&  area == "三民西區")
            area = area.Replace("三民西區", "三民區");

        //比對縣市鄉鎮
        if (FixedAddress.IndexOf(city, StringComparison.OrdinalIgnoreCase) > 0 && FixedAddress.IndexOf(area, StringComparison.OrdinalIgnoreCase) > 0)
        { result = true; }

        return result;
    }

大概就是這樣,我曾經碰過短時間內GOOGLE一直回傳"limit query"的錯誤訊息,不過過一下子就好了,與同事討論也無解,只能猜測是當時GOOGLE比較忙吧XD~

文章連結備份

Hash & Salt:http://blog.jobbole.com/61872/