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); } } }