package com.xiaopeng.map.utils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.*; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; /** * 高德地图地理围栏工具类 */ @Component public class AmapGeoFenceUtil { private final String apiKey; private final OkHttpClient httpClient; private final ObjectMapper objectMapper; // 缓存 private final Map geoFenceCache = new HashMap<>(); public AmapGeoFenceUtil(String apiKey) { this.apiKey = apiKey; this.httpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build(); this.objectMapper = new ObjectMapper(); } /** * 地理坐标点 */ public static class GeoPoint { private double longitude; private double latitude; public GeoPoint(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } public double getLongitude() { return longitude; } public double getLatitude() { return latitude; } @Override public String toString() { return String.format("%.6f,%.6f", longitude, latitude); } public String toLngLatString() { return String.format("%.6f,%.6f", longitude, latitude); } public String toLatLngString() { return String.format("%.6f,%.6f", latitude, longitude); } } /** * 地理围栏类型 */ public enum GeoFenceType { CIRCLE, // 圆形 POLYGON, // 多边形 DISTRICT // 行政区 } /** * 地理围栏 */ public static class GeoFence { private String id; private String name; private GeoFenceType type; private List points; // 多边形顶点 private GeoPoint center; // 圆心 private double radius; // 半径(米) private String adcode; // 行政区划代码 private String polygonString; // 多边形字符串 public GeoFence(String id, String name, GeoFenceType type) { this.id = id; this.name = name; this.type = type; this.points = new ArrayList<>(); } public GeoFence() { } // Getters and Setters public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public GeoFenceType getType() { return type; } public void setType(GeoFenceType type) { this.type = type; } public List getPoints() { return points; } public void setPoints(List points) { this.points = points; } public GeoPoint getCenter() { return center; } public void setCenter(GeoPoint center) { this.center = center; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } public String getAdcode() { return adcode; } public void setAdcode(String adcode) { this.adcode = adcode; } public String getPolygonString() { return polygonString; } public void setPolygonString(String polygonString) { this.polygonString = polygonString; } public void addPoint(GeoPoint point) { this.points.add(point); } public void addPoint(double lng, double lat) { this.points.add(new GeoPoint(lng, lat)); } } /** * 围栏检查结果 */ public static class GeoFenceResult { private boolean isInFence; private String fenceId; private String fenceName; private double distance; // 距离围栏边界的距离(米) private String message; private GeoPoint location; private GeoFenceType fenceType; // Getters and Setters public boolean isInFence() { return isInFence; } public void setInFence(boolean inFence) { isInFence = inFence; } public String getFenceId() { return fenceId; } public void setFenceId(String fenceId) { this.fenceId = fenceId; } public String getFenceName() { return fenceName; } public void setFenceName(String fenceName) { this.fenceName = fenceName; } public double getDistance() { return distance; } public void setDistance(double distance) { this.distance = distance; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public GeoPoint getLocation() { return location; } public void setLocation(GeoPoint location) { this.location = location; } public GeoFenceType getFenceType() { return fenceType; } public void setFenceType(GeoFenceType fenceType) { this.fenceType = fenceType; } @Override public String toString() { return String.format("GeoFenceResult{inFence=%s, fence='%s', distance=%.2fm, message='%s'}", isInFence, fenceName, distance, message); } } /** * 检查点是否在圆形围栏内(调用高德API) */ public GeoFenceResult checkInCircleFence(GeoPoint point, GeoFence fence) throws IOException { GeoFenceResult result = new GeoFenceResult(); result.setLocation(point); result.setFenceId(fence.getId()); result.setFenceName(fence.getName()); result.setFenceType(GeoFenceType.CIRCLE); // 构建API请求参数 Map params = new HashMap<>(); params.put("key", apiKey); params.put("location", point.toLngLatString()); params.put("radius", String.valueOf(fence.getRadius())); params.put("center", fence.getCenter().toLngLatString()); String url = buildUrl("https://restapi.amap.com/v3/geofence/status", params); Request request = new Request.Builder() .url(url) .get() .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { result.setInFence(false); result.setMessage("API请求失败: " + response.code()); return result; } String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); String status = rootNode.get("status").asText(); String info = rootNode.get("info").asText(); if ("1".equals(status)) { JsonNode fencesNode = rootNode.get("fences"); if (fencesNode != null && fencesNode.size() > 0) { JsonNode fenceNode = fencesNode.get(0); String fenceStatus = fenceNode.get("status").asText(); // status: in-在围栏内, out-在围栏外 result.setInFence("in".equals(fenceStatus)); result.setMessage("围栏状态: " + fenceStatus); // 获取距离 if (fenceNode.has("distance")) { result.setDistance(fenceNode.get("distance").asDouble()); } } } else { result.setInFence(false); result.setMessage("API返回错误: " + info); } } catch (Exception e) { result.setInFence(false); result.setMessage("检查围栏状态时发生错误: " + e.getMessage()); } return result; } /** * 检查点是否在多边形围栏内(调用高德API) */ public GeoFenceResult checkInPolygonFence(GeoPoint point, GeoFence fence) throws IOException { GeoFenceResult result = new GeoFenceResult(); result.setLocation(point); result.setFenceId(fence.getId()); result.setFenceName(fence.getName()); result.setFenceType(GeoFenceType.POLYGON); // 构建多边形字符串 String polygon = buildPolygonString(fence.getPoints()); // 构建API请求参数 Map params = new HashMap<>(); params.put("key", apiKey); params.put("location", point.toLngLatString()); params.put("polygon", polygon); String url = buildUrl("https://restapi.amap.com/v3/geofence/status", params); Request request = new Request.Builder() .url(url) .get() .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { result.setInFence(false); result.setMessage("API请求失败: " + response.code()); return result; } String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); String status = rootNode.get("status").asText(); String info = rootNode.get("info").asText(); if ("1".equals(status)) { JsonNode fencesNode = rootNode.get("fences"); if (fencesNode != null && fencesNode.size() > 0) { JsonNode fenceNode = fencesNode.get(0); String fenceStatus = fenceNode.get("status").asText(); result.setInFence("in".equals(fenceStatus)); result.setMessage("围栏状态: " + fenceStatus); if (fenceNode.has("distance")) { result.setDistance(fenceNode.get("distance").asDouble()); } } } else { result.setInFence(false); result.setMessage("API返回错误: " + info); } } catch (Exception e) { result.setInFence(false); result.setMessage("检查围栏状态时发生错误: " + e.getMessage()); } return result; } /** * 检查点是否在行政区划内 */ public GeoFenceResult checkInDistrict(GeoPoint point, String adcode) throws IOException { GeoFenceResult result = new GeoFenceResult(); result.setLocation(point); result.setFenceType(GeoFenceType.DISTRICT); // 调用逆地理编码API获取行政区划信息 Map params = new HashMap<>(); params.put("key", apiKey); params.put("location", point.toLngLatString()); params.put("extensions", "base"); params.put("poitype", ""); String url = buildUrl("https://restapi.amap.com/v3/geocode/regeo", params); Request request = new Request.Builder() .url(url) .get() .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { result.setInFence(false); result.setMessage("API请求失败: " + response.code()); return result; } String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); String status = rootNode.get("status").asText(); String info = rootNode.get("info").asText(); if ("1".equals(status)) { JsonNode regeocode = rootNode.get("regeocode"); if (regeocode != null) { JsonNode addressComponent = regeocode.get("addressComponent"); if (addressComponent != null) { String districtAdcode = addressComponent.get("adcode").asText(); result.setInFence(adcode.equals(districtAdcode)); result.setMessage("行政区划代码: " + districtAdcode); result.setFenceId(adcode); result.setFenceName(addressComponent.get("district").asText()); } } } else { result.setInFence(false); result.setMessage("API返回错误: " + info); } } catch (Exception e) { result.setInFence(false); result.setMessage("检查行政区划时发生错误: " + e.getMessage()); } return result; } /** * 批量检查多个围栏 */ public List checkMultipleFences(GeoPoint point, List fences) { List results = new ArrayList<>(); for (GeoFence fence : fences) { try { GeoFenceResult result; if (fence.getType() == GeoFenceType.CIRCLE) { result = checkInCircleFence(point, fence); } else if (fence.getType() == GeoFenceType.POLYGON) { result = checkInPolygonFence(point, fence); } else { result = new GeoFenceResult(); result.setInFence(false); result.setMessage("不支持的围栏类型"); } results.add(result); } catch (IOException e) { GeoFenceResult errorResult = new GeoFenceResult(); errorResult.setInFence(false); errorResult.setFenceId(fence.getId()); errorResult.setFenceName(fence.getName()); errorResult.setMessage("检查失败: " + e.getMessage()); results.add(errorResult); } } return results; } /** * 创建并缓存地理围栏 */ public void createGeoFence(GeoFence fence) { geoFenceCache.put(fence.getId(), fence); } /** * 从缓存获取地理围栏 */ public GeoFence getGeoFence(String fenceId) { return geoFenceCache.get(fenceId); } /** * 通过高德地图行政区域查询API获取边界坐标 */ public GeoFence getDistrictPolygon(String adcode) throws IOException { Map params = new HashMap<>(); params.put("key", apiKey); params.put("keywords", adcode); params.put("subdistrict", "0"); // 不获取下级行政区 params.put("extensions", "all"); // 获取边界坐标 String url = buildUrl("https://restapi.amap.com/v3/config/district", params); Request request = new Request.Builder() .url(url) .get() .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("请求失败: " + response.code()); } String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); String status = rootNode.get("status").asText(); String info = rootNode.get("info").asText(); if ("1".equals(status)) { JsonNode districts = rootNode.get("districts"); if (districts != null && districts.size() > 0) { JsonNode district = districts.get(0); GeoFence fence = new GeoFence( adcode, district.get("name").asText(), GeoFenceType.POLYGON ); // 解析多边形坐标 String polyline = district.get("polyline").asText(); parsePolylineString(polyline, fence); return fence; } } throw new IOException("获取行政区划失败: " + info); } } /** * 解析多边形坐标字符串 */ private void parsePolylineString(String polyline, GeoFence fence) { // 格式示例: "116.32,39.91|116.33,39.92|116.34,39.93" String[] points = polyline.split("\\|"); for (String point : points) { String[] coords = point.split(","); if (coords.length >= 2) { double lng = Double.parseDouble(coords[0].trim()); double lat = Double.parseDouble(coords[1].trim()); fence.addPoint(lng, lat); } } } /** * 计算两点间距离(Haversine公式) */ public double calculateDistance(GeoPoint p1, GeoPoint p2) { final double EARTH_RADIUS = 6371000.0; // 地球平均半径(米) double lat1 = Math.toRadians(p1.getLatitude()); double lon1 = Math.toRadians(p1.getLongitude()); double lat2 = Math.toRadians(p2.getLatitude()); double lon2 = Math.toRadians(p2.getLongitude()); double dlat = lat2 - lat1; double dlon = lon2 - lon1; double a = Math.sin(dlat / 2) * Math.sin(dlat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dlon / 2) * Math.sin(dlon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS * c; } /** * 构建多边形字符串 */ private String buildPolygonString(List points) { StringBuilder sb = new StringBuilder(); for (GeoPoint point : points) { sb.append(point.getLongitude()).append(",").append(point.getLatitude()).append("|"); } // 移除最后一个竖线 if (sb.length() > 0) { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } /** * 构建URL */ private String buildUrl(String baseUrl, Map params) { StringBuilder urlBuilder = new StringBuilder(baseUrl).append("?"); params.forEach((key, value) -> { urlBuilder.append(key).append("=").append(value).append("&"); }); urlBuilder.deleteCharAt(urlBuilder.length() - 1); return urlBuilder.toString(); } }