张世豪
4 天以前 dc9dce0555beb85d1262893fd5d56747d6a83855
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package gecaoji;
 
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
 
import javax.swing.ImageIcon;
 
/** Mower rendering helper. */
public class Gecaoji {
    private static final double ICON_PIXEL_SIZE = 48.0;
    private static final double ICON_SCALE_FACTOR = 0.8;
    private static final double MIN_SCALE = 1e-6;
    private static final Color FALLBACK_FILL = new Color(0, 150, 0);
    private static final String DEFAULT_ICON_PATH = "image/gecaoji.png";
    private static final String HANDHELD_ICON_PATH = "image/URT.png";
 
    private final Image defaultIcon;
    private final Image handheldIcon;
    private Image activeIcon;
    private boolean handheldIconActive;
    private final Ellipse2D.Double fallbackShape = new Ellipse2D.Double();
    private Point2D.Double position = new Point2D.Double();
    private boolean positionValid;
    private double headingDegrees;
 
    public Gecaoji() {
        defaultIcon = loadIcon(DEFAULT_ICON_PATH);
        handheldIcon = loadIcon(HANDHELD_ICON_PATH);
        activeIcon = defaultIcon != null ? defaultIcon : handheldIcon;
        handheldIconActive = false;
        refreshFromDevice();
    }
 
    private Image loadIcon(String path) {
        try {
            ImageIcon icon = new ImageIcon(path);
            if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) {
                return null;
            }
            return icon.getImage();
        } catch (Exception ex) {
            System.err.println("Unable to load mower icon from " + path + ": " + ex.getMessage());
            return null;
        }
    }
 
    public void refreshFromDevice() {
        Device device = Device.getGecaoji();
        if (device == null) {
            positionValid = false;
            return;
        }
 
        double x = parseCoordinate(device.getRealtimeX());
        double y = parseCoordinate(device.getRealtimeY());
        double heading = parseHeading(device.getHeading());
        if (Double.isNaN(x) || Double.isNaN(y)) {
            // Keep showing the last known mower position when temporary sensor glitches occur.
            return;
        }
 
        ensurePosition();
        position.x = x;
        position.y = y;
        positionValid = true;
        headingDegrees = heading;
    }
 
    private void ensurePosition() {
        if (position == null) {
            position = new Point2D.Double();
        }
    }
 
    private double parseCoordinate(String value) {
        if (value == null) {
            return Double.NaN;
        }
        String trimmed = value.trim();
        if (trimmed.isEmpty() || "-1".equals(trimmed)) {
            return Double.NaN;
        }
        try {
            return Double.parseDouble(trimmed);
        } catch (NumberFormatException ex) {
            return Double.NaN;
        }
    }
 
    private double parseHeading(String value) {
        if (value == null) {
            return 0.0;
        }
        String trimmed = value.trim();
        if (trimmed.isEmpty() || "-1".equals(trimmed)) {
            return 0.0;
        }
        try {
            double parsed = Double.parseDouble(trimmed);
            if (!Double.isFinite(parsed)) {
                return 0.0;
            }
            double normalized = parsed % 360.0;
            return normalized < 0 ? normalized + 360.0 : normalized;
        } catch (NumberFormatException ex) {
            return 0.0;
        }
    }
 
    public void draw(Graphics2D g2d, double scale) {
        if (!positionValid) {
            return;
        }
 
    double worldSize = (ICON_PIXEL_SIZE * ICON_SCALE_FACTOR) / Math.max(scale, MIN_SCALE);
        Image icon = activeIcon;
        if (icon != null && icon.getWidth(null) > 0 && icon.getHeight(null) > 0) {
            drawIcon(g2d, worldSize, icon);
        } else {
            drawFallback(g2d, worldSize);
        }
    }
 
    private void drawIcon(Graphics2D g2d, double worldSize, Image icon) {
        double iconWidth = icon.getWidth(null);
        double iconHeight = icon.getHeight(null);
        double maxSide = Math.max(iconWidth, iconHeight);
        double scaleFactor = worldSize / Math.max(maxSide, MIN_SCALE);
        double rotationRadians = Math.toRadians(-headingDegrees);
 
        AffineTransform original = g2d.getTransform();
        AffineTransform transformed = new AffineTransform(original);
        transformed.translate(position.x, position.y);
        if (rotationRadians != 0.0) {
            transformed.rotate(rotationRadians);
        }
        transformed.scale(scaleFactor, scaleFactor);
        transformed.translate(-iconWidth / 2.0, -iconHeight / 2.0);
        g2d.setTransform(transformed);
        g2d.drawImage(icon, 0, 0, null);
        g2d.setTransform(original);
    }
 
    private void drawFallback(Graphics2D g2d, double worldSize) {
        double radius = worldSize / 2.0;
        ensurePosition();
        fallbackShape.setFrame(position.x - radius, position.y - radius, worldSize, worldSize);
 
        Color original = g2d.getColor();
        g2d.setColor(FALLBACK_FILL);
        g2d.fill(fallbackShape);
        g2d.setColor(Color.WHITE);
        g2d.draw(fallbackShape);
    double rotationRadians = Math.toRadians(-headingDegrees);
    double lineLength = radius;
    double dx = lineLength * Math.sin(rotationRadians);
    double dy = lineLength * Math.cos(rotationRadians);
    g2d.drawLine(
        (int) Math.round(position.x),
        (int) Math.round(position.y),
        (int) Math.round(position.x + dx),
        (int) Math.round(position.y + dy));
        g2d.setColor(original);
    }
 
    public boolean hasValidPosition() {
        return positionValid;
    }
 
    public Point2D.Double getPosition() {
        if (!positionValid) {
            return null;
        }
        ensurePosition();
        return new Point2D.Double(position.x, position.y);
    }
 
    /**
     * 设置割草机位置(用于导航预览等场景)
     * @param x X坐标
     * @param y Y坐标
     */
    public void setPosition(double x, double y) {
        ensurePosition();
        position.x = x;
        position.y = y;
        positionValid = true;
    }
 
    /**
     * 设置割草机方向(用于导航预览等场景)
     * @param headingDegrees 方向角度(度,0-360)
     */
    public void setHeading(double headingDegrees) {
        double normalized = headingDegrees % 360.0;
        if (normalized < 0) {
            normalized += 360.0;
        }
        this.headingDegrees = normalized;
    }
 
    /**
     * 获取割草机方向
     * @return 方向角度(度,0-360)
     */
    public double getHeading() {
        return headingDegrees;
    }
 
    public double getWorldRadius(double scale) {
        if (!positionValid) {
            return Double.NaN;
        }
        double worldSize = (ICON_PIXEL_SIZE * ICON_SCALE_FACTOR) / Math.max(scale, MIN_SCALE);
        return worldSize / 2.0;
    }
 
    public boolean useHandheldIcon(boolean handheldMode) {
        if (handheldIconActive == handheldMode) {
            return false;
        }
        handheldIconActive = handheldMode;
        Image next = handheldMode
            ? (handheldIcon != null ? handheldIcon : defaultIcon)
            : defaultIcon;
        if (next == null) {
            next = handheldIcon;
        }
        activeIcon = next;
        return true;
    }
}