最近有個需求是定位後根據定位的經緯度獲取當前地址的詳細信息,例如獲取街道名稱,街道號,鄉鎮街道編碼,區域編碼等信息。
於是乎找到了高德的逆地理編碼接口,看了看正好符合我的需求。然而使用起來並不順利!
由於我使用的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
}
)
最終打印的結果
這樣一來就滿足我的需求了。
好了,關於高德逆地理編碼和百度逆地理編碼接口的才坑記錄就到這了。
如果你覺得本文對你有幫助,麻煩動動手指頂一下,算是對本文的一個認可,如果文中有什麼錯誤的地方,還望指正,轉載請註明轉自喻志強的博客 ,謝謝!