高德逆地理編碼接口返回數據格式不統一以及百度逆地理編碼接口返回數據解析失敗的踩坑記錄

最近有個需求是定位後根據定位的經緯度獲取當前地址的詳細信息,例如獲取街道名稱,街道號,鄉鎮街道編碼,區域編碼等信息。

於是乎找到了高德的逆地理編碼接口,看了看正好符合我的需求。然而使用起來並不順利!
由於我使用的Retrofit,正常情況下都是直接將json自動解析成實體類,但是由於接口返回的數據格式不規範,導致我遇見的一些問題,下面記錄一下解決辦法,希望能幫到你。

高德逆地理編碼接口返回數據格式不一致的坑

官方接口地址:高德逆地理編碼

文檔上的使用說明很簡單,於是美滋滋的按照文檔來實現了。
測試的也沒問題,都能正常獲取的相關信息。

正常情況下接口返回的數據:

{
	"status": "1",
	"regeocode": {
		"addressComponent": {
			"city": [],
			"province": "上海市",
			"adcode": "310112",
			"district": "閔行區",
			"towncode": "310112008000",
			"streetNumber": {
				"number": "490號",
				"location": "121.284975,31.2019319",
				"direction": "西",
				"distance": "2756.74",
				"street": "金豐路"
			},
			"country": "中國",
			"township": "新虹街道",
			"businessAreas": [{
				"location": "121.30079,31.22569",
				"name": "華漕",
				"id": "310112"
			}, {
				"location": "121.286159,31.175343",
				"name": "徐涇",
				"id": "310118"
			}],
			"building": {
				"name": [],
				"type": []
			},
			"neighborhood": {
				"name": [],
				"type": []
			},
			"citycode": "021"
		},
		"formatted_address": "上海市閔行區新虹街道申長路愛博一村"
	},
	"info": "OK",
	"infocode": "10000"
}

然而,版本發佈後,工作人員外出使用時,進行逆地理編碼時app一直提示數據解析失敗。
我擦嘞,這是什麼情況?數據解析失敗那就是接口返回的數據跟實體類匹配不上唄,然後跟蹤錯誤日誌發現,果然是是數據格式不對。

下面是當高德獲取不到鄉鎮級別的數據時返回的接口:

{
	"status": "1",
	"regeocode": {
		"addressComponent": {
			"city": [],
			"province": "上海市",
			"adcode": "310118",
			"district": "青浦區",
			"towncode": "310118001000",
			"streetNumber": {
				"number": [],
				"direction": [],
				"distance": [],
				"street": []
			},
			"country": "中國",
			"township": "夏陽街道",
			"businessAreas": [
				[]
			],
			"building": {
				"name": [],
				"type": []
			},
			"neighborhood": {
				"name": [],
				"type": []
			},
			"citycode": "021"
		},
		"formatted_address": "上海市青浦區夏陽街道盈港東路新青浦佳園(華科路)"
	},
	"info": "OK",
	"infocode": "10000"
}

可以看到,addressComponent中的streetNumber裏的字段值居然變成了空數組!
這不是扯淡麼,你獲取不到就獲取不到唄,直接返回個空字符串也行啊,有值的時候返回字符串,無值的時候居然返回的是個空數組,這叫我怎麼解析?
最關鍵的是文檔上居然沒有說明這個情況,我也是醉了!
在這裏插入圖片描述

於是乎趕緊給高德平臺進行反饋,給的答覆是在有些場景下,無法確定鄉鎮級別地址,所以導致鄉鎮級別的數據有可能是空值,
數據格式不一致是由於設計的問題,但是高德表示無法修改,這就很難受了,只能自己處理了。

在這裏插入圖片描述

當時能想到的方法就是將返回數據格式類型設置爲xml,解析xml就可以了。
在這裏插入圖片描述

但是我的需求是希望儘量能獲取到街鎮級別的數據,這樣一來用戶就無需手動填寫了。於是乎,想到了百度,本以爲高德獲取不到,那百度應該也獲取不到。
然而,同樣的地址,百度卻可以獲取的到,而且百度接口返回的數據格式是規範的。
於是乎就打算將接口更改爲百度的接口,但是,百度也有問題!


百度逆地理編碼接口返回數據不是json,而是js,導致無法正常解析

百度逆地理編碼

首先百度的文檔寫的還是很詳細的,包括返回數據的字段類型也標清楚了,而且百度的逆地理編碼好像要更強大一些,同樣的位置,高德獲取不到,但是百度能獲取到,並且結果也很精確。但是,我發現百度的接口返回的數據沒有鄉鎮街道編碼,只有區劃代碼,於是乎,又給百度進行反饋

在這裏插入圖片描述

百度給出明確答覆,表示暫不支持!

我尼瑪,一個逆地理編碼就這麼難弄?
這樣一來,只能是高德和百度兩個接口結合使用了。

而且百度接口不知道爲什麼,返回的數據居然是個js!而不是json。

在這裏插入圖片描述

這就導致了在使用百度接口時一直提示一下錯誤

onErrorcom.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $

這個是由於百度返回的接口數據不是一個嚴格的json數據導致的,

onErrorcom.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $

解決辦法,創建GsonConverterFactory的時候手動傳一個Gson對象進去,並開啓非嚴格模式
在這裏插入圖片描述
這個解決完之後,解析數據時又報一下錯誤,表示解析返回的json數據不正確

Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $

這個應該就是因爲返回的是js,而不是json數據導致的,看來,百度的接口是不能使用Retrofit對數據進行自動解析了。


高德百度兩個接口相結合來滿足需求

瞭解了高德和百度接口各自的問題後,我採用了高德和百度兩個接口相結合的方式來獲取詳細的地址信息,由於百度的接口返回的數據比高德更精確且更規範,那麼主要就採用百度的數據,至於鄉鎮街道編碼則使用高德的數據進行補充, 如果你不用必須獲取鄉鎮街道編碼這個數據,那麼,完全可以只使用百度的接口就能滿足你的需求。

因爲最終的數據是從兩個接口返回的數據合併來的,所以,第一反應就是用RxJava中的zip操作符。

下面是實現步驟:
1.首先新建實體類,把需要的字段定義好

/**
 * @description: 詳細的定位信息
 * @author : yzq
 * @date   : 2019/4/12
 * @time   : 10:49
 *
 */

data class LocationDetailsBean(

    var adcode: String = "", // 310118
    var city: String = "", // 上海市
    var country: String = "", // 中國
    var district: String = "", // 青浦區
    var province: String = "", // 上海市
    var street: String = "", // 浦倉路
    var streetNumber: String = "", // 567號
    var town: String = "",//盈浦街道
    var townCode: String = ""

)

2.先使用高德定位進行定位拿到經緯度數據,由於我這邊主要用的是高德的API,所以定位也是高德的,這裏定位代碼我就省略了。

3.請求百度的逆地理編碼接口

由於百度接口直接返回的是js,所以無法正常使用Retrofit的自動解析,這裏我就使用OkHttpClient請求手動轉了

    /**
     * 百度逆地理編碼
     * @param location LocationBean  
     * @return Observable<BaiduRegeoBean> 
     */
    fun getBaiduRegeo(location: LocationBean): Observable<BaiduRegeoBean> {
        return Observable.create<BaiduRegeoBean> { emitter ->
            LogUtils.i("百度逆地理編碼接口")
            val httpClient = OkHttpClient()
            val httpUrl = HttpUrl.parse(Urls.BAIDU_REGEO)!!.newBuilder()
                .addQueryParameter("location", "${location.latitude},${location.longitude}")
                .addQueryParameter("coordtype", "gcj02ll")
                .addQueryParameter("ak", "你申請的ak值")
                .addQueryParameter("output", "json")
                .addQueryParameter("extensions_town", "true")
                .addQueryParameter("latest_admin", "1")
                .build()
            // LogUtils.i("httpUrl:${httpUrl.query()}")
            val request = Request.Builder().url(httpUrl).build()
            //  LogUtils.i("開始請求")
            val response = httpClient.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    emitter.onError(e)
                    emitter.onComplete()
                }
                override fun onResponse(call: Call, response: Response) {
                    if (response.isSuccessful) {
                        val data = response.body()!!.string()
                        val baiduRegeoBean = Gson().fromJson<BaiduRegeoBean>(data, BaiduRegeoBean::class.java)

                        emitter.onNext(baiduRegeoBean)
                        emitter.onComplete()
                    }
                }
            })
        }
    }

這樣一來我們就獲取到了百度逆地理編碼返回的數據了,實體類自己定義即可,這裏就不浪費篇幅了。

4.請求高德逆地理編碼接口

    /**
     * 高德逆地理編碼
     * @param map Map<String, String>
     * @return Observable<GaodeRegeoBean>
     */
    fun getGaodeRegeo(location: LocationBean): Observable<GaodeRegeoBean> {

        val longitude = String.format("%.5f", location.longitude)
        val latitude = String.format("%.5f", location.latitude)
        
        val map = mutableMapOf<String, String>()
        map.put("key", "你的key")
        map.put("location", "${longitude},${latitude}")
        map.put("radius", "3000")
        map.put("output", "JSON")

        return RetrofitFactory.instance.getService(ApiService::class.java).regeo(Urls.GAODE_REGEO, map)
    }

請求高德你地理編碼接口正常使用Retrofit方式即可,需要注意的是,實體類中不要寫 StreetNumber 相關的字段,否則就會出現數據解析失敗的問題,我們這裏其實只需要towncode這個字段的值,其他的字段都不需要解析

 /**
 * @description: 高德逆地理編碼接口返回的數據實體類
 * @author : yzq
 * @date   : 2019/4/15
 * @time   : 16:01
 * 
 */
 
data class GaodeRegeoBean(
    var info: String = "", // OK
    var infocode: String = "", // 10000
    var regeocode: Regeocode = Regeocode(),
    var status: String = "" // 1
) {
    data class Regeocode(
        var addressComponent: AddressComponent = AddressComponent(),

        @SerializedName("formatted_address")
        var formattedAddress: String = ""// 上海市閔行區梅隴鎮春申路1235號春申創意園B座
    ) {

        data class AddressComponent(
            var adcode: String = "", // 310112
            var citycode: String = "", // 021
            var country: String = "", // 中國
            var district: String = "", // 閔行區
            var province: String = "", // 上海市
            //  var streetNumber: StreetNumber = StreetNumber(),
            var towncode: String = "", // 310112108000
            var township: String = "" // 梅隴鎮
        )

    }
}

5.使用Rxjava的zip操作符進行請求,然後將數據合併成我們需要的數據

        Observable.zip(
            editUnitInfoModel.getBaiduRegeo(location),
            editUnitInfoModel.getGaodeRegeo(location),
            BiFunction<BaiduRegeoBean, GaodeRegeoBean, LocationDetailsBean> { baidu, gaode ->
                val locationDetailsBean = LocationDetailsBean()

                if (baidu.status == 0) {
                    val baiduAddressComponent = baidu.result.addressComponent
                    locationDetailsBean.adcode = baiduAddressComponent.adcode
                    locationDetailsBean.city = baiduAddressComponent.city
                    locationDetailsBean.country = baiduAddressComponent.country
                    locationDetailsBean.district = baiduAddressComponent.district
                    locationDetailsBean.province = baiduAddressComponent.province
                    locationDetailsBean.street = baiduAddressComponent.street
                    locationDetailsBean.streetNumber = baiduAddressComponent.streetNumber
                    locationDetailsBean.town = baiduAddressComponent.town
                }

                if (gaode.status == "1") {
                    locationDetailsBean.townCode = gaode.regeocode.addressComponent.towncode
                }
                LogUtils.i("合併後的定位信息爲:${locationDetailsBean}")
                return@BiFunction locationDetailsBean

            }
        )

最終打印的結果
在這裏插入圖片描述

這樣一來就滿足我的需求了。

好了,關於高德逆地理編碼和百度逆地理編碼接口的才坑記錄就到這了。


如果你覺得本文對你有幫助,麻煩動動手指頂一下,算是對本文的一個認可,如果文中有什麼錯誤的地方,還望指正,轉載請註明轉自喻志強的博客 ,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章