最近有个需求是定位后根据定位的经纬度获取当前地址的详细信息,例如获取街道名称,街道号,乡镇街道编码,区域编码等信息。
于是乎找到了高德的逆地理编码接口,看了看正好符合我的需求。然而使用起来并不顺利!
由于我使用的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就可以了。
但是我的需求是希望尽量能获取到街镇级别的数据,这样一来用户就无需手动填写了。于是乎,想到了百度,本以为高德获取不到,那百度应该也获取不到。
然而,同样的地址,百度却可以获取的到,而且百度接口返回的数据格式是规范的。
于是乎就打算将接口更改为百度的接口,但是,百度也有问题!
百度逆地理编码
首先百度的文档写的还是很详细的,包括返回数据的字段类型也标清楚了,而且百度的逆地理编码好像要更强大一些,同样的位置,高德获取不到,但是百度能获取到,并且结果也很精确。但是,我发现百度的接口返回的数据没有乡镇街道编码,只有区划代码,于是乎,又给百度进行反馈
百度给出明确答复,表示暂不支持!
我尼玛,一个逆地理编码就这么难弄?
这样一来,只能是高德和百度两个接口结合使用了。
而且百度接口不知道为什么,返回的数据居然是个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
*/
fun getBaiduRegeo(location: LocationBean): Observable {
return Observable.create { 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(data, BaiduRegeoBean::class.java)
emitter.onNext(baiduRegeoBean)
emitter.onComplete()
}
}
})
}
}
这样一来我们就获取到了百度逆地理编码返回的数据了,实体类自己定义即可,这里就不浪费篇幅了。
4.请求高德逆地理编码接口
/**
* 高德逆地理编码
* @param map Map
* @return Observable
*/
fun getGaodeRegeo(location: LocationBean): Observable {
val longitude = String.format("%.5f", location.longitude)
val latitude = String.format("%.5f", location.latitude)
val map = mutableMapOf()
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 { 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
}
)
最终打印的结果
这样一来就满足我的需求了。
好了,关于高德逆地理编码和百度逆地理编码接口的才坑记录就到这了。
如果你觉得本文对你有帮助,麻烦动动手指顶一下,算是对本文的一个认可,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!