package com.doumee.core.utils.geocode;
|
|
import com.alibaba.fastjson.JSONObject;
|
import com.doumee.core.utils.Http;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.stereotype.Component;
|
|
import java.io.IOException;
|
import java.net.URLEncoder;
|
|
/**
|
* 高德地图工具类
|
*
|
* @Author : Rk
|
* @create 2026/4/20
|
*/
|
@Slf4j
|
@Component
|
public class MapUtil {
|
|
private static String amapKey;
|
|
/** 逆地理解析 */
|
private static final String GEO_URL = "https://restapi.amap.com/v3/geocode/regeo";
|
|
/** 驾车路径规划 */
|
private static final String DRIVING_URL = "https://restapi.amap.com/v3/direction/driving";
|
|
/** 骑行路径规划 */
|
private static final String BICYCLING_URL = "https://restapi.amap.com/v4/direction/bicycling";
|
|
@Value("${geocode_map_key:}")
|
public void setAmapKey(String amapKey) {
|
MapUtil.amapKey = amapKey;
|
}
|
|
/**
|
* 逆地理解析 - 根据经纬度获取地址信息
|
* 高德坐标系为 lng,lat(与腾讯 lat,lng 相反)
|
*
|
* @param lat 纬度
|
* @param lng 经度
|
* @return 包含 ad_info 城市信息的 JSONObject(兼容腾讯返回格式)
|
*/
|
public static JSONObject reverseGeocode(double lat, double lng) {
|
try {
|
String url = GEO_URL
|
+ "?key=" + amapKey
|
+ "&location=" + lng + "," + lat;
|
|
log.info("高德地图逆地理解析请求: lat={}, lng={}", lat, lng);
|
|
JSONObject json = new Http().build(url)
|
.setConnectTimeout(5000)
|
.setReadTimeout(10000)
|
.get()
|
.toJSONObject();
|
|
log.info("高德地图逆地理解析响应: {}", json);
|
|
if (!"1".equals(json.getString("status"))) {
|
throw new RuntimeException("高德地图逆地理解析失败: " + json.getString("info"));
|
}
|
|
// 转换为兼容腾讯返回格式: result.ad_info.city / adcode / district
|
JSONObject regeocode = json.getJSONObject("regeocode");
|
JSONObject addressComponent = regeocode.getJSONObject("addressComponent");
|
|
JSONObject adInfo = new JSONObject();
|
adInfo.put("adcode", addressComponent.getString("adcode"));
|
adInfo.put("city", addressComponent.getString("city"));
|
adInfo.put("district", addressComponent.getString("district"));
|
adInfo.put("province", addressComponent.getString("province"));
|
adInfo.put("nation", addressComponent.getString("country"));
|
|
JSONObject result = new JSONObject();
|
result.put("ad_info", adInfo);
|
result.put("formatted_addresses", regeocode.getString("formatted_address"));
|
return result;
|
} catch (IOException e) {
|
log.error("高德地图逆地理解析异常", e);
|
throw new RuntimeException("高德地图逆地理解析异常", e);
|
}
|
}
|
|
/**
|
* 判断两个经纬度是否在同一个城市
|
*/
|
public static boolean isSameCity(double lat1, double lng1, double lat2, double lng2) {
|
JSONObject result1 = reverseGeocode(lat1, lng1);
|
JSONObject result2 = reverseGeocode(lat2, lng2);
|
|
String city1 = result1.getJSONObject("ad_info").getString("city");
|
String city2 = result2.getJSONObject("ad_info").getString("city");
|
|
log.info("判断同城: ({},{}) => city={}, ({},{}) => city={}", lat1, lng1, city1, lat2, lng2, city2);
|
|
return city1 != null && city1.equals(city2);
|
}
|
|
/**
|
* 路径规划(统一入口)
|
* 内部根据 mode 调用高德不同的路径规划接口
|
*
|
* @param mode 模式:driving(驾车)、bicycling(骑行)
|
* @param from 起点,格式:lat,lng
|
* @param to 终点,格式:lat,lng
|
* @return JSONObject 包含 distance(米) 和 duration(秒)
|
*/
|
public static JSONObject direction(String mode, String from, String to) {
|
// 高德坐标系为 lng,lat
|
String[] fromArr = from.split(",");
|
String[] toArr = to.split(",");
|
String origin = fromArr[1] + "," + fromArr[0]; // lng,lat
|
String destination = toArr[1] + "," + toArr[0]; // lng,lat
|
|
try {
|
String url;
|
if ("bicycling".equals(mode)) {
|
url = BICYCLING_URL
|
+ "?key=" + amapKey
|
+ "&origin=" + URLEncoder.encode(origin, "UTF-8")
|
+ "&destination=" + URLEncoder.encode(destination, "UTF-8");
|
} else {
|
// 默认驾车
|
url = DRIVING_URL
|
+ "?key=" + amapKey
|
+ "&origin=" + URLEncoder.encode(origin, "UTF-8")
|
+ "&destination=" + URLEncoder.encode(destination, "UTF-8");
|
}
|
|
log.info("高德地图路径规划请求: mode={}, from={}, to={}", mode, from, to);
|
|
JSONObject json = new Http().build(url)
|
.setConnectTimeout(5000)
|
.setReadTimeout(10000)
|
.get()
|
.toJSONObject();
|
|
log.info("高德地图路径规划响应: {}", json);
|
|
if (!"1".equals(json.getString("status"))) {
|
throw new RuntimeException("高德地图路径规划失败: " + json.getString("info"));
|
}
|
|
// 提取第一条路径的 distance 和 duration
|
JSONObject routeData;
|
if ("bicycling".equals(mode)) {
|
routeData = json.getJSONObject("data");
|
} else {
|
routeData = json.getJSONObject("route");
|
}
|
|
JSONObject path = routeData.getJSONArray("paths").getJSONObject(0);
|
long distance = path.getLongValue("distance");
|
long duration = path.getLongValue("duration");
|
|
JSONObject result = new JSONObject();
|
result.put("distance", distance);
|
result.put("duration", duration);
|
return result;
|
} catch (IOException e) {
|
log.error("高德地图路径规划异常", e);
|
throw new RuntimeException("高德地图路径规划异常", e);
|
}
|
}
|
}
|