问题解析以及解决思路
业务
:微信小程序定位后,将坐标转换为百度地图坐标,在百度地图做逆地址解析
问题
:微信小程序的定位是拿的腾讯地图的经纬度,但是我们app端这边使用的百度地图,如果直接使用腾讯地图的经纬度再使用腾讯地图的逆地址解析需要腾讯和百度商业授权,为了减少授权成本
解决方案
:将腾讯地图的经纬度转换为百度地图的经纬度后,再进行逆地址解析
- 腾讯地图转百度地图此处有两种解决方案
- 使用数学的方式进行转换可参考我的这篇文章微信小程序定位判断点位是否在某个范围内(腾讯地图转百度地图),但是这种方法有会导致定位有偏差,如果对定位精度要求高的需求不建议使用
- 使用百度地图的官方接口进行转换(我相信官方的转换精度肯定会高一些,本文使用)
本文使用到的两个百度地图的接口文档如下
坐标转换
全球逆地理编码
解决方案实现
开通百度地图相关接口权限
Springboot对接接口
- 新增接口相关的配置
baidu:
service: # 服务端应用
appId: 1160xxxxx
apiKey: C8NCMTxxxxxxxxxxxxxxxxxxx
- 百度接口统一返回对象封装
import lombok.Data;
@Data
public class BaiduApiResponse<T> {
private int status;
private T result;
}
- 坐标转换返回对象封装
import lombok.Data;
import java.util.List;
@Data
public class GeoConvResponseDto {
private double x;
private double y;
}
- 全球逆地理编码返回对象封装
import lombok.Data;
import java.util.List;
@Data
public class ReverseGeocodingResponseDto {
private Location location;
private String formatted_address;
private Edz edz;
private String business;
private AddressComponent addressComponent;
private List<Object> pois;
private List<Object> roads;
private List<Object> poiRegions;
private String sematic_description;
private String formatted_address_poi;
private int cityCode;
@Data
public static class Location {
private double lng;
private double lat;
}
@Data
public static class Edz {
private String name;
}
@Data
public static class AddressComponent {
private String country;
private int country_code;
private String country_code_iso;
private String country_code_iso2;
private String province;
private String city;
private int city_level;
private String district;
private String town;
private String town_code;
private String adcode;
private String street;
private String street_number;
}
}
- 点位对象封装
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class CoordinateDto {
private double longitude;
private double latitude;
@Override
public String toString() {
return longitude + "," + latitude;
}
}
- BaiduMapService(方法实现)
- requestGeoConv参数解释
- coords: 需转换的源坐标,多组坐标以“;”我这里使用的coordinate对象重新了toString方法来构造接口参数
- ak: 开发者密钥, 申请AK
- model:
- 转换方式可选值(默认值 1):
1:amap/tencent to bd09ll
2:gps to bd09ll
3:bd09ll to bd09mc
4:bd09mc to bd09ll
5:bd09ll to amap/tencent
6:bd09mc to amap/tencent- requestReverseGeocoding 参数解释
- location: 根据经纬度坐标获取地址。(请注意这里是反过来的38.76623,116.43213lat<纬度>,lng<经度>)
- ak: 开发者密钥, 申请AK
- coordtype: 传入的坐标类型,目前支持的坐标类型包括:bd09ll(百度经纬度坐标)、bd09mc(百度米制坐标)、gcj02ll(国测局经纬度坐标,仅限中国)、wgs84ll( GPS经纬度)坐标系说明
- 因为requestGeoConv方法已经给我们将经纬度转换为bd09ll标准了,所以我写死为bd09ll
- extensions_poi: extensions_poi=0,不召回pois数据。
extensions_poi=1,返回pois数据(默认显示周边1000米内的poi),并返回sematic_description语义化数据。
package com.applets.manager.core.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.applets.manager.core.exception.BusinessException;
import com.applets.manager.core.model.dto.BaiduApiResponse;
import com.applets.manager.core.model.dto.CoordinateDto;
import com.applets.manager.core.model.dto.GeoConvResponseDto;
import com.applets.manager.core.model.dto.ReverseGeocodingResponseDto;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriUtils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class BaiduMapService {
private static final String GEO_CONV_URL = "https://api.map.baidu.com/geoconv/v2/?";
private static final String REVERSE_GEOCODING_URL = "https://api.map.baidu.com/reverse_geocoding/v3?";
@Value("${baidu.service.appId}")
private String appId;
@Value("${baidu.service.apiKey}")
private String apiKey;
/**
* 发送请求到百度地图 API,执行坐标转换
*
* @param coordinate 点位对象,即经纬度对象
* @param model 转换模型
* @return API 响应结果
* @throws Exception
*/
public BaiduApiResponse<List<GeoConvResponseDto>> requestGeoConv(CoordinateDto coordinate, String model) throws Exception {
Map<String, String> params = new LinkedHashMap<>();
params.put("coords", coordinate.toString());
params.put("model", model);
params.put("ak", apiKey);
String res = sendGetRequest(GEO_CONV_URL, params);
BaiduApiResponse<List<GeoConvResponseDto>> response = null;
if (StringUtils.isNotEmpty(res)){
response = JSON.parseObject(res, new TypeReference<BaiduApiResponse<List<GeoConvResponseDto>>>() {});
if (0 != response.getStatus()){
log.info("坐标转换结果:{}", response);
throw new BusinessException("百度接口请求错误:"+response.getResult());
}
}
return response;
}
/**
* 发送请求到百度地图 API,执行逆地理编码
*
* @param coordinate 点位对象,即经纬度对象
* @return API 响应结果
* @throws Exception
*/
public BaiduApiResponse<ReverseGeocodingResponseDto> requestReverseGeocoding(CoordinateDto coordinate) throws Exception {
Map<String, String> params = new LinkedHashMap<>();
params.put("ak", apiKey);
params.put("output", "json");
params.put("coordtype", "bd09ll");
params.put("extensions_poi", "0");
params.put("location", coordinate.getLatitude()+","+coordinate.getLongitude());
String res = sendGetRequest(REVERSE_GEOCODING_URL, params);
BaiduApiResponse<ReverseGeocodingResponseDto> response = null;
if (StringUtils.isNotEmpty(res)){
response = JSON.parseObject(res, new TypeReference<BaiduApiResponse<ReverseGeocodingResponseDto>>() {});
if (0 != response.getStatus()){
log.info("坐标转换结果:{}", response);
throw new BusinessException("百度接口请求错误:"+response.getResult());
}
}
return response;
}
/**
* 通用的 GET 请求方法
*
* @param strUrl API URL
* @param param 请求参数
* @return 响应字符串
* @throws Exception
*/
private String sendGetRequest(String strUrl, Map<String, String> param) throws Exception {
if (strUrl == null || strUrl.isEmpty() || param == null || param.isEmpty()) {
throw new IllegalArgumentException("URL 和参数不能为空");
}
StringBuilder queryString = new StringBuilder(strUrl);
for (Map.Entry<String, String> entry : param.entrySet()) {
queryString.append(entry.getKey()).append("=")
.append(UriUtils.encode(entry.getValue(), "UTF-8")).append("&");
}
// 删除最后一个 '&' 字符
queryString.deleteCharAt(queryString.length() - 1);
URL url = new URL(queryString.toString());
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.connect();
try (InputStreamReader isr = new InputStreamReader(httpConnection.getInputStream());
BufferedReader reader = new BufferedReader(isr)) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
log.info("BaiduMapService.sendGetRequest.response:{}",response.toString());
return response.toString();
}
}
}
测试
import com.alibaba.fastjson.JSON;
import com.applets.manager.api.StartApplication;
import com.applets.manager.core.model.dto.BaiduApiResponse;
import com.applets.manager.core.model.dto.CoordinateDto;
import com.applets.manager.core.model.dto.GeoConvResponseDto;
import com.applets.manager.core.model.dto.ReverseGeocodingResponseDto;
import com.applets.manager.core.service.BaiduMapService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.geotools.referencing.GeodeticCalculator;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.web.util.UriUtils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zr 2024/4/17
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartApplication.class)
@Slf4j
public class BaiduMapTest {
@Autowired
private BaiduMapService baiduMapService;
@Test
public void requestGeoConv() {
try {
BaiduApiResponse<List<GeoConvResponseDto>> geoConvResponseDtoBaiduApiResponse = baiduMapService.requestGeoConv(new CoordinateDto(103.979967,30.990777), "1");
log.info("requestGeoConv: " + JSON.toJSONString(geoConvResponseDtoBaiduApiResponse));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void requestReverseGeocoding() {
try {
BaiduApiResponse<ReverseGeocodingResponseDto> response = baiduMapService.requestReverseGeocoding(new CoordinateDto(103.98657912268618,30.996470768223653));
log.info("requestReverseGeocoding: " + JSON.toJSONString(response));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
坐标转换测试
初始点位是我随便再腾讯地图上找的点位
注意:这里百度地图返回的地址是一个list
全球逆地理编码测试
使用第一步转换的结果进行逆地址解析
返回json实例
{"result":{"addressComponent":{"adcode":"510182","city":"成都市","city_level":2,"country":"中国","country_code":0,"country_code_iso":"CHN","country_code_iso2":"CN","district":"彭州市","province":"四川省","street":"什彭路","street_number":"","town":"天彭街道","town_code":"510182001"},"business":"","cityCode":75,"edz":{"name":""},"formatted_address":"四川省成都市彭州市什彭路","formatted_address_poi":"","location":{"lat":30.99647061454463,"lng":103.98657912268614},"poiRegions":[],"pois":[],"roads":[],"sematic_description":""},"status":0}