1. 程式人生 > 實用技巧 >使用百度地圖API進行地址和經緯度之間的轉換

使用百度地圖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