张世豪
3 天以前 55d23efc30f7db5ec2d9b8a0f04a268a10f3e855
src/dikuai/addzhangaiwu.java
@@ -46,20 +46,20 @@
import baseStation.BaseStation;
import gecaoji.Device;
import publicway.buttonset;
import set.Setsys;
import ui.UIConfig;
import zhuye.Coordinate;
import zhuye.MapRenderer;
import zhuye.Shouye;
import zhangaiwu.AddDikuai;
import zhangaiwu.Obstacledge;
import zhangaiwu.yulanzhangaiwu;
import zhuye.buttonset;
import bianjie.bianjieguihua2;
import bianjie.Bianjieyouhuatoxy;
/**
 * 障碍物新增/编辑对话框。设计语言参考 {@link AddDikuai},支持通过实地绘制采集障碍物坐标。
 */
import publicway.Gpstoxuzuobiao;
public class addzhangaiwu extends JDialog {
    private static final long serialVersionUID = 1L;
    private static final double METERS_PER_DEGREE_LAT = 111320.0d;
@@ -796,7 +796,7 @@
            return;
        }
        if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) {
            JOptionPane.showMessageDialog(this, "请先添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE);
            JOptionPane.showMessageDialog(this, "请先去系统设置添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        if (selectedMethodPanel != null && selectedMethodPanel != option) {
@@ -921,6 +921,12 @@
            boundaryStatusLabel.setVisible(false);
            boundaryStatusLabel.setText("");
        }
        // 对于圆形障碍物,隐藏生成边界按钮
        String shapeKey = formData.get("obstacleShape");
        boolean isCircle = "circle".equals(shapeKey);
        if (isCircle && generateBoundaryButton != null) {
            generateBoundaryButton.setVisible(false);
        }
        String method = formData.get("drawingMethod");
        if (!isMeaningfulValue(method)) {
@@ -1095,14 +1101,11 @@
                session.captureMessage = "无法根据采集的点生成圆,请确保选择了三个非共线的圆周点";
                return;
            }
            double[] radiusPoint = pickRadiusPoint(xyPoints, circle);
            if (radiusPoint == null) {
                session.captureSuccessful = false;
                session.captureMessage = "采集的圆周点异常,无法生成圆";
                return;
            }
            // 使用计算出的半径生成一个真正的圆上点(圆心右侧的点),确保预览时计算的半径和结束绘制时的半径一致
            double radiusX = circle.centerX + circle.radius;
            double radiusY = circle.centerY;
            String result = String.format(Locale.US, "%.2f,%.2f;%.2f,%.2f",
                    circle.centerX, circle.centerY, radiusPoint[0], radiusPoint[1]);
                    circle.centerX, circle.centerY, radiusX, radiusY);
            session.data.put("obstacleCoordinates", result);
            session.captureSuccessful = true;
            session.captureMessage = "已采集圆形障碍物,共 " + xyPoints.size() + " 个点";
@@ -1213,34 +1216,11 @@
    }
    private static double parseDMToDecimal(String dmm, String direction) {
        if (dmm == null || dmm.trim().isEmpty()) {
            return Double.NaN;
        }
        try {
            String trimmed = dmm.trim();
            int dotIndex = trimmed.indexOf('.');
            if (dotIndex < 2) {
                return Double.NaN;
            }
            int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
            double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
            double decimal = degrees + minutes / 60.0;
            if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
                decimal = -decimal;
            }
            return decimal;
        } catch (NumberFormatException ex) {
            return Double.NaN;
        }
        return Gpstoxuzuobiao.parseDMToDecimal(dmm, direction);
    }
    private static double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
        double deltaLat = lat - baseLat;
        double deltaLon = lon - baseLon;
        double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
        double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
        double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
        return new double[]{eastMeters, northMeters};
        return Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon);
    }
    private static CircleFitResult fitCircleFromPoints(List<double[]> points) {
@@ -1355,7 +1335,7 @@
        }
        if (!configMap.isEmpty()) {
            List<ExistingObstacle> remaining = new ArrayList<>(configMap.values());
            remaining.sort(Comparator.comparing(ExistingObstacle::getName, String.CASE_INSENSITIVE_ORDER));
            remaining.sort((a, b) -> String.CASE_INSENSITIVE_ORDER.compare(a.getName(), b.getName()));
            result.addAll(remaining);
        }
        if (result.isEmpty()) {
@@ -1436,16 +1416,26 @@
            return;
        }
        String coords = formData.get("obstacleCoordinates");
        String shapeKey = formData.get("obstacleShape");
        boolean isCircle = "circle".equals(shapeKey);
        if (isMeaningfulValue(coords)) {
            int count = countCoordinatePairs(coords);
            drawingStatusLabel.setText("已采集障碍物数据,点数:" + count);
            if (isCircle) {
                // 对于圆形障碍物,显示圆心坐标和半径
                String statusText = parseCircleStatusText(coords);
                drawingStatusLabel.setText(statusText);
            } else {
                // 对于多边形障碍物,显示点数
                int count = countCoordinatePairs(coords);
                drawingStatusLabel.setText("已采集障碍物数据,点数:" + count);
            }
            if (!drawingInProgress && drawButton != null) {
                drawButton.setText("重新绘制");
                drawButton.setEnabled(true);
            }
            // 绘制完成后显示"生成障碍物边界"按钮
            // 对于圆形障碍物,不显示"生成障碍物边界"按钮
            if (generateBoundaryButton != null) {
                generateBoundaryButton.setVisible(true);
                generateBoundaryButton.setVisible(!isCircle);
            }
        } else {
            drawingStatusLabel.setText("尚未采集障碍物坐标");
@@ -1493,10 +1483,20 @@
        if (previewButton == null) {
            return;
        }
        // 对于圆形障碍物,不显示预览按钮
        String shapeKey = formData.get("obstacleShape");
        boolean isCircle = "circle".equals(shapeKey);
        if (isCircle) {
            previewButton.setVisible(false);
            previewButton.setEnabled(false);
            return;
        }
        // 只有在生成边界后才显示预览按钮
        String generatedBoundary = formData.get("generatedBoundaryCoordinates");
        boolean hasGeneratedBoundary = isMeaningfulValue(generatedBoundary);
        if (hasGeneratedBoundary) {
            // 生成边界后,重新创建绿色的预览按钮
            // 获取按钮的父容器和位置
@@ -1572,6 +1572,49 @@
        }
        return count;
    }
    /**
     * 解析圆形障碍物坐标,生成状态文本
     * 格式:centerX,centerY;radiusX,radiusY
     * 返回:当前采集圆形圆心坐标x,y,半径n米
     */
    private String parseCircleStatusText(String coords) {
        if (!isMeaningfulValue(coords)) {
            return "尚未采集圆形障碍物坐标";
        }
        try {
            String[] pairs = coords.split(";");
            if (pairs.length < 2) {
                return "圆形障碍物坐标格式错误";
            }
            // 解析圆心坐标
            String[] centerParts = pairs[0].trim().split(",");
            if (centerParts.length < 2) {
                return "圆形障碍物坐标格式错误";
            }
            double centerX = Double.parseDouble(centerParts[0].trim());
            double centerY = Double.parseDouble(centerParts[1].trim());
            // 解析圆上一点坐标
            String[] radiusParts = pairs[1].trim().split(",");
            if (radiusParts.length < 2) {
                return "圆形障碍物坐标格式错误";
            }
            double radiusX = Double.parseDouble(radiusParts[0].trim());
            double radiusY = Double.parseDouble(radiusParts[1].trim());
            // 计算半径(米)
            double radius = Math.sqrt(Math.pow(radiusX - centerX, 2) + Math.pow(radiusY - centerY, 2));
            // 格式化显示:当前采集圆形圆心坐标x,y,半径n米
            return String.format(Locale.US, "当前采集圆形圆心坐标%.2f,%.2f,半径%.2f米",
                    centerX, centerY, radius);
        } catch (Exception e) {
            return "圆形障碍物坐标解析失败";
        }
    }
    private void updateSaveButtonState() {
        if (saveButton != null) {
@@ -1651,8 +1694,16 @@
        String coords = coordsValue.trim();
        String originalCoords = isMeaningfulValue(originalValue) ? originalValue.trim() : null;
        // 如果有生成的边界坐标,使用生成的边界坐标;否则使用原始坐标
        String finalCoords = isMeaningfulValue(generatedBoundaryValue) ? generatedBoundaryValue.trim() : coords;
        // 对于圆形障碍物,直接使用obstacleCoordinates(在第3个点确认时已生成)
        // 对于多边形障碍物,如果有生成的边界坐标,使用生成的边界坐标;否则使用原始坐标
        String finalCoords;
        if ("circle".equals(shapeKey)) {
            // 圆形障碍物直接使用已生成的坐标
            finalCoords = coords;
        } else {
            // 多边形障碍物优先使用生成的边界坐标
            finalCoords = isMeaningfulValue(generatedBoundaryValue) ? generatedBoundaryValue.trim() : coords;
        }
        String trimmedName = isMeaningfulValue(obstacleName) ? obstacleName.trim() : null;
        if (!isMeaningfulValue(trimmedName)) {
            JOptionPane.showMessageDialog(this, "障碍物名称无效", "错误", JOptionPane.ERROR_MESSAGE);
@@ -1694,14 +1745,128 @@
        }
        
        String shapeKey = formData.get("obstacleShape");
        if (!"polygon".equals(shapeKey)) {
            JOptionPane.showMessageDialog(this, "只有多边形障碍物才需要生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE);
            return;
        }
        
        try {
            // 检查绘制方式,只有割草机绘制的多边形才调用bianjieguihua2
            String method = formData.get("drawingMethod");
            // 处理圆形障碍物
            if ("circle".equals(shapeKey)) {
                if (!"mower".equals(method)) {
                    JOptionPane.showMessageDialog(this, "只有割草机绘制的圆形障碍物才支持生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE);
                    return;
                }
                // 将原始坐标转换为Coordinate对象列表
                List<Coordinate> coordinateList = parseOriginalCoordinatesToCoordinateList(originalCoords);
                if (coordinateList.size() < 3) {
                    JOptionPane.showMessageDialog(this, "圆形障碍物至少需要三个采集点", "错误", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                // 解析基站坐标
                String[] baseParts = baseStation.split(",");
                if (baseParts.length < 4) {
                    JOptionPane.showMessageDialog(this, "基站坐标格式无效", "错误", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                double baseLat = parseDMToDecimal(baseParts[0].trim(), baseParts[1].trim());
                double baseLon = parseDMToDecimal(baseParts[2].trim(), baseParts[3].trim());
                // 将原始坐标转换为XY坐标(本地坐标系)
                List<double[]> xyPoints = new ArrayList<>();
                for (Coordinate coord : coordinateList) {
                    double lat = parseDMToDecimal(coord.getLatitude(), coord.getLatDirection());
                    double lon = parseDMToDecimal(coord.getLongitude(), coord.getLonDirection());
                    xyPoints.add(convertLatLonToLocal(lat, lon, baseLat, baseLon));
                }
                // 使用ThreePointCircle算法计算圆心和半径
                if (xyPoints.size() < 3) {
                    JOptionPane.showMessageDialog(this, "至少需要三个点才能生成圆形边界", "错误", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                // 取前三个点计算圆
                double[] p1 = xyPoints.get(0);
                double[] p2 = xyPoints.get(1);
                double[] p3 = xyPoints.get(2);
                String point1 = String.format(Locale.US, "%.2f,%.2f", p1[0], p1[1]);
                String point2 = String.format(Locale.US, "%.2f,%.2f", p2[0], p2[1]);
                String point3 = String.format(Locale.US, "%.2f,%.2f", p3[0], p3[1]);
                String circleResult = bianjie.ThreePointCircle.getCircleFromPoints(point1, point2, point3);
                // 解析结果:格式为 "圆心: x,y; 半径: r"
                if (circleResult == null || circleResult.startsWith("错误")) {
                    JOptionPane.showMessageDialog(this, "生成圆形边界失败: " + circleResult, "错误", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                // 解析圆心和半径
                String[] parts = circleResult.split(";");
                if (parts.length < 2) {
                    JOptionPane.showMessageDialog(this, "解析圆形边界结果失败", "错误", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                // 提取圆心坐标
                String centerPart = parts[0].trim(); // "圆心: x,y"
                String radiusPart = parts[1].trim(); // "半径: r"
                String centerCoords = centerPart.substring(centerPart.indexOf(":") + 1).trim();
                String radiusStr = radiusPart.substring(radiusPart.indexOf(":") + 1).trim();
                String[] centerXY = centerCoords.split(",");
                if (centerXY.length < 2) {
                    JOptionPane.showMessageDialog(this, "解析圆心坐标失败", "错误", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                double centerX = Double.parseDouble(centerXY[0].trim());
                double centerY = Double.parseDouble(centerXY[1].trim());
                double radius = Double.parseDouble(radiusStr.trim());
                // 计算圆上一点(圆心右侧的点)
                double radiusX = centerX + radius;
                double radiusY = centerY;
                // 生成边界坐标格式:圆心X,圆心Y;圆上点X,圆上点Y
                String boundaryCoords = String.format(Locale.US, "%.2f,%.2f;%.2f,%.2f",
                    centerX, centerY, radiusX, radiusY);
                // 保存生成的边界坐标
                formData.put("generatedBoundaryCoordinates", boundaryCoords);
                // 更新边界状态标签显示(圆形只有2个点:圆心和圆上一点)
                updateBoundaryStatusLabel(2);
                // 更新预览按钮显示(变成绿色可点击)
                updatePreviewButtonState();
                // 更新保存按钮状态(变成可点击)
                updateSaveButtonState();
                // 强制刷新UI
                SwingUtilities.invokeLater(() -> {
                    if (previewButton != null) {
                        previewButton.revalidate();
                        previewButton.repaint();
                    }
                });
                JOptionPane.showMessageDialog(this, "圆形障碍物边界坐标已生成", "成功", JOptionPane.INFORMATION_MESSAGE);
                return;
            }
            // 处理多边形障碍物
            if (!"polygon".equals(shapeKey)) {
                JOptionPane.showMessageDialog(this, "只有多边形或圆形障碍物才需要生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE);
                return;
            }
            // 检查绘制方式,只有割草机绘制的多边形才调用bianjieguihua2
            if (!"mower".equals(method)) {
                JOptionPane.showMessageDialog(this, "只有割草机绘制的多边形才支持生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE);
                return;
@@ -1718,14 +1883,13 @@
            List<Coordinate> savedCoordinates = new ArrayList<>(Coordinate.coordinates);
            
            try {
                // 设置到全局坐标列表
                Coordinate.coordinates.clear();
                Coordinate.coordinates.addAll(coordinateList);
                // 构建边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
                String boundaryStr = buildBoundaryStringForOptimization(coordinateList);
                
                // 调用bianjieguihua2算法生成优化后的多边形边界坐标
                String optimizedCoordsStr = bianjieguihua2.processCoordinateListAuto(baseStation);
                // 调用 Bianjieyouhuatoxy.optimizeBoundary 方法生成优化后的多边形边界坐标
                String optimizedCoordsStr = Bianjieyouhuatoxy.optimizeBoundary(baseStation, boundaryStr);
                
                if (optimizedCoordsStr == null || optimizedCoordsStr.trim().isEmpty()) {
                if (optimizedCoordsStr == null || optimizedCoordsStr.trim().isEmpty() || optimizedCoordsStr.startsWith("ERROR")) {
                    JOptionPane.showMessageDialog(this, "生成边界坐标失败", "错误", JOptionPane.ERROR_MESSAGE);
                    return;
                }
@@ -1803,6 +1967,34 @@
    }
    
    /**
     * 构建用于优化的边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
     * 其中lat和lon是度分格式(DMM格式),例如 "3949.89151752"
     */
    private String buildBoundaryStringForOptimization(List<Coordinate> coordinates) {
        if (coordinates == null || coordinates.isEmpty()) {
            return "()";
        }
        StringBuilder sb = new StringBuilder("(");
        java.text.DecimalFormat elevationFormat = new java.text.DecimalFormat("0.00");
        for (int i = 0; i < coordinates.size(); i++) {
            Coordinate coord = coordinates.get(i);
            // Coordinate类中的getLatitude()和getLongitude()已经返回度分格式(DMM格式)
            String latDMM = coord.getLatitude();
            String lonDMM = coord.getLongitude();
            double elevation = coord.getElevation();
            if (i > 0) {
                sb.append(";");
            }
            sb.append(latDMM).append(",")
              .append(lonDMM).append(",")
              .append(elevationFormat.format(elevation));
        }
        sb.append(")");
        return sb.toString();
    }
    /**
     * 预览障碍物边界
     */
    private void previewObstacleBoundary() {
@@ -1816,6 +2008,36 @@
        String landName = targetDikuai.getLandName();
        String boundary = targetDikuai.getBoundaryCoordinates();
        
        // 获取障碍物坐标(优先使用生成的边界坐标,如果没有则使用原始坐标)
        String obstacleCoords = generatedBoundary;
        String shapeKey = formData.get("obstacleShape");
        String obstacleName = formData.get("obstacleName");
        // 对于圆形障碍物,生成的边界坐标格式就是障碍物坐标格式,可以直接使用
        // 对于多边形障碍物,也需要使用生成的边界坐标
        // 如果生成的边界坐标不可用,尝试使用原始障碍物坐标
        if (!isMeaningfulValue(obstacleCoords)) {
            obstacleCoords = formData.get("obstacleCoordinates");
            if (!isMeaningfulValue(obstacleCoords)) {
                JOptionPane.showMessageDialog(this, "无法获取障碍物坐标进行预览", "提示", JOptionPane.INFORMATION_MESSAGE);
                return;
            }
        }
        // 构建障碍物数据字符串,包含名称、形状和坐标
        // 格式:障碍物名称::形状::坐标 或 障碍物名称:形状:坐标
        final String obstacleData;
        if (isMeaningfulValue(obstacleName) && isMeaningfulValue(shapeKey)) {
            // 使用 :: 分隔符格式:名称::形状::坐标
            obstacleData = obstacleName.trim() + "::" + shapeKey.trim() + "::" + obstacleCoords;
        } else if (isMeaningfulValue(shapeKey)) {
            // 只有形状:形状::坐标
            obstacleData = shapeKey.trim() + "::" + obstacleCoords;
        } else {
            // 只有坐标
            obstacleData = obstacleCoords;
        }
        // 关闭当前对话框
        setVisible(false);
        
@@ -1827,7 +2049,7 @@
                    landNumber,
                    landName,
                    boundary,
                    generatedBoundary,  // 使用生成的边界坐标作为障碍物坐标
                    obstacleData,  // 使用包含名称和形状的障碍物数据
                    null,
                    () -> SwingUtilities.invokeLater(() -> {
                        // 重新打开新增障碍物步骤2页面
@@ -2056,6 +2278,17 @@
            saveButton.setVisible(true);
            updateDrawingStatus();
            updatePreviewButtonState();
            // 对于圆形障碍物,确保预览按钮和生成边界按钮隐藏
            String shapeKey = formData.get("obstacleShape");
            boolean isCircle = "circle".equals(shapeKey);
            if (isCircle) {
                if (previewButton != null) {
                    previewButton.setVisible(false);
                }
                if (generateBoundaryButton != null) {
                    generateBoundaryButton.setVisible(false);
                }
            }
        }
        updateSaveButtonState();
        revalidate();