2026-03-09 优化标注功能
增加GeoCalculator计算工具类,算出面、圆的水平面积和水平周长,线的水平距离和直线距离。在调取按标注id获取标注详细信息接口给出,不入库。
1 files added
2 files modified
337 ■■■■■ changed files
ard-admin/src/main/java/com/ard/web/controller/sample/AnnotationController.java 120 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/annotation/model/dto/AnnotationDTO.java 14 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/annotation/util/GeoCalculator.java 203 ●●●●● 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; }
    }
}