| | |
| | | private JButton prevButton; |
| | | private JButton nextButton; |
| | | private JButton createButton; |
| | | private JButton previewButton; |
| | | private JButton previewButton; // 步骤3的预览按钮(预览割草路径) |
| | | private JButton boundaryPreviewButton; // 步骤2的预览按钮(预览边界) |
| | | private Component previewButtonSpacer; |
| | | private JLabel boundaryCountLabel; |
| | | private JTextArea boundaryXYTextArea; // 显示边界XY坐标的文本域 |
| | |
| | | )); |
| | | areaNameField.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | // 添加输入框焦点效果 |
| | | // 添加输入框焦点效果和文本变化监听 |
| | | areaNameField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(12, 15, 12, 15) |
| | | )); |
| | | // 更新下一步按钮状态 |
| | | updateStep1ButtonState(); |
| | | } |
| | | }); |
| | | |
| | | // 添加文本变化监听,实时更新按钮状态 |
| | | areaNameField.addKeyListener(new KeyAdapter() { |
| | | @Override |
| | | public void keyReleased(KeyEvent e) { |
| | | updateStep1ButtonState(); |
| | | } |
| | | }); |
| | | |
| | |
| | | startEndDrawingBtn.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | startEndDrawingBtn.setMaximumSize(new Dimension(400, 55)); |
| | | startEndDrawingBtn.setEnabled(false); // 初始不可用 |
| | | startEndDrawingBtn.setBackground(MEDIUM_GRAY); // 初始灰色背景 |
| | | |
| | | startEndDrawingBtn.addActionListener(e -> toggleDrawing()); |
| | | |
| | |
| | | return; |
| | | } |
| | | if (selectDrawingOption(optionPanel, type, true)) { |
| | | startEndDrawingBtn.setEnabled(true); // 选择后启用按钮 |
| | | updateStartDrawingButtonState(); // 选择后更新按钮状态 |
| | | } |
| | | } |
| | | |
| | |
| | | JOptionPane.showMessageDialog(this, "边界绘制已完成", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | showBoundaryPointSummary(); |
| | | updateBoundaryXYDisplay(); |
| | | // 更新预览和下一步按钮状态(背景颜色变绿色,可点击) |
| | | updateStep2ButtonsAfterDrawing(); |
| | | } |
| | | } |
| | | |
| | |
| | | JOptionPane.showMessageDialog(this, "无法启动预览,请稍后再试", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 在步骤3预览时,不显示边界点圆圈 |
| | | if (shouye.getMapRenderer() != null) { |
| | | shouye.getMapRenderer().setBoundaryPointsVisible(false); |
| | | } |
| | | |
| | | closePreviewAndDispose(); |
| | | } |
| | |
| | | @Override |
| | | public void mouseEntered(MouseEvent e) { |
| | | if (button.isEnabled()) { |
| | | button.setBackground(PRIMARY_DARK); |
| | | // 如果按钮可用,鼠标悬停时显示深绿色 |
| | | if (button.getBackground().equals(PRIMARY_COLOR)) { |
| | | button.setBackground(PRIMARY_DARK); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void mouseExited(MouseEvent e) { |
| | | if (button.isEnabled()) { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | // 如果按钮可用,鼠标离开时恢复绿色 |
| | | if (!button.getBackground().equals(MEDIUM_GRAY)) { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | } |
| | | } else { |
| | | // 如果按钮不可用,保持灰色 |
| | | button.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | }); |
| | |
| | | )); |
| | | prevButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | |
| | | boundaryPreviewButton = createPrimaryButton("预览", 16); |
| | | boundaryPreviewButton.setVisible(false); |
| | | boundaryPreviewButton.setEnabled(false); |
| | | boundaryPreviewButton.addActionListener(e -> previewBoundary()); |
| | | |
| | | nextButton = createPrimaryButton("下一步", 16); |
| | | nextButton.setBackground(MEDIUM_GRAY); // 初始灰色背景 |
| | | nextButton.setEnabled(false); // 初始不可用 |
| | | |
| | | createButton = createPrimaryButton("保存", 16); |
| | | createButton.setVisible(false); |
| | | createButton.setEnabled(false); |
| | |
| | | |
| | | buttonPanel.add(prevButton); |
| | | buttonPanel.add(Box.createHorizontalGlue()); |
| | | buttonPanel.add(boundaryPreviewButton); |
| | | buttonPanel.add(Box.createHorizontalStrut(15)); |
| | | buttonPanel.add(nextButton); |
| | | buttonPanel.add(previewButtonSpacer); |
| | | buttonPanel.add(previewButton); |
| | |
| | | dikuai.setLandArea(snapshot.areaSqMeters); |
| | | dikuai.setBaseStationCoordinates(snapshot.baseStationCoordinates); |
| | | dikuai.setUpdateTime(getCurrentTime()); |
| | | // 计算并设置原始边界XY坐标 |
| | | String originalBoundaryXY = convertOriginalBoundaryToXY(snapshot.originalBoundary, snapshot.baseStationCoordinates); |
| | | if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) { |
| | | dikuai.setBoundaryOriginalXY(originalBoundaryXY); |
| | | } |
| | | Dikuai.putDikuai(landNumber, dikuai); |
| | | } |
| | | |
| | |
| | | } |
| | | return dikuai; |
| | | } |
| | | |
| | | /** |
| | | * 将原始边界坐标(经纬度格式)转换为XY坐标 |
| | | * @param originalBoundary 原始边界坐标字符串,格式:"lat1,lon1,alt1;lat2,lon2,alt2;..." |
| | | * @param baseStationCoordinates 基准站坐标,格式:"lat,N/S,lon,E/W" |
| | | * @return XY坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." 如果转换失败返回null |
| | | */ |
| | | private static String convertOriginalBoundaryToXY(String originalBoundary, String baseStationCoordinates) { |
| | | if (originalBoundary == null || originalBoundary.trim().isEmpty() || "-1".equals(originalBoundary.trim())) { |
| | | return null; |
| | | } |
| | | if (baseStationCoordinates == null || baseStationCoordinates.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | // 解析基准站坐标 |
| | | String[] baseParts = baseStationCoordinates.trim().split(","); |
| | | if (baseParts.length != 4) { |
| | | return null; |
| | | } |
| | | double baseLat = convertToDecimalDegree(baseParts[0], baseParts[1]); |
| | | double baseLon = convertToDecimalDegree(baseParts[2], baseParts[3]); |
| | | |
| | | // 解析原始边界坐标 |
| | | String[] points = originalBoundary.split(";"); |
| | | StringBuilder xyStr = new StringBuilder(); |
| | | |
| | | for (int i = 0; i < points.length; i++) { |
| | | String point = points[i].trim(); |
| | | if (point.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | String[] coords = point.split(","); |
| | | if (coords.length >= 2) { |
| | | try { |
| | | double lat = Double.parseDouble(coords[0].trim()); |
| | | double lon = Double.parseDouble(coords[1].trim()); |
| | | |
| | | // 转换为XY坐标 |
| | | double[] xy = publicway.Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon); |
| | | if (xy != null && xy.length >= 2) { |
| | | if (xyStr.length() > 0) { |
| | | xyStr.append(";"); |
| | | } |
| | | xyStr.append(String.format(Locale.US, "%.3f,%.3f", xy[0], xy[1])); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 跳过无效的坐标点 |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return xyStr.length() > 0 ? xyStr.toString() : null; |
| | | } catch (Exception e) { |
| | | System.err.println("转换原始边界坐标到XY失败: " + e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 预览边界 |
| | | */ |
| | | private void previewBoundary() { |
| | | if (!dikuaiData.containsKey("boundaryDrawn")) { |
| | | JOptionPane.showMessageDialog(this, "请先完成边界绘制后再预览", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 获取或创建地块对象 |
| | | String landNumber = getPendingLandNumber(); |
| | | Dikuai dikuai = getOrCreatePendingDikuai(); |
| | | if (dikuai == null) { |
| | | JOptionPane.showMessageDialog(this, "无法获取地块信息", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 确保地块数据是最新的 |
| | | String optimizedBoundaryXY = dikuaiData.get("optimizedBoundaryXY"); |
| | | if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || optimizedBoundaryXY.startsWith("ERROR")) { |
| | | // 如果没有优化后的边界,尝试从boundaryCoordinates获取 |
| | | String boundaryCoords = dikuaiData.get("boundaryCoordinates"); |
| | | if (boundaryCoords != null && !boundaryCoords.isEmpty() && !"-1".equals(boundaryCoords)) { |
| | | optimizedBoundaryXY = boundaryCoords; |
| | | } else { |
| | | // 尝试从地块对象获取 |
| | | optimizedBoundaryXY = dikuai.getBoundaryCoordinates(); |
| | | } |
| | | } |
| | | |
| | | if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || "-1".equals(optimizedBoundaryXY)) { |
| | | JOptionPane.showMessageDialog(this, "未找到有效的边界坐标,无法预览", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 确保原始边界XY坐标已计算 |
| | | String originalBoundaryXY = dikuai.getBoundaryOriginalXY(); |
| | | if (originalBoundaryXY == null || originalBoundaryXY.isEmpty() || "-1".equals(originalBoundaryXY)) { |
| | | // 计算原始边界XY坐标 |
| | | String originalBoundary = dikuaiData.get("boundaryOriginalCoordinates"); |
| | | String baseStationCoordinates = dikuaiData.get("baseStationCoordinates"); |
| | | if (originalBoundary != null && baseStationCoordinates != null) { |
| | | originalBoundaryXY = convertOriginalBoundaryToXY(originalBoundary, baseStationCoordinates); |
| | | if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) { |
| | | dikuai.setBoundaryOriginalXY(originalBoundaryXY); |
| | | Dikuai.putDikuai(landNumber, dikuai); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存会话快照 |
| | | captureSessionSnapshot(); |
| | | |
| | | // 创建final变量供lambda使用 |
| | | final String finalOptimizedBoundaryXY = optimizedBoundaryXY; |
| | | final Dikuai finalDikuai = dikuai; |
| | | |
| | | // 关闭对话框 |
| | | setVisible(false); |
| | | dispose(); |
| | | |
| | | // 调用首页显示预览(与边界管理页面逻辑一致) |
| | | SwingUtilities.invokeLater(() -> { |
| | | Shouye.showBoundaryPreview(finalDikuai, finalOptimizedBoundaryXY, () -> { |
| | | // 返回回调:重新打开新增地块对话框,并显示步骤2 |
| | | Component parent = Shouye.getInstance(); |
| | | if (parent != null) { |
| | | // 确保会话状态正确,以便返回时显示步骤2 |
| | | if (activeSession != null && activeSession.drawingCompleted) { |
| | | resumeRequested = true; |
| | | } |
| | | showAddDikuaiDialog(parent); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | private void hideBoundaryPointSummary() { |
| | | if (boundaryCountLabel != null) { |
| | |
| | | |
| | | if (step == 1) { |
| | | updateObstacleSummary(); |
| | | // 步骤1显示时,立即更新按钮状态 |
| | | SwingUtilities.invokeLater(() -> updateStep1ButtonState()); |
| | | } |
| | | |
| | | // 更新按钮状态 |
| | |
| | | if (previewButtonSpacer != null) { |
| | | previewButtonSpacer.setVisible(false); |
| | | } |
| | | // 步骤1:根据验证结果更新下一步按钮状态 |
| | | if (step == 1) { |
| | | updateStep1ButtonState(); |
| | | } |
| | | // 步骤2显示边界预览按钮 |
| | | if (step == 2) { |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setVisible(true); |
| | | // 根据是否完成边界绘制来设置按钮状态和背景颜色 |
| | | boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn"); |
| | | boundaryPreviewButton.setEnabled(boundaryDrawn); |
| | | if (boundaryDrawn) { |
| | | boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } else { |
| | | boundaryPreviewButton.setBackground(MEDIUM_GRAY); // 灰色背景 |
| | | } |
| | | } |
| | | // 更新下一步按钮状态(根据是否完成边界绘制) |
| | | boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn"); |
| | | nextButton.setEnabled(boundaryDrawn); |
| | | if (boundaryDrawn) { |
| | | nextButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } else { |
| | | nextButton.setBackground(MEDIUM_GRAY); // 灰色背景 |
| | | } |
| | | // 更新开始绘制按钮状态 |
| | | updateStartDrawingButtonState(); |
| | | } else { |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setVisible(false); |
| | | boundaryPreviewButton.setEnabled(false); |
| | | } |
| | | } |
| | | } else { |
| | | nextButton.setVisible(false); |
| | | createButton.setVisible(true); |
| | |
| | | if (previewButtonSpacer != null) { |
| | | previewButtonSpacer.setVisible(true); |
| | | } |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setVisible(false); |
| | | boundaryPreviewButton.setEnabled(false); |
| | | } |
| | | setPathAvailability(hasGeneratedPath()); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新步骤1的下一步按钮状态 |
| | | * 根据地块名称是否填写来设置按钮的启用状态和背景颜色 |
| | | */ |
| | | private void updateStep1ButtonState() { |
| | | if (nextButton == null || currentStep != 1) { |
| | | return; |
| | | } |
| | | |
| | | String name = areaNameField.getText().trim(); |
| | | boolean canProceed = !name.isEmpty(); |
| | | |
| | | nextButton.setEnabled(canProceed); |
| | | if (canProceed) { |
| | | // 可点击时:绿色背景 |
| | | nextButton.setBackground(PRIMARY_COLOR); |
| | | } else { |
| | | // 不可点击时:灰色背景 |
| | | nextButton.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新步骤2的开始绘制按钮状态 |
| | | * 根据是否选择了绘制方式来设置按钮的启用状态和背景颜色 |
| | | */ |
| | | private void updateStartDrawingButtonState() { |
| | | if (startEndDrawingBtn == null || currentStep != 2) { |
| | | return; |
| | | } |
| | | |
| | | boolean hasSelectedMethod = dikuaiData.containsKey("drawingMethod"); |
| | | boolean isDrawingActive = isDrawing; |
| | | |
| | | // 如果正在绘制,按钮状态由toggleDrawing方法控制 |
| | | if (isDrawingActive) { |
| | | return; |
| | | } |
| | | |
| | | // 如果已经完成绘制,按钮显示"已完成"且不可用 |
| | | boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn"); |
| | | if (boundaryDrawn) { |
| | | startEndDrawingBtn.setEnabled(false); |
| | | startEndDrawingBtn.setBackground(MEDIUM_GRAY); |
| | | return; |
| | | } |
| | | |
| | | startEndDrawingBtn.setEnabled(hasSelectedMethod); |
| | | if (hasSelectedMethod) { |
| | | // 已选择绘制方式:绿色背景,可点击 |
| | | startEndDrawingBtn.setBackground(PRIMARY_COLOR); |
| | | } else { |
| | | // 未选择绘制方式:灰色背景,不可点击 |
| | | startEndDrawingBtn.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新步骤2的预览和下一步按钮状态(在完成边界绘制后调用) |
| | | * 将按钮背景颜色设置为绿色,表示可以点击操作 |
| | | */ |
| | | private void updateStep2ButtonsAfterDrawing() { |
| | | if (currentStep != 2) { |
| | | return; |
| | | } |
| | | |
| | | // 更新预览按钮 |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setEnabled(true); |
| | | boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } |
| | | |
| | | // 更新下一步按钮 |
| | | if (nextButton != null) { |
| | | nextButton.setEnabled(true); |
| | | nextButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } |
| | | } |
| | | |
| | | private boolean validateCurrentStep() { |
| | | switch (currentStep) { |
| | | case 1: |
| | |
| | | showStep(2); |
| | | showBoundaryPointSummary(); |
| | | updateBoundaryXYDisplay(); |
| | | // 更新预览和下一步按钮状态(背景颜色变绿色,可点击) |
| | | updateStep2ButtonsAfterDrawing(); |
| | | } else { |
| | | if (startEndDrawingBtn != null) { |
| | | startEndDrawingBtn.setText("开始绘制"); |
| | |
| | | if (dikuaiData.containsKey("mowingPattern")) { |
| | | dikuai.setMowingPattern(dikuaiData.get("mowingPattern")); |
| | | } |
| | | if (dikuaiData.containsKey("mowingWidth")) { |
| | | |
| | | // 保存割草宽度(从文本框获取,单位:米,转换为厘米保存) |
| | | if (mowingWidthField != null) { |
| | | String mowingWidthStr = mowingWidthField.getText().trim(); |
| | | if (mowingWidthStr != null && !mowingWidthStr.isEmpty()) { |
| | | try { |
| | | double mowingWidthMeters = Double.parseDouble(mowingWidthStr); |
| | | // 转换为厘米保存 |
| | | double mowingWidthCm = mowingWidthMeters * 100.0; |
| | | dikuai.setMowingWidth(String.format(Locale.US, "%.2f", mowingWidthCm)); |
| | | } catch (NumberFormatException e) { |
| | | // 如果解析失败,尝试使用dikuaiData中的值 |
| | | if (dikuaiData.containsKey("mowingWidth")) { |
| | | dikuai.setMowingWidth(dikuaiData.get("mowingWidth")); |
| | | } |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingWidth")) { |
| | | dikuai.setMowingWidth(dikuaiData.get("mowingWidth")); |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingWidth")) { |
| | | dikuai.setMowingWidth(dikuaiData.get("mowingWidth")); |
| | | } |
| | | |
| | | // 保存割草机割刀宽度(从文本框获取,单位:米) |
| | | if (bladeWidthField != null) { |
| | | String bladeWidthStr = bladeWidthField.getText().trim(); |
| | | if (bladeWidthStr != null && !bladeWidthStr.isEmpty()) { |
| | | try { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthStr); |
| | | // 保存为米,保留2位小数 |
| | | dikuai.setMowingBladeWidth(String.format(Locale.US, "%.2f", bladeWidthMeters)); |
| | | } catch (NumberFormatException e) { |
| | | // 解析失败时,保存原始字符串 |
| | | dikuai.setMowingBladeWidth(bladeWidthStr); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存割草安全距离(从文本框获取,单位:米) |
| | | if (safetyDistanceField != null) { |
| | | String safetyDistanceStr = safetyDistanceField.getText().trim(); |
| | | if (safetyDistanceStr != null && !safetyDistanceStr.isEmpty()) { |
| | | try { |
| | | double safetyDistanceMeters = Double.parseDouble(safetyDistanceStr); |
| | | // 保存为米,保留2位小数 |
| | | String formattedValue = String.format(Locale.US, "%.2f", safetyDistanceMeters); |
| | | dikuai.setMowingSafetyDistance(formattedValue); |
| | | // 同时保存到dikuaiData中,以便后续使用 |
| | | dikuaiData.put("mowingSafetyDistance", formattedValue); |
| | | } catch (NumberFormatException e) { |
| | | // 解析失败时,保存原始字符串 |
| | | dikuai.setMowingSafetyDistance(safetyDistanceStr); |
| | | dikuaiData.put("mowingSafetyDistance", safetyDistanceStr); |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingSafetyDistance")) { |
| | | // 如果文本框为空,尝试从dikuaiData获取 |
| | | dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance")); |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingSafetyDistance")) { |
| | | // 如果safetyDistanceField为null,从dikuaiData获取 |
| | | dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance")); |
| | | } |
| | | |
| | | // 保存割草路径坐标 |
| | | String plannedPath = dikuaiData.get("plannedPath"); |
| | | if (isMeaningfulValue(plannedPath)) { |
| | | dikuai.setPlannedPath(plannedPath); |
| | |
| | | if (resumeRequested && activeSession != null) { |
| | | dialog.applySessionData(activeSession); |
| | | resumeRequested = false; |
| | | // 如果会话已生成路径,优先显示步骤3 |
| | | if (activeSession.data != null && isMeaningfulValue(activeSession.data.get("plannedPath"))) { |
| | | dialog.showStep(3); |
| | | } else if (activeSession.drawingCompleted) { |
| | | // 如果会话已完成绘制,显示步骤2 |
| | | dialog.showStep(2); |
| | | } |
| | | } |
| | | |
| | | dialog.setVisible(true); |