高德逆地理编码接口返回数据格式不统一以及百度逆地理编码接口返回数据解析失败的踩坑记录

最近有个需求是定位后根据定位的经纬度获取当前地址的详细信息,例如获取街道名称,街道号,乡镇街道编码,区域编码等信息。

于是乎找到了高德的逆地理编码接口,看了看正好符合我的需求。然而使用起来并不顺利!
由于我使用的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里的字段值居然变成了空数组!
这不是扯淡么,你获取不到就获取不到呗,直接返回个空字符串也行啊,有值的时候返回字符串,无值的时候居然返回的是个空数组,这叫我怎么解析?
最关键的是文档上居然没有说明这个情况,我也是醉了!
高德逆地理编码接口返回数据格式不统一以及百度逆地理编码接口返回数据解析失败的踩坑记录_第1张图片

于是乎赶紧给高德平台进行反馈,给的答复是在有些场景下,无法确定乡镇级别地址,所以导致乡镇级别的数据有可能是空值,
数据格式不一致是由于设计的问题,但是高德表示无法修改,这就很难受了,只能自己处理了。

高德逆地理编码接口返回数据格式不统一以及百度逆地理编码接口返回数据解析失败的踩坑记录_第2张图片

当时能想到的方法就是将返回数据格式类型设置为xml,解析xml就可以了。
在这里插入图片描述

但是我的需求是希望尽量能获取到街镇级别的数据,这样一来用户就无需手动填写了。于是乎,想到了百度,本以为高德获取不到,那百度应该也获取不到。
然而,同样的地址,百度却可以获取的到,而且百度接口返回的数据格式是规范的。
于是乎就打算将接口更改为百度的接口,但是,百度也有问题!


百度逆地理编码接口返回数据不是json,而是js,导致无法正常解析

百度逆地理编码

首先百度的文档写的还是很详细的,包括返回数据的字段类型也标清楚了,而且百度的逆地理编码好像要更强大一些,同样的位置,高德获取不到,但是百度能获取到,并且结果也很精确。但是,我发现百度的接口返回的数据没有乡镇街道编码,只有区划代码,于是乎,又给百度进行反馈

高德逆地理编码接口返回数据格式不统一以及百度逆地理编码接口返回数据解析失败的踩坑记录_第3张图片

百度给出明确答复,表示暂不支持!

我尼玛,一个逆地理编码就这么难弄?
这样一来,只能是高德和百度两个接口结合使用了。

而且百度接口不知道为什么,返回的数据居然是个js!而不是json。

高德逆地理编码接口返回数据格式不统一以及百度逆地理编码接口返回数据解析失败的踩坑记录_第4张图片

这就导致了在使用百度接口时一直提示一下错误

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对象进去,并开启非严格模式
高德逆地理编码接口返回数据格式不统一以及百度逆地理编码接口返回数据解析失败的踩坑记录_第5张图片
这个解决完之后,解析数据时又报一下错误,表示解析返回的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

            }
        )

最终打印的结果
在这里插入图片描述

这样一来就满足我的需求了。

好了,关于高德逆地理编码和百度逆地理编码接口的才坑记录就到这了。


如果你觉得本文对你有帮助,麻烦动动手指顶一下,算是对本文的一个认可,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

你可能感兴趣的:(Android)