使用百度地图API进行地址和经纬度之间的转换

简介

我们在项目中可能会遇到将经纬度转换成省市区的需求,这个时候需要各种地图提供的API,这里我们使用百度地图的API。百度地图开放平台,以下所指的经纬度都是百度地图的经纬度。

代码实现

添加一些工具的maven依赖,本例我们使用JDK11

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.4</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.56</version>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
</dependency>

将地理位置转换成经纬度

定义接口请求

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class GeoCodingRequest {

  /**
   * 待解析的地址。最多支持84个字节。
   */
  private String address;
  /**
   * 用户申请注册的key
   */
  private String ak;
  /**
   * 请求签名
   */
  private String sn;
  /**
   * 输出格式为json或者xml
   */
  private String output;
}

定义接口响应

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class GeoCodingInfo {

  private static final Integer SUCCESS = 0;
  //返回结果状态值, 成功返回0
  private Integer status;
  //响应结果
  private GeoCodingResultInfo result;

  public boolean isSuccess() {
    return SUCCESS.equals(status);
  }
}
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class GeoCodingResultInfo {

  /**
   * 经纬度信息
   */
  private GeoCodingLocationInfo location;
  /**
   * 位置的附加信息,是否精确查找。1为精确查找,即准确打点;0为不精确,即模糊打点。
   */
  private Integer precise;
  /**
   * 描述打点绝对精度(即坐标点的误差范围)。 confidence=100,解析误差绝对精度小于20m; confidence≥90,解析误差绝对精度小于50m;
   * confidence≥80,解析误差绝对精度小于100m; confidence≥75,解析误差绝对精度小于200m; confidence≥70,解析误差绝对精度小于300m;
   * confidence≥60,解析误差绝对精度小于500m; confidence≥50,解析误差绝对精度小于1000m; confidence≥40,解析误差绝对精度小于2000m;
   * confidence≥30,解析误差绝对精度小于5000m; confidence≥25,解析误差绝对精度小于8000m; confidence≥20,解析误差绝对精度小于10000m;
   */
  private Integer confidence;
  /**
   * 能精确理解的地址类型,包含:UNKNOWN、国家、省、城市、区县、乡镇、村庄、道路、地产小区、商务大厦、政府机构、交叉路口、商圈、生活服务、休闲娱乐、餐饮、宾馆、购物、金融、教育、医疗
   * 、工业园区 、旅游景点 、汽车服务、火车站、长途汽车站、桥 、停车场/停车区、港口/码头、收费区/收费站、飞机场 、机场 、收费处/收费站 、加油站、绿地、门址
   */
  private String level;
}
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class GeoCodingLocationInfo {

  /**
   * 经度
   */
  private BigDecimal lng;
  /**
   * 维度
   */
  private BigDecimal lat;
}

接下来编写访问百度地图API的工具类,这里我们使用的请求校验方式为sn校验。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.codec.digest.DigestUtils;

public class BaiduMapUtility {

  private static final String BASE_URL = "http://api.map.baidu.com";
  private static final String GEOCODING_URL = "http://api.map.baidu.com/geocoding/v3/";
  private static final String REVERSE_GEOCODING_URL = "http://api.map.baidu.com/reverse_geocoding/v3/";
  private static final String AK = "xxx"; // 你的ak
  private static final String SK = "xxx"; // 你的sk

  /**
   * 地理编码,将地址转换成经纬度
   *
   * @return 响应
   */
  public static GeoCodingInfo geoCoding(String address) throws Exception {
    String url = GEOCODING_URL;
    GeoCodingRequest request = new GeoCodingRequest();
    request.setAddress(address);
    request.setOutput("json");
    request.setAk(AK);
    request.setSn(generateSign(url, request));
    url += getAppendUrl(request, false);
    return doApiRequest(url, GeoCodingInfo.class);
  }

  /**
   * 逆地理编码,将经纬度转换成地址
   *
   * @return 响应
   */
  public static ReverseGeoCodingInfo reverseGeoCoding(String location) throws Exception {
    String url = REVERSE_GEOCODING_URL;
    ReverseGeoCodingRequest request = new ReverseGeoCodingRequest();
    request.setLocation(location);
    request.setOutput("json");
    request.setAk(AK);
    request.setSn(generateSign(url, request));
    //必须
    request.setLocation(encodeValue(true, location));
    url += getAppendUrl(request, false);
    return doApiRequest(url, ReverseGeoCodingInfo.class);
  }

  private static <T> T doApiRequest(String url, Class<T> clazz) throws Exception {
    //创建请求客户端
    HttpClient client = HttpClient.newHttpClient();
    //创建GET请求
    HttpRequest httpRequest = HttpRequest.newBuilder()
        .GET()
        .uri(URI.create(url))
        .build();
    //同步发送请求
    String resp = client.send(httpRequest, BodyHandlers.ofString()).body();
    System.out.println(resp);
    return JSON.parseObject(resp, clazz);
  }

  /**
   * 生成请求签名
   *
   * @param url 请求url
   * @param request 请求对象
   */
  private static String generateSign(String url, Object request) {
    String params = getAppendUrl(request, true);
    String wholeStr =
        url.substring(url.lastIndexOf(BASE_URL) + BASE_URL.length()) + params + SK;
    String encodedStr = encodeValue(true, wholeStr);
    return DigestUtils.md5Hex(encodedStr.getBytes(StandardCharsets.UTF_8));
  }

  /**
   * url拼接参数
   *
   * @param obj 参数对象
   * @param encode 参数值是否编码
   * @return 参数字符串
   */
  private static String getAppendUrl(Object obj, boolean encode) {
    Map<String, Object> map = JSON
        .parseObject(JSON.toJSONString(obj), new TypeReference<LinkedHashMap<String, Object>>() {
        });
    if (map != null && !map.isEmpty()) {
      StringBuilder buffer = new StringBuilder();
      for (Map.Entry<String, Object> entry : map.entrySet()) {
        if (buffer.length() == 0) {
          buffer.append("?");
        } else {
          buffer.append("&");
        }
        buffer.append(entry.getKey()).append("=");
        if (entry.getValue() instanceof Collection) {
          Collection collection = (Collection) entry.getValue();
          for (Object o : collection) {
            buffer.append(encodeValue(encode, o)).append(",");
          }
          buffer.deleteCharAt(buffer.length() - 1);
        } else {
          buffer.append(encodeValue(encode, entry.getValue()));
        }
      }
      return buffer.toString();
    }
    return "";
  }

  /**
   * url编码
   *
   * @param encode 是否编码
   * @param value 待编码值
   * @return 编码后的值
   */
  private static String encodeValue(boolean encode, Object value) {
    return encode ? URLEncoder.encode(value.toString(), StandardCharsets.UTF_8) : value.toString();
  }
}

编写客户端

public class Client {

  public static void main(String[] args) throws Exception {
    GeoCodingInfo geoCodingInfo = BaiduMapUtility.geoCoding("上海市闵行区");
    if (geoCodingInfo.isSuccess()) {
      GeoCodingLocationInfo locationInfo = geoCodingInfo.getResult().getLocation();
      System.out.println(locationInfo.getLng());
      System.out.println(locationInfo.getLat());
    }
  }

}

这里我们将上海市闵行区这个地址转换成经纬度,输出如下

121.38861193361008
31.118842580087429

将经纬度显示一下

将经纬度转换成省市区等地址

定义接口请求

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class ReverseGeoCodingRequest {

  /**
   * 经纬度坐标获取地址,lat<纬度>,lng<经度>
   */
  private String location;
  /**
   * 用户申请注册的key
   */
  private String ak;
  /**
   * 请求签名
   */
  private String sn;
  /**
   * 输出格式为json或者xml
   */
  private String output;
}

定义接口响应

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class ReverseGeoCodingInfo {

  private static final Integer SUCCESS = 0;
  //返回结果状态值, 成功返回0
  private Integer status;
  //响应结果
  private ReverseGeoCodingResultInfo result;

  public boolean isSuccess() {
    return SUCCESS.equals(status);
  }
}
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class ReverseGeoCodingResultInfo {

  /**
   * 经纬度信息
   */
  private GeoCodingLocationInfo location;
  /**
   * 结构化地址信息
   */
  @JSONField(name = "formatted_address")
  private String formattedAddress;
}

客户端调用

public class Client2 {

  public static void main(String[] args) throws Exception {
    String location = "31.118842580087429,121.38861193361008";
    ReverseGeoCodingInfo reverseGeoCodingInfo = BaiduMapUtility.reverseGeoCoding(location);
    if (reverseGeoCodingInfo.isSuccess()) {
      System.out.println(reverseGeoCodingInfo.getResult().getFormattedAddress());
    }
  }

}

将上一个接口响应的经纬度转换成地址信息,输出如下

上海市闵行区沪闵路6258号

遇到的问题

整个请求访问的核心就是创建请求的签名,逆地理编码的接口请求中,经纬度坐标中的逗号需要进行url编码,其他不需要。
错误的url

http://api.map.baidu.com/reverse_geocoding/v3/?ak=ak&location=31.118842580087429,121.38861193361008&output=json&sn=sn

正确的url

http://api.map.baidu.com/reverse_geocoding/v3/?ak=ak&location=31.118842580087429%2C121.38861193361008&output=json&sn=sn

参考

讲讲百度地图API遇到的坑,石锤百度官方代码的错,解决SN校验失败
百度地图开放平台Web服务API

posted @ 2020-12-18 22:23  strongmore  阅读(3140)  评论(0编辑  收藏  举报