| ard-admin/src/main/java/com/ard/web/controller/sample/AnnotationController.java | ●●●●● patch | view | raw | blame | history | |
| ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDTO.java | ●●●●● patch | view | raw | blame | history | |
| ard-sample/src/main/java/com/ard/sample/annotation/util/GeoCalculator.java | ●●●●● patch | view | raw | blame | history |
ard-admin/src/main/java/com/ard/web/controller/sample/AnnotationController.java
@@ -2,11 +2,13 @@ import com.ard.sample.annotation.model.dto.AnnotationDTO; import com.ard.sample.annotation.model.dto.AnnotationDirectoryDTO; import com.ard.sample.annotation.model.dto.AnnotationPointsDTO; import com.ard.sample.annotation.model.entity.AnnotationDirectoryEntity; import com.ard.sample.annotation.model.entity.AnnotationEntity; import com.ard.sample.annotation.service.IAnnotationDirectoryService; import com.ard.sample.annotation.service.IAnnotationService; import com.ard.sample.annotation.util.ConvertUtil; import com.ard.sample.annotation.util.GeoCalculator; import com.ard.sample.annotation.util.JsonSerializeUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -19,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Api(tags = "标注管理") @Slf4j @@ -284,21 +287,20 @@ /** * 新增:获取全部目录及下属标注数据 */ @ApiOperation("获取全部目录及下属标注数据") @ApiOperation("获取全部目录及下属标记数据") @GetMapping("/directory/all") public ResponseEntity<Map<String, Object>> listAllDirectoryWithAnnotations() { List<AnnotationDirectoryDTO> directoryList = directoryService.listAllDirectoryWithAnnotations(); Map<String, Object> result = new HashMap<>(); result.put("code", 200); result.put("msg", "查询全部目录及标注成功"); result.put("msg", "查询全部目录及标记成功"); result.put("data", directoryList); return ResponseEntity.ok(result); } // 新增:按标注ID查询详细信息(核心补充接口,返回格式统一为ResponseEntity<Map>) @ApiOperation("按标注ID获取标注详细信息") @ApiOperation("按标注ID获取标记详细信息,包含线的长度,面、圆的面积") @GetMapping("/{id}") public ResponseEntity<Map<String, Object>> getAnnotationDetailById(@PathVariable String id) { Map<String, Object> resultMap = new HashMap<>(); @@ -306,43 +308,135 @@ // 1. 参数校验 if (id == null || id.isEmpty()) { resultMap.put("code", 400); resultMap.put("msg", "标注ID不能为空"); resultMap.put("msg", "标记ID不能为空"); resultMap.put("data", null); return new ResponseEntity<>(resultMap, HttpStatus.BAD_REQUEST); } // 2. 查询标注实体 // 2. 查询标记数据 AnnotationEntity annotationEntity = annotationService.getAnnotationById(id); if (annotationEntity == null) { resultMap.put("code", 404); resultMap.put("msg", "标注不存在或已删除"); resultMap.put("msg", "标记不存在或已删除"); resultMap.put("data", null); return new ResponseEntity<>(resultMap, HttpStatus.NOT_FOUND); } // 3. 实体转DTO(反序列化坐标数据) // 3. 转换为DTO并反序列化坐标 AnnotationDTO annotationDTO = AnnotationDTO.builder().build(); org.springframework.beans.BeanUtils.copyProperties(annotationEntity, annotationDTO); List<AnnotationPointsDTO> coordinateList = null; List<GeoCalculator.Coordinate> geoPoints = new java.util.ArrayList<>(); try { annotationDTO.setCoordinateInfo(JsonSerializeUtil.deserialize(annotationEntity.getCoordinateInfo())); coordinateList = JsonSerializeUtil.deserialize(annotationEntity.getCoordinateInfo()); annotationDTO.setCoordinateInfo(coordinateList); // 转换为工具类坐标 if (coordinateList != null && !coordinateList.isEmpty()) { geoPoints = coordinateList.stream() .map(p -> new GeoCalculator.Coordinate(p.getLongitude(), p.getLatitude())) .collect(Collectors.toList()); } } catch (Exception e) { log.error("反序列化标注坐标失败,标注ID:{}", id, e); log.error("反序列化坐标失败,标注ID:{}", id, e); annotationDTO.setCoordinateInfo(null); } // 4. 统一返回格式 // 4. 按标注类型计算所有参数 String annotationType = annotationEntity.getAnnotationType(); switch (annotationType) { case "2": // line 线:水平距离、直线距离 annotationDTO.setLineHorizontalDistance(GeoCalculator.calculatePolylineHorizontalDistance(geoPoints)); annotationDTO.setLineStraightDistance(GeoCalculator.calculatePolylineStraightDistance(geoPoints)); break; case "3": // polygon 面:水平面积、水平周长 annotationDTO.setPolygonHorizontalArea(GeoCalculator.calculatePolygonHorizontalArea(geoPoints)); annotationDTO.setPolygonHorizontalPerimeter(GeoCalculator.calculatePolygonHorizontalPerimeter(geoPoints)); break; case "4": // circle 圆:水平面积、水平周长(仅需半径) Double radius = annotationDTO.getCoordinateInfo().get(0).getRadius(); //annotationEntity.getRadius(); if (radius == null) radius = 0.0; annotationDTO.setCircleHorizontalArea(GeoCalculator.calculateCircleHorizontalArea(radius)); annotationDTO.setCircleHorizontalPerimeter(GeoCalculator.calculateCircleHorizontalPerimeter(radius)); break; case "1": // point 点:无计算项,赋值0 annotationDTO.setLineHorizontalDistance(0.00); annotationDTO.setLineStraightDistance(0.00); annotationDTO.setPolygonHorizontalArea(0.00); annotationDTO.setPolygonHorizontalPerimeter(0.00); annotationDTO.setCircleHorizontalArea(0.00); annotationDTO.setCircleHorizontalPerimeter(0.00); break; default: // 未知类型 log.warn("未知标记类型:{},不计算地理参数", annotationType); break; } // 5. 返回结果 resultMap.put("code", 200); resultMap.put("msg", "查询成功"); resultMap.put("data", annotationDTO); return new ResponseEntity<>(resultMap, HttpStatus.OK); } catch (Exception e) { // 全局异常捕获 log.error("按ID查询标注详情异常,标注ID:{}", id, e); log.error("查询标记详情异常,ID:{}", id, e); resultMap.put("code", 500); resultMap.put("msg", "查询失败:" + e.getMessage()); resultMap.put("data", null); return new ResponseEntity<>(resultMap, HttpStatus.INTERNAL_SERVER_ERROR); } } // // 新增:按标注ID查询详细信息(核心补充接口,返回格式统一为ResponseEntity<Map>) // @ApiOperation("按标注ID获取标注详细信息") // @GetMapping("/{id}") // public ResponseEntity<Map<String, Object>> getAnnotationDetailById(@PathVariable String id) { // Map<String, Object> resultMap = new HashMap<>(); // try { // // 1. 参数校验 // if (id == null || id.isEmpty()) { // resultMap.put("code", 400); // resultMap.put("msg", "标注ID不能为空"); // resultMap.put("data", null); // return new ResponseEntity<>(resultMap, HttpStatus.BAD_REQUEST); // } // // // 2. 查询标注实体 // AnnotationEntity annotationEntity = annotationService.getAnnotationById(id); // if (annotationEntity == null) { // resultMap.put("code", 404); // resultMap.put("msg", "标注不存在或已删除"); // resultMap.put("data", null); // return new ResponseEntity<>(resultMap, HttpStatus.NOT_FOUND); // } // // // 3. 实体转DTO(反序列化坐标数据) // AnnotationDTO annotationDTO = AnnotationDTO.builder().build(); // org.springframework.beans.BeanUtils.copyProperties(annotationEntity, annotationDTO); // try { // annotationDTO.setCoordinateInfo(JsonSerializeUtil.deserialize(annotationEntity.getCoordinateInfo())); // } catch (Exception e) { // log.error("反序列化标注坐标失败,标注ID:{}", id, e); // annotationDTO.setCoordinateInfo(null); // } // // // 4. 统一返回格式 // resultMap.put("code", 200); // resultMap.put("msg", "查询成功"); // resultMap.put("data", annotationDTO); // return new ResponseEntity<>(resultMap, HttpStatus.OK); // // } catch (Exception e) { // // 全局异常捕获 // log.error("按ID查询标注详情异常,标注ID:{}", id, e); // resultMap.put("code", 500); // resultMap.put("msg", "查询失败:" + e.getMessage()); // resultMap.put("data", null); // return new ResponseEntity<>(resultMap, HttpStatus.INTERNAL_SERVER_ERROR); // } // } } ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDTO.java
@@ -1,6 +1,7 @@ package com.ard.sample.annotation.model.dto; import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -12,6 +13,7 @@ @AllArgsConstructor @NoArgsConstructor @Builder @JsonInclude(JsonInclude.Include.NON_NULL) public class AnnotationDTO { private String id; @@ -40,4 +42,16 @@ private Long createTime; private Long updateTime; // 线相关 private Double lineHorizontalDistance; // 线的水平距离(米) private Double lineStraightDistance; // 线的直线距离(米) // 面相关 private Double polygonHorizontalArea; // 面的水平面积(平方米) private Double polygonHorizontalPerimeter; // 面的水平周长(米) // 圆相关 private Double circleHorizontalArea; // 圆的水平面积(平方米) private Double circleHorizontalPerimeter; // 圆的水平周长(米) } ard-sample/src/main/java/com/ard/sample/annotation/util/GeoCalculator.java
New file @@ -0,0 +1,203 @@ package com.ard.sample.annotation.util; import lombok.extern.slf4j.Slf4j; import java.util.List; /** * 地理坐标计算工具类(WGS84坐标系) * 支持: * 1. 线:水平距离(累加)、直线距离(起点→终点) * 2. 面:水平面积、水平周长 * 3. 圆:水平面积(πr²)、水平周长(2πr) */ @Slf4j public class GeoCalculator { // WGS84椭球参数 private static final double WGS84_A = 6378137.0; // 地球长半轴(米) private static final double RAD = Math.PI / 180.0; // 角度转弧度 private static final double PI = Math.PI; // 圆周率 // ====================== 通用工具方法 ====================== /** * 保留两位小数(四舍五入) */ public static double keepTwoDecimal(double value) { return Math.round(value * 100.0) / 100.0; } /** * 计算两点间的水平距离(米)- 经纬度投影到水平面 */ private static double calculateTwoPointHorizontalDistance(Coordinate p1, Coordinate p2) { if (p1 == null || p2 == null) return 0.0; // 转换为弧度 double lon1 = p1.getLongitude() * RAD; double lat1 = p1.getLatitude() * RAD; double lon2 = p2.getLongitude() * RAD; double lat2 = p2.getLatitude() * RAD; // 计算纬度对应的地球切面半径 double R = WGS84_A * Math.cos(lat1); // 经度差对应的水平距离 double dx = R * (lon2 - lon1); // 纬度差对应的水平距离 double dy = WGS84_A * (lat2 - lat1); // 水平直线距离(勾股定理) return Math.sqrt(dx * dx + dy * dy); } /** * 计算两点间的直线地理距离(米)- Haversine公式 */ private static double calculateTwoPointStraightDistance(Coordinate p1, Coordinate p2) { if (p1 == null || p2 == null) return 0.0; double lon1 = p1.getLongitude() * RAD; double lat1 = p1.getLatitude() * RAD; double lon2 = p2.getLongitude() * RAD; double lat2 = p2.getLatitude() * RAD; double deltaLat = lat2 - lat1; double deltaLon = lon2 - lon1; double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return WGS84_A * c; } // ====================== 线(折线)计算 ====================== /** * 计算折线的水平距离(累加,米) */ public static double calculatePolylineHorizontalDistance(List<Coordinate> points) { if (points == null || points.size() < 2) { log.warn("折线水平距离计算失败:顶点数不足2个"); return 0.0; } double totalHorizontal = 0.0; for (int i = 0; i < points.size() - 1; i++) { totalHorizontal += calculateTwoPointHorizontalDistance(points.get(i), points.get(i + 1)); } return keepTwoDecimal(totalHorizontal); } /** * 计算折线的直线距离(起点→终点,米) */ public static double calculatePolylineStraightDistance(List<Coordinate> points) { if (points == null || points.size() < 2) { log.warn("折线直线距离计算失败:顶点数不足2个"); return 0.0; } Coordinate start = points.get(0); Coordinate end = points.get(points.size() - 1); return keepTwoDecimal(calculateTwoPointStraightDistance(start, end)); } // ====================== 面(多边形)计算 ====================== /** * 计算多边形的水平面积(平方米) */ public static double calculatePolygonHorizontalArea(List<Coordinate> points) { if (points == null || points.size() < 3) { log.warn("多边形水平面积计算失败:顶点数不足3个"); return 0.0; } // 闭合多边形 List<Coordinate> closedPoints = new java.util.ArrayList<>(points); Coordinate first = closedPoints.get(0); Coordinate last = closedPoints.get(closedPoints.size() - 1); if (!isCoordinateEqual(first, last)) { closedPoints.add(first); } double area = 0.0; int n = closedPoints.size() - 1; for (int i = 0; i < n; i++) { Coordinate p1 = closedPoints.get(i); Coordinate p2 = closedPoints.get(i + 1); // 水平投影面积公式(高斯平面投影) area += (p1.getLongitude() * p2.getLatitude() - p2.getLongitude() * p1.getLatitude()); } // 转换为实际面积(修正系数) area = Math.abs(area) * 0.5 * WGS84_A * WGS84_A * RAD * RAD; return keepTwoDecimal(area); } /** * 计算多边形的水平周长(米) */ public static double calculatePolygonHorizontalPerimeter(List<Coordinate> points) { if (points == null || points.size() < 3) { log.warn("多边形水平周长计算失败:顶点数不足3个"); return 0.0; } // 闭合多边形 List<Coordinate> closedPoints = new java.util.ArrayList<>(points); Coordinate first = closedPoints.get(0); Coordinate last = closedPoints.get(closedPoints.size() - 1); if (!isCoordinateEqual(first, last)) { closedPoints.add(first); } double perimeter = 0.0; int n = closedPoints.size() - 1; for (int i = 0; i < n; i++) { perimeter += calculateTwoPointHorizontalDistance(closedPoints.get(i), closedPoints.get(i + 1)); } return keepTwoDecimal(perimeter); } // ====================== 圆计算 ====================== /** * 计算圆的水平面积(平方米)- π×r² */ public static double calculateCircleHorizontalArea(double radius) { if (radius <= 0) { log.warn("圆形水平面积计算失败:半径非法({}米)", radius); return 0.0; } return keepTwoDecimal(PI * radius * radius); } /** * 计算圆的水平周长(米)- 2πr */ public static double calculateCircleHorizontalPerimeter(double radius) { if (radius <= 0) { log.warn("圆形水平周长计算失败:半径非法({}米)", radius); return 0.0; } return keepTwoDecimal(2 * PI * radius); } // ====================== 辅助方法 ====================== /** * 判断两个坐标是否相等(容错1e-6) */ private static boolean isCoordinateEqual(Coordinate p1, Coordinate p2) { if (p1 == null || p2 == null) return false; double lonDiff = Math.abs(p1.getLongitude() - p2.getLongitude()); double latDiff = Math.abs(p1.getLatitude() - p2.getLatitude()); return lonDiff < 1e-6 && latDiff < 1e-6; } // ====================== 坐标实体类 ====================== public static class Coordinate { private double longitude; // 经度 private double latitude; // 纬度 public Coordinate() {} public Coordinate(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } // Getter & Setter public double getLongitude() { return longitude; } public void setLongitude(double longitude) { this.longitude = longitude; } public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } } }