rk
15 小时以前 ab9cd2c82bd64de8e33510db1d1e78a5b3b4de70
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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);
        }
    }
}