张世豪
2025-12-04 3ad76f98fa8b4a9d3d95207cfb4ae4706087c964
src/zhuye/MapRenderer.java
@@ -52,11 +52,13 @@
    private static final Color HANDHELD_BOUNDARY_BORDER = new Color(51, 102, 204, 220);
    private static final Color HANDHELD_BOUNDARY_POINT = new Color(51, 102, 204);
    private static final Color HANDHELD_BOUNDARY_LABEL = new Color(22, 62, 138);
    private static final double BOUNDARY_CONTAINS_TOLERANCE = 0.05;
    
    // 组件引用
    private JPanel visualizationPanel;
    private List<Point2D.Double> currentBoundary;
    private Rectangle2D.Double boundaryBounds;
    private Path2D.Double currentBoundaryPath;
    private List<Point2D.Double> currentPlannedPath;
    private Rectangle2D.Double plannedPathBounds;
    private List<Obstacledge.Obstacle> currentObstacles;
@@ -94,6 +96,7 @@
    private long lastTrackPersistTimeMillis;
    private boolean trackDirty;
    private boolean handheldBoundaryPreviewActive;
    private boolean pendingTrackBreak = true;
    private static final double TRACK_SAMPLE_MIN_DISTANCE_METERS = 0.1d;
    private static final long TRACK_PERSIST_INTERVAL_MS = 5_000L;
@@ -432,15 +435,39 @@
    }
    private void captureRealtimeTrackPoint() {
        if (!realtimeTrackRecording) {
            return;
        }
        if (realtimeTrackLandNumber == null || visualizationPanel == null) {
            pendingTrackBreak = true;
            return;
        }
        Device device = Device.getGecaoji();
        if (device == null) {
            pendingTrackBreak = true;
            return;
        }
        String fixQuality = device.getPositioningStatus();
        if (!isHighPrecisionFix(fixQuality)) {
            pendingTrackBreak = true;
            return;
        }
        Point2D.Double position = mower.getPosition();
        if (position == null || !Double.isFinite(position.x) || !Double.isFinite(position.y)) {
            pendingTrackBreak = true;
            return;
        }
        if (!isPointInsideActiveBoundary(position)) {
            pendingTrackBreak = true;
            return;
        }
        Point2D.Double lastPoint = realtimeMowingTrack.isEmpty() ? null : realtimeMowingTrack.get(realtimeMowingTrack.size() - 1);
        if (pendingTrackBreak) {
            lastPoint = null;
        }
        double distance = 0.0;
        if (lastPoint != null) {
            double dx = position.x - lastPoint.x;
@@ -459,6 +486,7 @@
        updateCompletionMetrics();
        trackDirty = true;
        maybePersistRealtimeTrack(false);
        pendingTrackBreak = false;
    }
    private void updateCompletionMetrics() {
@@ -575,16 +603,19 @@
        realtimeTrackLandNumber = normalizedLand;
        realtimeTrackRecording = true;
        pendingTrackBreak = true;
        captureRealtimeTrackPoint();
    }
    public void pauseRealtimeTrackRecording() {
        realtimeTrackRecording = false;
        pendingTrackBreak = true;
        maybePersistRealtimeTrack(true);
    }
    public void stopRealtimeTrackRecording() {
        realtimeTrackRecording = false;
        pendingTrackBreak = true;
        maybePersistRealtimeTrack(true);
    }
@@ -602,15 +633,22 @@
        completedMowingAreaSqMeters = 0.0;
        mowingCompletionRatio = 0.0;
        trackDirty = true;
        pendingTrackBreak = true;
        maybePersistRealtimeTrack(true);
        visualizationPanel.repaint();
    }
    public double getMowingCompletionRatio() {
        if (!isMowerInsideSelectedBoundary()) {
            return 0.0;
        }
        return mowingCompletionRatio;
    }
    public double getCompletedMowingAreaSqMeters() {
        if (!isMowerInsideSelectedBoundary()) {
            return 0.0;
        }
        return completedMowingAreaSqMeters;
    }
@@ -622,6 +660,14 @@
        return trackLengthMeters;
    }
    private boolean isMowerInsideSelectedBoundary() {
        Point2D.Double position = mower.getPosition();
        if (position == null) {
            return false;
        }
        return isPointInsideActiveBoundary(position);
    }
    public void flushRealtimeTrack() {
        maybePersistRealtimeTrack(true);
    }
@@ -635,6 +681,7 @@
        mowingCompletionRatio = 0.0;
        trackDirty = false;
        lastTrackPersistTimeMillis = 0L;
        pendingTrackBreak = true;
        String trimmed = normalizeValue(trackData);
        if (trimmed == null || trimmed.isEmpty()) {
@@ -1071,7 +1118,7 @@
        mowerNumberValueLabel.setText(formatDeviceValue(device.getMowerNumber()));
        realtimeXValueLabel.setText(formatDeviceValue(device.getRealtimeX()));
        realtimeYValueLabel.setText(formatDeviceValue(device.getRealtimeY()));
        positioningStatusValueLabel.setText(formatDeviceValue(device.getPositioningStatus()));
    positioningStatusValueLabel.setText(formatFixQualityValue(device.getPositioningStatus()));
        satelliteCountValueLabel.setText(formatDeviceValue(device.getSatelliteCount()));
        realtimeSpeedValueLabel.setText(formatDeviceValue(device.getRealtimeSpeed()));
        headingValueLabel.setText(formatDeviceValue(device.getHeading()));
@@ -1082,7 +1129,7 @@
        if (mowerNumberValueLabel != null) mowerNumberValueLabel.setText(value);
        if (realtimeXValueLabel != null) realtimeXValueLabel.setText(value);
        if (realtimeYValueLabel != null) realtimeYValueLabel.setText(value);
        if (positioningStatusValueLabel != null) positioningStatusValueLabel.setText(value);
    if (positioningStatusValueLabel != null) positioningStatusValueLabel.setText(value);
        if (satelliteCountValueLabel != null) satelliteCountValueLabel.setText(value);
        if (realtimeSpeedValueLabel != null) realtimeSpeedValueLabel.setText(value);
        if (headingValueLabel != null) headingValueLabel.setText(value);
@@ -1105,6 +1152,37 @@
        return sanitized == null ? "--" : sanitized;
    }
    private String formatFixQualityValue(String value) {
        String sanitized = sanitizeDeviceValue(value);
        if (sanitized == null) {
            return "--";
        }
        switch (sanitized) {
            case "0":
                return "未定位";
            case "1":
                return "单点定位";
            case "2":
                return "码差分";
            case "3":
                return "无效PPS";
            case "4":
                return "固定解";
            case "5":
                return "浮点解";
            case "6":
                return "正在估算";
            case "7":
                return "人工输入固定值";
            case "8":
                return "模拟模式";
            case "9":
                return "WAAS差分";
            default:
                return sanitized;
        }
    }
    private String formatTimestamp(String value) {
        String sanitized = sanitizeDeviceValue(value);
        if (sanitized == null) {
@@ -1207,6 +1285,7 @@
        if (updated.size() < 2) {
            currentBoundary = null;
            currentBoundaryPath = null;
            boundaryBounds = null;
            boundaryPointsVisible = false;
            Dikuaiguanli.updateBoundaryPointVisibility(currentBoundaryLandNumber, false);
@@ -1214,10 +1293,12 @@
            adjustViewAfterBoundaryReset();
        } else {
            currentBoundary = updated;
            rebuildBoundaryPath();
            boundaryBounds = computeBounds(updated);
            Dikuaiguanli.updateBoundaryPointVisibility(currentBoundaryLandNumber, boundaryPointsVisible);
            visualizationPanel.repaint();
        }
        pendingTrackBreak = true;
    }
    private boolean persistBoundaryChanges(List<Point2D.Double> updatedBoundary) {
@@ -1279,6 +1360,79 @@
        return Math.hypot(dx, dy) <= BOUNDARY_POINT_MERGE_THRESHOLD;
    }
    private boolean isHighPrecisionFix(String fixQuality) {
        if (fixQuality == null) {
            return false;
        }
        String trimmed = fixQuality.trim();
        if (trimmed.isEmpty()) {
            return false;
        }
        if ("4".equals(trimmed)) {
            return true;
        }
        try {
            double value = Double.parseDouble(trimmed);
            return Math.abs(value - 4.0d) < 1e-6;
        } catch (NumberFormatException ex) {
            return false;
        }
    }
    private boolean isPointInsideActiveBoundary(Point2D.Double point) {
        if (point == null || !Double.isFinite(point.x) || !Double.isFinite(point.y)) {
            return false;
        }
        if (realtimeTrackLandNumber == null) {
            return false;
        }
        if (currentBoundaryLandNumber != null && !currentBoundaryLandNumber.equals(realtimeTrackLandNumber)) {
            return false;
        }
        Path2D.Double path = currentBoundaryPath;
        if (path == null) {
            path = buildBoundaryPath(currentBoundary);
            currentBoundaryPath = path;
        }
        if (path == null) {
            return false;
        }
        if (path.contains(point.x, point.y)) {
            return true;
        }
        double size = BOUNDARY_CONTAINS_TOLERANCE * 2.0;
        return path.intersects(point.x - BOUNDARY_CONTAINS_TOLERANCE, point.y - BOUNDARY_CONTAINS_TOLERANCE, size, size);
    }
    private void rebuildBoundaryPath() {
        currentBoundaryPath = buildBoundaryPath(currentBoundary);
    }
    private Path2D.Double buildBoundaryPath(List<Point2D.Double> boundary) {
        if (boundary == null || boundary.size() < 3) {
            return null;
        }
        Path2D.Double path = new Path2D.Double();
        boolean started = false;
        for (Point2D.Double point : boundary) {
            if (point == null || !Double.isFinite(point.x) || !Double.isFinite(point.y)) {
                continue;
            }
            if (!started) {
                path.moveTo(point.x, point.y);
                started = true;
            } else {
                path.lineTo(point.x, point.y);
            }
        }
        if (!started) {
            return null;
        }
        path.closePath();
        return path;
    }
    
    /**
     * 绘制视图信息
@@ -1360,8 +1514,10 @@
            return;
        }
        currentBoundary = parsed;
        boundaryBounds = computeBounds(parsed);
    currentBoundary = parsed;
    rebuildBoundaryPath();
    pendingTrackBreak = true;
    boundaryBounds = computeBounds(parsed);
        Rectangle2D.Double bounds = boundaryBounds;
        SwingUtilities.invokeLater(() -> {
@@ -1372,10 +1528,12 @@
    private void clearBoundaryData() {
        currentBoundary = null;
        currentBoundaryPath = null;
        boundaryBounds = null;
        boundaryName = null;
        boundaryPointsVisible = false;
        currentBoundaryLandNumber = null;
        pendingTrackBreak = true;
    }
    public void setCurrentObstacles(String obstaclesData, String landNumber) {