| | |
| | | # å²èæºå°åéç¢ç©é
ç½®æä»¶ |
| | | # çææ¶é´ï¼2025-12-27T23:31:09.655894400 |
| | | # çææ¶é´ï¼2025-12-28T20:21:18.532091 |
| | | # åæ ç³»ï¼WGS84ï¼åº¦åæ ¼å¼ï¼ |
| | | |
| | | # ============ å°ååºåç«é
ç½® ============ |
| | |
| | | #Dikuai Properties |
| | | #Sat Dec 27 23:31:09 GMT+08:00 2025 |
| | | #Sun Dec 28 20:21:18 GMT+08:00 2025 |
| | | LAND1.intelligentSceneAnalysis=-1 |
| | | LAND1.mowingSafetyDistance=0.53 |
| | | LAND1.landArea=577.12 |
| | |
| | | LAND1.returnPathRawCoordinates=-1 |
| | | LAND1.boundaryOriginalXY=-1 |
| | | LAND1.mowingWidth=1.00 |
| | | LAND1.plannedPath=73.82,49.86;50.78,49.99,M;41.98,22.49,M;49.63,20.04,M;51.80,34.27,M;66.28,36.75,M;66.34,21.40,M;78.08,24.80,M;73.82,49.86,M;73.87,49.87;73.87,49.87,T;73.87,49.87,T;73.87,49.87,T;73.87,49.87,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.91,49.36;50.66,49.52,M;50.62,49.54;50.62,49.54,T;50.62,49.54,T;50.62,49.54,T;50.62,49.54,T;50.30,48.54,T;50.30,48.54,T;50.30,48.54,T;50.30,48.54,T;50.30,48.54,T;50.35,48.53;74.07,48.36,M;74.12,48.37;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.24,47.36;50.03,47.53,M;49.98,47.54;49.98,47.54,T;49.98,47.54,T;49.98,47.54,T;49.98,47.54,T;49.66,46.55,T;49.66,46.55,T;49.66,46.55,T;49.66,46.55,T;49.66,46.55,T;49.71,46.53;74.41,46.36,M;74.46,46.37;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.58,45.36;49.39,45.53,M;49.34,45.55;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.07,44.53;74.75,44.36,M;74.80,44.37;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.91,43.36;48.75,43.54,M;48.71,43.55;48.71,43.55,T;48.71,43.55,T;48.71,43.55,T;48.71,43.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.43,42.54;75.08,42.36,M;75.13,42.36;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.25,41.35;48.12,41.54,M;48.07,41.56;48.07,41.56,T;48.07,41.56,T;48.07,41.56,T;48.07,41.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;75.47,40.36,M;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.59,39.35;47.48,39.55,M;47.43,39.56;47.43,39.56,T;47.43,39.56,T;47.43,39.56,T;47.43,39.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.16,38.55;75.75,38.35,M;75.80,38.36;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;46.79,37.57,M;46.79,37.57,T;46.79,37.57,T;46.79,37.57,T;46.79,37.57,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.34,36.43;76.09,36.35,M;76.14,36.36;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;66.29,35.43,M;66.29,35.43,T;66.29,35.43,T;66.29,35.43,T;66.29,35.43,T;66.29,34.43,T;66.29,34.43,T;66.29,34.43,T;66.29,34.43,T;66.29,34.43,T;66.34,34.43;76.43,34.35,M;76.48,34.35;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.60,33.34;66.34,33.43,M;66.29,33.43;66.29,33.43,T;66.29,33.43,T;66.29,33.43,T;66.29,33.43,T;66.29,32.43,T;66.29,32.43,T;66.29,32.43,T;66.29,32.43,T;66.29,32.43,T;66.34,32.43;76.76,32.34,M;76.81,32.35;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.93,31.34;66.34,31.43,M;66.29,31.43;66.29,31.43,T;66.29,31.43,T;66.29,31.43,T;66.29,31.43,T;66.29,30.43,T;66.29,30.43,T;66.29,30.43,T;66.29,30.43,T;66.29,30.43,T;66.34,30.43;77.10,30.34,M;77.15,30.35;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.27,29.34;66.34,29.43,M;66.29,29.43;66.29,29.43,T;66.29,29.43,T;66.29,29.43,T;66.29,29.43,T;66.29,28.43,T;66.29,28.43,T;66.29,28.43,T;66.29,28.43,T;66.29,28.43,T;66.34,28.43;77.44,28.34,M;77.48,28.35;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.60,27.34;66.34,27.43,M;66.29,27.43;66.29,27.43,T;66.29,27.43,T;66.29,27.43,T;66.29,27.43,T;66.29,26.43,T;66.29,26.43,T;66.29,26.43,T;66.29,26.43,T;66.29,26.43,T;66.34,26.43;77.77,26.34,M;77.82,26.34;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.94,25.34;66.34,25.43,M;66.29,25.43;66.29,25.43,T;66.29,25.43,T;66.29,25.43,T;66.29,25.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.34,24.43;76.67,24.40,M;76.69,24.35;76.69,24.35,T;76.69,24.35,T;76.69,24.35,T;76.69,24.35,T;73.25,23.38,T;73.25,23.38,T;73.25,23.38,T;73.25,23.38,T;73.25,23.38,T;73.24,23.43;66.34,23.43,M;66.29,23.43;66.29,23.43,T;66.29,23.43,T;66.29,23.43,T;66.29,23.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.34,22.43;69.80,22.45,M;69.81,22.40;69.81,22.40,T;69.81,22.40,T;69.81,22.40,T;69.81,22.40,T;66.38,21.43,T;66.38,21.43,T;66.38,21.43,T;66.38,21.43,T;66.38,21.43,T;66.36,21.47;66.34,21.43,M;66.29,21.43;66.29,21.43,T;66.29,21.43,T;66.29,21.43,T;66.29,21.43,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.87,20.60;49.65,20.55,M;49.70,20.54;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.52,36.55;64.75,36.49,M;64.75,36.44;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.06,35.53;46.20,35.55,M;46.16,35.57;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.89,34.56;53.37,34.57,M;53.38,34.52;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.68,33.54;45.57,33.56,M;45.52,33.57;45.52,33.57,T;45.52,33.57,T;45.52,33.57,T;45.52,33.57,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.25,32.56;51.53,32.54,M;51.57,32.53;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.37,31.54;44.93,31.56,M;44.88,31.58;44.88,31.58,T;44.88,31.58,T;44.88,31.58,T;44.88,31.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.61,30.57;51.21,30.54,M;51.26,30.53;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.06,29.54;44.29,29.57,M;44.25,29.58;44.25,29.58,T;44.25,29.58,T;44.25,29.58,T;44.25,29.58,T;43.93,28.59,T;43.93,28.59,T;43.93,28.59,T;43.93,28.59,T;43.93,28.59,T;43.97,28.57;50.90,28.54,M;50.95,28.54;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.74,27.54;43.66,27.57,M;43.61,27.59;43.61,27.59,T;43.61,27.59,T;43.61,27.59,T;43.61,27.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.34,26.57;50.59,26.55,M;50.64,26.54;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.43,25.55;43.02,25.58,M;42.97,25.59;42.97,25.59,T;42.97,25.59,T;42.97,25.59,T;42.97,25.59,T;42.65,24.59,T;42.65,24.59,T;42.65,24.59,T;42.65,24.59,T;42.65,24.59,T;42.70,24.58;50.27,24.55,M;50.32,24.54;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.12,23.55;42.38,23.58,M;42.33,23.60;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.06,22.58;49.96,22.55,M;50.01,22.54;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.81,21.55;44.68,21.63,M |
| | | LAND1.updateTime=2025-12-27 23\:31\:09 |
| | | LAND1.plannedPath=73.82,49.86;50.78,49.99,M;41.98,22.49,M;49.63,20.04,M;51.80,34.27,M;66.28,36.75,M;66.34,21.40,M;78.08,24.80,M;73.82,49.86,M;73.87,49.87;73.87,49.87,T;73.87,49.87,T;73.87,49.87,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.91,49.36;50.66,49.52,M;50.62,49.54;50.30,48.54,T;50.35,48.53;74.07,48.36,M;74.12,48.37;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.24,47.36;50.03,47.53,M;49.98,47.54;49.66,46.55,T;49.71,46.53;74.41,46.36,M;74.46,46.37;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.58,45.36;49.39,45.53,M;49.34,45.55;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.07,44.53;74.75,44.36,M;74.80,44.37;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.91,43.36;48.75,43.54,M;48.71,43.55;48.71,43.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.43,42.54;75.08,42.36,M;75.13,42.36;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.25,41.35;48.12,41.54,M;48.07,41.56;48.07,41.56,T;48.07,41.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;75.47,40.36,M;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.59,39.35;47.48,39.55,M;47.43,39.56;47.43,39.56,T;47.43,39.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.16,38.55;75.75,38.35,M;75.80,38.36;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;46.79,37.57,M;46.79,37.57,T;41.97,22.44,T;49.61,19.99,T;51.85,34.26,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;66.29,36.70,T;66.29,36.70,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.34,36.43;76.09,36.35,M;76.14,36.36;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;66.29,35.43,M;66.29,34.43,T;66.34,34.43;76.43,34.35,M;76.48,34.35;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.60,33.34;66.34,33.43,M;66.29,33.43;66.29,32.43,T;66.34,32.43;76.76,32.34,M;76.81,32.35;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.93,31.34;66.34,31.43,M;66.29,31.43;66.29,30.43,T;66.34,30.43;77.10,30.34,M;77.15,30.35;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.27,29.34;66.34,29.43,M;66.29,29.43;66.29,28.43,T;66.34,28.43;77.44,28.34,M;77.48,28.35;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.60,27.34;66.34,27.43,M;66.29,27.43;66.29,26.43,T;66.34,26.43;77.77,26.34,M;77.82,26.34;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.94,25.34;66.34,25.43,M;66.29,25.43;66.29,25.43,T;66.29,25.43,T;66.29,25.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.34,24.43;76.67,24.40,M;76.69,24.35;73.25,23.38,T;73.24,23.43;66.34,23.43,M;66.29,23.43;66.29,23.43,T;66.29,23.43,T;66.29,23.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.34,22.43;69.80,22.45,M;69.81,22.40;66.38,21.43,T;66.36,21.47;66.34,21.43,M;66.29,21.43;66.29,21.43,T;66.29,21.43,T;66.29,21.43,T;66.29,36.70,T;66.29,36.70,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.87,20.60;49.65,20.55,M;49.70,20.54;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;41.97,22.44,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.52,36.55;64.75,36.49,M;64.75,36.44;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.06,35.53;46.20,35.55,M;46.16,35.57;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.89,34.56;53.37,34.57,M;53.38,34.52;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.68,33.54;45.57,33.56,M;45.52,33.57;45.52,33.57,T;45.52,33.57,T;45.52,33.57,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.25,32.56;51.53,32.54,M;51.57,32.53;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.37,31.54;44.93,31.56,M;44.88,31.58;44.88,31.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.61,30.57;51.21,30.54,M;51.26,30.53;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.06,29.54;44.29,29.57,M;44.25,29.58;43.93,28.59,T;43.97,28.57;50.90,28.54,M;50.95,28.54;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.74,27.54;43.66,27.57,M;43.61,27.59;43.61,27.59,T;43.61,27.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.34,26.57;50.59,26.55,M;50.64,26.54;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.43,25.55;43.02,25.58,M;42.97,25.59;42.65,24.59,T;42.70,24.58;50.27,24.55,M;50.32,24.54;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.12,23.55;42.38,23.58,M;42.33,23.60;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.06,22.58;49.96,22.55,M;50.01,22.54;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.81,21.55;44.68,21.63,M |
| | | LAND1.updateTime=2025-12-28 20\:21\:18 |
| | | LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E |
| | | LAND1.boundaryPointInterval=-1 |
| | | LAND1.createTime=2025-12-23 17\:08\:09 |
| | |
| | | #Mower Configuration Properties - Updated |
| | | #Sat Dec 27 23:41:35 GMT+08:00 2025 |
| | | #Sun Dec 28 20:35:53 GMT+08:00 2025 |
| | | appVersion=-1 |
| | | simCardNumber=-1 |
| | | currentWorkLandNumber=LAND1 |
| | |
| | | boundaryLengthVisible=false |
| | | idleTrailDurationSeconds=60 |
| | | handheldMarkerId=1872 |
| | | viewCenterX=-57.93 |
| | | viewCenterY=-27.08 |
| | | viewCenterX=-60.00 |
| | | viewCenterY=-34.94 |
| | | manualBoundaryDrawingMode=false |
| | | mowerId=6258 |
| | | serialPortName=COM15 |
| | |
| | | import zhuye.Shouye; |
| | | import zhuye.MapRenderer; |
| | | import gecaoji.Gecaoji; |
| | | import gecaoji.Device; |
| | | import gecaoji.lujingdraw; |
| | | import publicway.buttonset; |
| | | |
| | |
| | | this.currentDikuai = dikuai; |
| | | this.currentPathIndex = 0; |
| | | this.currentSpeed = DEFAULT_SPEED; |
| | | // å°å½åé度(ç±³/ç§)转æ¢ä¸ºkm/håå
¥è®¾å¤yaw屿§ |
| | | try { |
| | | double kmh = this.currentSpeed * 3.6; |
| | | Device.getActiveDevice().setYaw(String.valueOf(kmh)); |
| | | } catch (Exception ignore) { } |
| | | this.navigationTrack.clear(); |
| | | |
| | | // è·åé¦é¡µåå°å¾æ¸²æå¨ |
| | |
| | | |
| | | /** |
| | | * 计ç®ä¸¤ç¹ä¹é´çæ¹åè§ï¼åº¦ï¼ |
| | | * 徿 é»è®¤æä¸ï¼åå³æè½¬90度车头æå³ |
| | | * atan2è¿åçè§åº¦ï¼å峿¯0度ï¼å䏿¯90度 |
| | | * éè¦è½¬æ¢ä¸ºå¾æ æè½¬è§åº¦ï¼åå³éè¦90度ï¼åä¸éè¦0度 |
| | | * 车è¾å¾æ ç车头é»è®¤æ¯å¨å±å¹æ£ä¸æ¹ï¼270åº¦ï¼ |
| | | * atan2è¿åçè§åº¦ï¼å峿¯0度ï¼å䏿¯90度ï¼åå·¦æ¯180度ï¼å䏿¯-90度ï¼270åº¦ï¼ |
| | | * éè¦è½¬æ¢ä¸ºå¾æ æè½¬è§åº¦ï¼ä½¿è½¦å¤´æåè¡é©¶æ¹å |
| | | */ |
| | | private double calculateHeading(Point2D.Double from, Point2D.Double to) { |
| | | double dx = to.x - from.x; |
| | |
| | | atan2Angle += 360; |
| | | } |
| | | |
| | | // 徿 é»è®¤æä¸ï¼0度ï¼ï¼åå³æè½¬90度车头æå³ |
| | | // æä»¥ï¼è¿å¨æ¹ååå³ï¼0度ï¼â éè¦æè½¬90度 |
| | | // è¿å¨æ¹ååä¸ï¼90度ï¼â éè¦æè½¬0度 |
| | | // è¿å¨æ¹ååå·¦ï¼180度ï¼â éè¦æè½¬270度 |
| | | // è¿å¨æ¹ååä¸ï¼270度ï¼â éè¦æè½¬180度 |
| | | // å
¬å¼ï¼heading = (90 - atan2Angle + 360) % 360 |
| | | double heading = (90.0 - atan2Angle + 360.0) % 360.0; |
| | | // 徿 é»è®¤æä¸ï¼270度ï¼ï¼éè¦è®¡ç®æè½¬è§åº¦ä½¿è½¦å¤´æåè¡é©¶æ¹å |
| | | // 妿è¿å¨æ¹ååä¸ï¼90度ï¼â éè¦æè½¬180度ï¼270 - 90 = 180ï¼ |
| | | // 妿è¿å¨æ¹ååå³ï¼0度ï¼â éè¦æè½¬270度ï¼270 - 0 = 270ï¼ |
| | | // 妿è¿å¨æ¹ååå·¦ï¼180度ï¼â éè¦æè½¬90度ï¼270 - 180 = 90ï¼ |
| | | // 妿è¿å¨æ¹ååä¸ï¼270度ï¼â éè¦æè½¬0度ï¼270 - 270 = 0ï¼ |
| | | // å
¬å¼ï¼heading = (270 - atan2Angle + 360) % 360 |
| | | double heading = (270.0 - atan2Angle + 360.0) % 360.0; |
| | | |
| | | return heading; |
| | | } |
| | |
| | | */ |
| | | private void speedUp() { |
| | | currentSpeed += SPEED_MULTIPLIER; |
| | | // 忥å°è®¾å¤yaw屿§ï¼km/hï¼ |
| | | try { |
| | | double kmh = currentSpeed * 3.6; |
| | | Device.getActiveDevice().setYaw(String.valueOf(kmh)); |
| | | } catch (Exception ignore) { } |
| | | updateSpeedDisplay(); |
| | | } |
| | | |
| | |
| | | currentSpeed = 0.1; |
| | | } |
| | | } |
| | | // 忥å°è®¾å¤yaw屿§ï¼km/hï¼ |
| | | try { |
| | | double kmh = currentSpeed * 3.6; |
| | | Device.getActiveDevice().setYaw(String.valueOf(kmh)); |
| | | } catch (Exception ignore) { } |
| | | updateSpeedDisplay(); |
| | | } |
| | | |
| | |
| | | * æ´æ°é度æ¾ç¤º |
| | | */ |
| | | private void updateSpeedDisplay() { |
| | | // å¯ä»¥å¨å°å¾ä¸æ¾ç¤ºå½åé度 |
| | | // è¿éææ¶ä¸å®ç°ï¼å¦æéè¦å¯ä»¥å¨MapRenderer䏿·»å é度æ¾ç¤º |
| | | // 导èªé¢è§ä¸å¨å²èæºå¾æ 䏿¹å®æ¶æ¾ç¤ºé度ï¼åºå®åç´ é´è·ï¼ |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setNavigationPreviewSpeed(currentSpeed); |
| | | mapRenderer.repaint(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | mapRenderer.repaint(); |
| | | } |
| | | |
| | | // éåºæ¶å°è®¾å¤yaw屿§é置为0 |
| | | try { |
| | | Device.getActiveDevice().setYaw("0"); |
| | | } catch (Exception ignore) { } |
| | | |
| | | // æ¢å¤å°å管çé¡µé¢ |
| | | // 卿¸
空currentDikuaiä¹åä¿åå°åç¼å·ï¼ä½¿ç¨finalåé以便å¨lambdaä¸ä½¿ç¨ |
| | | final String landNumber = (currentDikuai != null) ? currentDikuai.getLandNumber() : null; |
| | |
| | | double iconHeight = icon.getHeight(null); |
| | | double maxSide = Math.max(iconWidth, iconHeight); |
| | | double scaleFactor = worldSize / Math.max(maxSide, MIN_SCALE); |
| | | // å²èæºå¾æ é»è®¤æåï¼Yaw=0表示æ£åï¼éè¦æè½¬180度 |
| | | double rotationRadians = Math.toRadians(headingDegrees + 180); |
| | | // 车è¾å¾æ ç车头é»è®¤æ¯å¨å±å¹æ£ä¸æ¹ï¼270åº¦ï¼ |
| | | // headingDegreeså·²ç»æ¯è®¡ç®å¥½çæè½¬è§åº¦ï¼0-360度ï¼ï¼ç´æ¥ä½¿ç¨ |
| | | double rotationRadians = Math.toRadians(headingDegrees); |
| | | |
| | | AffineTransform original = g2d.getTransform(); |
| | | AffineTransform transformed = new AffineTransform(original); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package lujing; |
| | | |
| | | import java.util.*; |
| | | |
| | | public class DebugPath { |
| | | public static void main(String[] args) { |
| | | String coordinates = "50.39,50.57;74.32,50.40;78.69,24.37;65.76,20.70;65.76,36.07;52.31,33.80;50.04,19.30;41.30,22.10"; |
| | | String widthStr = "1"; |
| | | String marginStr = "0.53"; |
| | | |
| | | List<YixinglujingNoObstacle.PathSegment> path = YixinglujingNoObstacle.planPath(coordinates, widthStr, marginStr); |
| | | // Validate that non-mowing segments do not cross outside polygon |
| | | List<YixinglujingNoObstacle.Point> rawPoints = parseCoordinates("50.39,50.57;74.32,50.40;78.69,24.37;65.76,20.70;65.76,36.07;52.31,33.80;50.04,19.30;41.30,22.10"); |
| | | YixinglujingNoObstacle.ensureCounterClockwise(rawPoints); |
| | | List<YixinglujingNoObstacle.Point> polygonInset = YixinglujingNoObstacle.getInsetPolygon(rawPoints, Double.parseDouble(marginStr)); |
| | | |
| | | int total = path.size(); |
| | | int mowing = 0, travel = 0, unsafe = 0; |
| | | for (YixinglujingNoObstacle.PathSegment s : path) { |
| | | if (s.isMowing) { |
| | | mowing++; |
| | | continue; |
| | | } |
| | | travel++; |
| | | boolean safe = isSegmentSafe(s.start, s.end, polygonInset); |
| | | if (!safe) { |
| | | // å
è®¸âæ²¿è¾¹çâçæ
è¡æ®µï¼ä¸¤ç«¯é½è´´è¿è¾¹çå³å¯è§ä¸ºå®å
¨ |
| | | double d1 = minDistanceToBoundary(s.start, polygonInset); |
| | | double d2 = minDistanceToBoundary(s.end, polygonInset); |
| | | if (d1 <= 0.2 && d2 <= 0.2) { |
| | | // treat as safe boundary-following segment |
| | | continue; |
| | | } |
| | | unsafe++; |
| | | } |
| | | } |
| | | System.out.println("Segments total=" + total + ", mowing=" + mowing + ", travel=" + travel + ", unsafeTravel=" + unsafe); |
| | | if (unsafe == 0) { |
| | | System.out.println("Validation OK: no travel segment crosses the inset boundary."); |
| | | } else { |
| | | System.out.println("Validation FAILED: some travel segments cross boundary: " + unsafe); |
| | | } |
| | | } |
| | | |
| | | // --- Helpers copied for validation --- |
| | | private static List<YixinglujingNoObstacle.Point> parseCoordinates(String coordinates) { |
| | | List<YixinglujingNoObstacle.Point> points = new ArrayList<>(); |
| | | String[] pairs = coordinates.split(";"); |
| | | for (String pair : pairs) { |
| | | String[] xy = pair.split(","); |
| | | if (xy.length == 2) points.add(new YixinglujingNoObstacle.Point(Double.parseDouble(xy[0]), Double.parseDouble(xy[1]))); |
| | | } |
| | | return points; |
| | | } |
| | | |
| | | private static boolean isSegmentSafe(YixinglujingNoObstacle.Point p1, YixinglujingNoObstacle.Point p2, List<YixinglujingNoObstacle.Point> polygon) { |
| | | YixinglujingNoObstacle.Point mid = new YixinglujingNoObstacle.Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); |
| | | if (!isPointInPolygon(mid, polygon)) return false; |
| | | for (int i = 0; i < polygon.size(); i++) { |
| | | YixinglujingNoObstacle.Point a = polygon.get(i); |
| | | YixinglujingNoObstacle.Point b = polygon.get((i + 1) % polygon.size()); |
| | | if (segmentsIntersect(p1, p2, a, b)) return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private static boolean segmentsIntersect(YixinglujingNoObstacle.Point a, YixinglujingNoObstacle.Point b, YixinglujingNoObstacle.Point c, YixinglujingNoObstacle.Point d) { |
| | | return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d); |
| | | } |
| | | |
| | | private static boolean ccw(YixinglujingNoObstacle.Point a, YixinglujingNoObstacle.Point b, YixinglujingNoObstacle.Point c) { |
| | | return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x); |
| | | } |
| | | |
| | | private static boolean isPointInPolygon(YixinglujingNoObstacle.Point p, List<YixinglujingNoObstacle.Point> polygon) { |
| | | boolean result = false; |
| | | for (int i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) { |
| | | if ((polygon.get(i).y > p.y) != (polygon.get(j).y > p.y) && |
| | | (p.x < (polygon.get(j).x - polygon.get(i).x) * (p.y - polygon.get(i).y) / (polygon.get(j).y - polygon.get(i).y) + polygon.get(i).x)) { |
| | | result = !result; |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private static double minDistanceToBoundary(YixinglujingNoObstacle.Point p, List<YixinglujingNoObstacle.Point> poly) { |
| | | double minD = Double.MAX_VALUE; |
| | | for (int i = 0; i < poly.size(); i++) { |
| | | YixinglujingNoObstacle.Point s = poly.get(i); |
| | | YixinglujingNoObstacle.Point e = poly.get((i + 1) % poly.size()); |
| | | double l2 = (s.x - e.x)*(s.x - e.x) + (s.y - e.y)*(s.y - e.y); |
| | | if (l2 == 0) { |
| | | minD = Math.min(minD, Math.hypot(p.x - s.x, p.y - s.y)); |
| | | continue; |
| | | } |
| | | double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2; |
| | | t = Math.max(0, Math.min(1, t)); |
| | | double px = s.x + t * (e.x - s.x); |
| | | double py = s.y + t * (e.y - s.y); |
| | | double d = Math.hypot(p.x - px, p.y - py); |
| | | minD = Math.min(minD, d); |
| | | } |
| | | return minD; |
| | | } |
| | | } |
| | |
| | | remainingSegments.addAll(clippedSegments); |
| | | } |
| | | |
| | | // 3. éæ°è¿æ¥è·¯å¾æ®µï¼å¼åå½¢è¿æ¥ï¼ |
| | | return reconnectSegments(remainingSegments); |
| | | // 3. éæ°è¿æ¥è·¯å¾æ®µï¼å¼åå½¢è¿æ¥ï¼æºè½å¤çè¾¹çç©¿è¶ï¼ |
| | | return reconnectSegments(remainingSegments, polygon); |
| | | } |
| | | |
| | | /** |
| | |
| | | return result; |
| | | } else { |
| | | // ä¸ç«¯å¨å
é¨ï¼ä¸ç«¯å¨å¤é¨ |
| | | Point insidePoint = startInside ? segment.start : segment.end; |
| | | Point outsidePoint = startInside ? segment.end : segment.start; |
| | | |
| | | List<Point> intersections = obstacle.getIntersections(segment); |
| | |
| | | |
| | | /** |
| | | * éæ°è¿æ¥è·¯å¾æ®µï¼å½¢æè¿ç»å¼åå½¢è·¯å¾ |
| | | * ä¼åï¼æºè½å¤çè¾¹çç©¿è¶ï¼å½æ¢è¡è·¯å¾ç©¿è¶è¾¹çæ¶ï¼æ²¿è¾¹çè¡èµ° |
| | | */ |
| | | private static List<PathSegment> reconnectSegments(List<PathSegment> segments) { |
| | | private static List<PathSegment> reconnectSegments(List<PathSegment> segments, List<Point> boundary) { |
| | | if (segments.isEmpty()) return new ArrayList<>(); |
| | | |
| | | List<PathSegment> reconnected = new ArrayList<>(); |
| | |
| | | if (seg.isMowing) { |
| | | // å²èæ®µï¼æ£æ¥æ¯å¦éè¦æ·»å 空走段 |
| | | if (distance(currentPos, seg.start) > 0.01) { |
| | | reconnected.add(new PathSegment(currentPos, seg.start, false)); |
| | | // ä½¿ç¨æºè½è¿æ¥æ¹æ³çææ¢è¡è·¯å¾ |
| | | List<PathSegment> connectionPath = buildSmartConnection(currentPos, seg.start, boundary); |
| | | reconnected.addAll(connectionPath); |
| | | } |
| | | reconnected.add(seg); |
| | | currentPos = seg.end; |
| | |
| | | } |
| | | |
| | | /** |
| | | * æºè½è¿æ¥ä¸¤ç¹ï¼å¦æç´çº¿ä¸ç©¿è¶è¾¹çåç´æ¥è¿æ¥ï¼å¦å使ç¨ç´çº¿+è¾¹çæ··åè·¯å¾ |
| | | * ä¼åé»è¾ï¼ |
| | | * 1. 妿AB线ä¸ç©¿è¶è¾¹çCï¼ç´æ¥ä½¿ç¨ABä½ä¸ºæ¢è¡è·¯çº¿ |
| | | * 2. 妿AB线穿è¶äºè¾¹çCï¼æ¾å°ææäº¤ç¹ï¼å°ABåæå¤ä¸ªæ®µ |
| | | * - 对äºå¨è¾¹çå
é¨ç段ï¼å¦DF段ãGH段ï¼ï¼æ²¿è¾¹çè¡èµ° |
| | | * - 对äºå¨è¾¹çå¤é¨çæ®µï¼æ²¿ABç´çº¿è¡èµ° |
| | | * è·¯å¾ç¤ºä¾ï¼A â D(ç´çº¿) â F(沿边ç) â G(ç´çº¿) â H(沿边ç) â B(ç´çº¿) |
| | | * |
| | | * @param pointA èµ·ç¹ï¼ä¸ä¸æ®µç»æçç»ç¹ï¼ |
| | | * @param pointB ç»ç¹ï¼ä¸ä¸æ®µéè¦å²èè·¯å¾çèµ·å§ç¹ï¼ |
| | | * @param boundary å®å
¨å
缩边çC |
| | | * @return è¿æ¥è·¯å¾æ®µå表ï¼å
¨é¨ä¸ºisMowing=falseçç©ºèµ°æ®µï¼ |
| | | */ |
| | | private static List<PathSegment> buildSmartConnection(Point pointA, Point pointB, List<Point> boundary) { |
| | | List<PathSegment> result = new ArrayList<>(); |
| | | |
| | | // 1. æ£æ¥ABç´çº¿æ¯å¦ç©¿è¶è¾¹çC |
| | | if (!segmentIntersectsBoundary(pointA, pointB, boundary)) { |
| | | // ä¸ç©¿è¶è¾¹çï¼ç´æ¥ä½¿ç¨ABä½ä¸ºæ¢è¡è·¯çº¿ |
| | | result.add(new PathSegment(pointA, pointB, false)); |
| | | return result; |
| | | } |
| | | |
| | | // 2. AB线穿è¶äºè¾¹çCï¼éè¦æ¾å°ææäº¤ç¹ |
| | | List<IntersectionInfo> intersections = getAllBoundaryIntersections(pointA, pointB, boundary); |
| | | |
| | | if (intersections.isEmpty()) { |
| | | // 没æäº¤ç¹ï¼ä¸åºè¯¥åçï¼ä½å®å
¨å¤çï¼ï¼ä½¿ç¨ç´çº¿ |
| | | result.add(new PathSegment(pointA, pointB, false)); |
| | | return result; |
| | | } |
| | | |
| | | // 3. æè·ç¦»èµ·ç¹Açè·ç¦»æåºäº¤ç¹ |
| | | intersections.sort(Comparator.comparingDouble(inter -> distance(pointA, inter.point))); |
| | | |
| | | // 4. æå»ºå®æ´çç¹åºåï¼A, I1, I2, ..., In, Bï¼I为交ç¹ï¼ |
| | | List<Point> pointSequence = new ArrayList<>(); |
| | | pointSequence.add(pointA); |
| | | for (IntersectionInfo inter : intersections) { |
| | | pointSequence.add(inter.point); |
| | | } |
| | | pointSequence.add(pointB); |
| | | |
| | | // 5. å¤çæ¯ä¸¤ä¸ªç¸é»ç¹ä¹é´ç段 |
| | | Point currentPos = pointA; |
| | | |
| | | for (int i = 0; i < pointSequence.size() - 1; i++) { |
| | | Point p1 = pointSequence.get(i); |
| | | Point p2 = pointSequence.get(i + 1); |
| | | |
| | | // 夿p1å°p2çæ®µï¼AB线段çä¸é¨åï¼æ¯å¦å¨è¾¹çCå
é¨ï¼æ£æ¥ä¸ç¹ï¼ |
| | | Point midPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); |
| | | boolean segmentInsideBoundary = isPointInPolygon(midPoint, boundary); |
| | | |
| | | if (segmentInsideBoundary) { |
| | | // 段å¨è¾¹çå
é¨ï¼å¦DF段ãGH段ï¼ï¼éè¦æ²¿è¾¹çè¡èµ° |
| | | if (i == 0) { |
| | | // 第ä¸ä¸ªæ®µï¼ä»Aå°ç¬¬ä¸ä¸ªäº¤ç¹D |
| | | // å¦ææ®µå¨è¾¹çå
é¨ï¼è¯´æAå¨è¾¹çå
é¨ï¼éè¦å
ä»A沿边çèµ°å°ç¬¬ä¸ä¸ªäº¤ç¹ |
| | | IntersectionInfo firstInter = intersections.get(0); |
| | | SnapResult snapA = snapToBoundary(currentPos, boundary); |
| | | List<PathSegment> boundaryPath = getBoundaryPathBetweenPoints( |
| | | snapA.onEdge, snapA.edgeIndex, |
| | | firstInter.point, firstInter.edgeIndex, |
| | | boundary); |
| | | result.addAll(boundaryPath); |
| | | currentPos = firstInter.point; |
| | | } else if (i == pointSequence.size() - 2) { |
| | | // æåä¸ä¸ªæ®µï¼ä»æåä¸ä¸ªäº¤ç¹Hå°B |
| | | // éè¦æ²¿è¾¹çä»å½åç¹ï¼åºè¯¥æ¯æåä¸ä¸ªäº¤ç¹Hï¼å°Bå¨è¾¹çä¸çæå½± |
| | | IntersectionInfo lastInter = intersections.get(intersections.size() - 1); |
| | | SnapResult snapB = snapToBoundary(pointB, boundary); |
| | | List<PathSegment> boundaryPath = getBoundaryPathBetweenPoints( |
| | | currentPos, lastInter.edgeIndex, |
| | | snapB.onEdge, snapB.edgeIndex, |
| | | boundary); |
| | | result.addAll(boundaryPath); |
| | | // 妿Bä¸å¨è¾¹çä¸ï¼ä»è¾¹çæå½±ç´çº¿å°B |
| | | if (distance(snapB.onEdge, pointB) > 1e-6) { |
| | | result.add(new PathSegment(snapB.onEdge, pointB, false)); |
| | | } |
| | | currentPos = pointB; |
| | | } else { |
| | | // ä¸é´æ®µï¼ä¸¤ä¸ªäº¤ç¹ä¹é´ç段ï¼é½å¨è¾¹çä¸ï¼ï¼æ²¿è¾¹çè¡èµ° |
| | | IntersectionInfo inter1 = intersections.get(i - 1); |
| | | IntersectionInfo inter2 = intersections.get(i); |
| | | List<PathSegment> boundaryPath = getBoundaryPathBetweenPoints( |
| | | inter1.point, inter1.edgeIndex, |
| | | inter2.point, inter2.edgeIndex, |
| | | boundary); |
| | | result.addAll(boundaryPath); |
| | | currentPos = inter2.point; |
| | | } |
| | | } else { |
| | | // 段å¨è¾¹çå¤é¨ï¼å¯ä»¥ç´æ¥æ²¿ABç´çº¿è¿æ¥ï¼å¦Aå°Dï¼Få°Gï¼Hå°Bï¼ |
| | | if (distance(p1, p2) > 1e-6) { |
| | | // 妿å½åç¹ä¸å¨p1ï¼å
è¿æ¥å°p1 |
| | | if (distance(currentPos, p1) > 1e-6) { |
| | | result.add(new PathSegment(currentPos, p1, false)); |
| | | } |
| | | // ä»p1ç´çº¿å°p2 |
| | | result.add(new PathSegment(p1, p2, false)); |
| | | currentPos = p2; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * æ£æ¥çº¿æ®µæ¯å¦ç©¿è¶è¾¹çï¼ä¸è¾¹çè¾¹ç¸äº¤ï¼ä¸å
æ¬ç«¯ç¹ï¼ |
| | | */ |
| | | private static boolean segmentIntersectsBoundary(Point a, Point b, List<Point> boundary) { |
| | | for (int i = 0; i < boundary.size(); i++) { |
| | | Point c = boundary.get(i); |
| | | Point d = boundary.get((i + 1) % boundary.size()); |
| | | // 忽ç¥å
±äº«ç«¯ç¹çç¸äº¤ |
| | | if (isSamePoint(a, c) || isSamePoint(a, d) || isSamePoint(b, c) || isSamePoint(b, d)) { |
| | | continue; |
| | | } |
| | | if (segmentsIntersect(a, b, c, d)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * è·å线段ä¸è¾¹ççææäº¤ç¹ä¿¡æ¯ï¼å
æ¬ç¹å对åºè¾¹ç´¢å¼ï¼ |
| | | */ |
| | | private static List<IntersectionInfo> getAllBoundaryIntersections(Point a, Point b, List<Point> boundary) { |
| | | List<IntersectionInfo> intersections = new ArrayList<>(); |
| | | |
| | | for (int i = 0; i < boundary.size(); i++) { |
| | | Point c = boundary.get(i); |
| | | Point d = boundary.get((i + 1) % boundary.size()); |
| | | |
| | | // 忽ç¥å
±äº«ç«¯ç¹ |
| | | if (isSamePoint(a, c) || isSamePoint(a, d) || isSamePoint(b, c) || isSamePoint(b, d)) { |
| | | continue; |
| | | } |
| | | |
| | | Point intersection = getLineIntersection(a, b, c, d); |
| | | if (intersection != null) { |
| | | intersections.add(new IntersectionInfo(intersection, i)); |
| | | } |
| | | } |
| | | |
| | | return intersections; |
| | | } |
| | | |
| | | /** |
| | | * è·åè¾¹çä¸ä¸¤ç¹ä¹é´çè·¯å¾ï¼æ²¿è¾¹çè¡èµ°ï¼ |
| | | * @param start èµ·ç¹ï¼å¿
é¡»å¨è¾¹çä¸ï¼ |
| | | * @param startEdgeIndex èµ·ç¹æå¨çè¾¹ç´¢å¼ |
| | | * @param end ç»ç¹ï¼å¿
é¡»å¨è¾¹çä¸ï¼ |
| | | * @param endEdgeIndex ç»ç¹æå¨çè¾¹ç´¢å¼ |
| | | * @param boundary è¾¹çç¹å表 |
| | | * @return 沿边ççè·¯å¾æ®µå表 |
| | | */ |
| | | private static List<PathSegment> getBoundaryPathBetweenPoints( |
| | | Point start, int startEdgeIndex, |
| | | Point end, int endEdgeIndex, |
| | | List<Point> boundary) { |
| | | |
| | | List<PathSegment> result = new ArrayList<>(); |
| | | |
| | | if (startEdgeIndex == endEdgeIndex) { |
| | | // å¨å䏿¡è¾¹ä¸ï¼ç´æ¥è¿æ¥ |
| | | if (distance(start, end) > 1e-6) { |
| | | result.add(new PathSegment(start, end, false)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | int n = boundary.size(); |
| | | |
| | | // 计ç®é¡ºæ¶éè·¯å¾ |
| | | List<Point> pathClockwise = new ArrayList<>(); |
| | | pathClockwise.add(start); |
| | | |
| | | int curr = startEdgeIndex; |
| | | while (curr != endEdgeIndex) { |
| | | pathClockwise.add(boundary.get((curr + 1) % n)); |
| | | curr = (curr + 1) % n; |
| | | } |
| | | pathClockwise.add(end); |
| | | |
| | | // 计ç®éæ¶éè·¯å¾ |
| | | List<Point> pathCounterClockwise = new ArrayList<>(); |
| | | pathCounterClockwise.add(start); |
| | | curr = startEdgeIndex; |
| | | while (curr != endEdgeIndex) { |
| | | pathCounterClockwise.add(boundary.get(curr)); |
| | | curr = (curr - 1 + n) % n; |
| | | } |
| | | pathCounterClockwise.add(end); |
| | | |
| | | // éæ©è¾ççè·¯å¾ |
| | | List<Point> chosenPath = getPathLength(pathClockwise) < getPathLength(pathCounterClockwise) |
| | | ? pathClockwise : pathCounterClockwise; |
| | | |
| | | // 转æ¢ä¸ºè·¯å¾æ®µ |
| | | for (int i = 0; i < chosenPath.size() - 1; i++) { |
| | | if (distance(chosenPath.get(i), chosenPath.get(i + 1)) > 1e-6) { |
| | | result.add(new PathSegment(chosenPath.get(i), chosenPath.get(i + 1), false)); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 计ç®è·¯å¾æ»é¿åº¦ |
| | | */ |
| | | private static double getPathLength(List<Point> path) { |
| | | double len = 0; |
| | | for (int i = 0; i < path.size() - 1; i++) { |
| | | len += distance(path.get(i), path.get(i + 1)); |
| | | } |
| | | return len; |
| | | } |
| | | |
| | | /** |
| | | * å¤æä¸¤ä¸ªç¹æ¯å¦ç¸åï¼èèæµ®ç¹è¯¯å·®ï¼ |
| | | */ |
| | | private static boolean isSamePoint(Point a, Point b) { |
| | | return Math.abs(a.x - b.x) < 1e-6 && Math.abs(a.y - b.y) < 1e-6; |
| | | } |
| | | |
| | | /** |
| | | * 夿䏤æ¡çº¿æ®µæ¯å¦ç¸äº¤ï¼ä¸å
æ¬ç«¯ç¹ï¼ |
| | | */ |
| | | private static boolean segmentsIntersect(Point a, Point b, Point c, Point d) { |
| | | return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d); |
| | | } |
| | | |
| | | /** |
| | | * 夿ä¸ç¹æ¯å¦éæ¶éæå |
| | | */ |
| | | private static boolean ccw(Point a, Point b, Point c) { |
| | | return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x); |
| | | } |
| | | |
| | | /** |
| | | * 交ç¹ä¿¡æ¯å
é¨ç±» |
| | | */ |
| | | private static class IntersectionInfo { |
| | | Point point; // 交ç¹åæ |
| | | int edgeIndex; // äº¤ç¹æå¨çè¾¹çè¾¹ç´¢å¼ |
| | | |
| | | IntersectionInfo(Point point, int edgeIndex) { |
| | | this.point = point; |
| | | this.edgeIndex = edgeIndex; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è¾¹çå¸éç»æå
é¨ç±» |
| | | */ |
| | | private static class SnapResult { |
| | | Point onEdge; // å¨è¾¹çä¸çæå½±ç¹ |
| | | int edgeIndex; // æå¨çè¾¹çè¾¹ç´¢å¼ |
| | | |
| | | SnapResult(Point p, int idx) { |
| | | this.onEdge = p; |
| | | this.edgeIndex = idx; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计ç®ç¹å°è¾¹çæè¿çæå½±ç¹ä»¥åæå¨è¾¹ç´¢å¼ |
| | | * @param p è¦å¸éçç¹ |
| | | * @param poly è¾¹çå¤è¾¹å½¢ |
| | | * @return å¸éç»æ |
| | | */ |
| | | private static SnapResult snapToBoundary(Point p, List<Point> poly) { |
| | | double minD = Double.MAX_VALUE; |
| | | Point bestProj = p; |
| | | int bestIdx = -1; |
| | | for (int i = 0; i < poly.size(); i++) { |
| | | Point s = poly.get(i); |
| | | Point e = poly.get((i + 1) % poly.size()); |
| | | double l2 = (s.x - e.x) * (s.x - e.x) + (s.y - e.y) * (s.y - e.y); |
| | | if (l2 < 1e-10) { |
| | | double d = Math.hypot(p.x - s.x, p.y - s.y); |
| | | if (d < minD) { |
| | | minD = d; |
| | | bestProj = s; |
| | | bestIdx = i; |
| | | } |
| | | continue; |
| | | } |
| | | double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2; |
| | | t = Math.max(0, Math.min(1, t)); |
| | | Point proj = new Point(s.x + t * (e.x - s.x), s.y + t * (e.y - s.y)); |
| | | double d = Math.hypot(p.x - proj.x, p.y - proj.y); |
| | | if (d < minD) { |
| | | minD = d; |
| | | bestProj = proj; |
| | | bestIdx = i; |
| | | } |
| | | } |
| | | return new SnapResult(bestProj, bestIdx == -1 ? 0 : bestIdx); |
| | | } |
| | | |
| | | /** |
| | | * å¤æç¹æ¯å¦å¨è¾¹çä¸ï¼è·ç¦»è¾¹çå¾è¿ï¼ |
| | | * @param p è¦æ£æ¥çç¹ |
| | | * @param boundary è¾¹çå¤è¾¹å½¢ |
| | | * @return æ¯å¦å¨è¾¹çä¸ |
| | | */ |
| | | @SuppressWarnings("unused") |
| | | private static boolean isPointOnBoundary(Point p, List<Point> boundary) { |
| | | double threshold = 1e-4; // éå¼ï¼èèæµ®ç¹è¯¯å·® |
| | | for (int i = 0; i < boundary.size(); i++) { |
| | | Point s = boundary.get(i); |
| | | Point e = boundary.get((i + 1) % boundary.size()); |
| | | double dist = distToSegment(p, s, e); |
| | | if (dist < threshold) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * 计ç®ç¹å°çº¿æ®µçè·ç¦» |
| | | * @param p ç¹ |
| | | * @param s çº¿æ®µèµ·ç¹ |
| | | * @param e 线段ç»ç¹ |
| | | * @return è·ç¦» |
| | | */ |
| | | private static double distToSegment(Point p, Point s, Point e) { |
| | | double l2 = (s.x - e.x) * (s.x - e.x) + (s.y - e.y) * (s.y - e.y); |
| | | if (l2 < 1e-10) { |
| | | return Math.hypot(p.x - s.x, p.y - s.y); |
| | | } |
| | | double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2; |
| | | t = Math.max(0, Math.min(1, t)); |
| | | return Math.hypot(p.x - (s.x + t * (e.x - s.x)), p.y - (s.y + t * (e.y - s.y))); |
| | | } |
| | | |
| | | /** |
| | | * çæåå§æ«æè·¯å¾ï¼æ éç¢ç©çæ¬ï¼ |
| | | */ |
| | | private static List<PathSegment> generateGlobalScanPath( |
| | |
| | | boolean endInside = isPointInPolygon(s.end, polygon); |
| | | boolean intersects = segmentIntersectsBoundary(s.start, s.end, polygon); |
| | | if (!s.isMowing) { |
| | | // éä½ä¸æ®µç»ä¸æ¿æ¢ä¸ºæ²¿è¾¹çè·¯å¾ |
| | | // éä½ä¸æ®µï¼è¥ABç´çº¿å®å
¨ï¼ä¸è·¨è¶è¾¹çä¸ä¸ç¹å¨å
ï¼ï¼ä¿çç´çº¿ï¼å¦åæ²¿è¾¹çæ¿æ¢ |
| | | if (isSegmentSafe(s.start, s.end, polygon)) { |
| | | sanitized.add(s); |
| | | } else { |
| | | List<Point> path = getBoundaryPathWithSnap(s.start, s.end, polygon); |
| | | for (int i = 0; i < path.size() - 1; i++) { |
| | | sanitized.add(new PathSegment(path.get(i), path.get(i+1), false)); |
| | | } |
| | | } |
| | | } else { |
| | | if (!startInside || !endInside || intersects) { |
| | | SnapResult s1 = snapToBoundary(s.start, polygon); |
| | |
| | | } |
| | | |
| | | PathSegment s = lineSegmentsInRow.get(idxInRow); |
| | | // 馿¬¡è¿æ¥æè·¨åºè¿æ¥åå¼ºå¶æ²¿è¾¹çï¼é¿å
ç©¿è¶å¹é·åº |
| | | // 馿¬¡æè·¨åºè¿æ¥ï¼ä½¿ç¨æºè½æ¢è¡è·¯å¾ï¼ä¼å
ç´çº¿æçï¼å¿
è¦æ¶æ²¿è¾¹çï¼ |
| | | if (Math.hypot(currentPos.x - s.start.x, currentPos.y - s.start.y) > 0.01) { |
| | | addBoundaryConnection(segments, currentPos, s.start, polygon); |
| | | addSafeConnection(segments, currentPos, s.start, polygon); |
| | | firstSegmentConnected = true; |
| | | } |
| | | segments.add(s); |
| | |
| | | } |
| | | |
| | | private static void addSafeConnection(List<PathSegment> segments, Point start, Point end, List<Point> polygon) { |
| | | if (!FORCE_BOUNDARY_TRAVEL && isSegmentSafe(start, end, polygon)) { |
| | | segments.add(new PathSegment(start, end, false)); |
| | | return; |
| | | // ä½¿ç¨æ°çæºè½æ¢è¡è·¯å¾ç®æ³ |
| | | List<PathSegment> connectionPath = generateSmartConnectionPath(start, end, polygon); |
| | | segments.addAll(connectionPath); |
| | | } |
| | | List<Point> path = getBoundaryPathWithSnap(start, end, polygon); |
| | | |
| | | /** |
| | | * æºè½æ¢è¡è·¯å¾çæï¼æ ¹æ®AB线段ä¸å®å
¨è¾¹çCç交ç¹ï¼çææ··åè·¯å¾ |
| | | * é»è¾ï¼ |
| | | * 1. 妿AB线段ä¸ç©¿è¶è¾¹çï¼ç´æ¥è¿åABç´çº¿ |
| | | * 2. 妿AB线段穿è¶è¾¹çï¼æ¾å°ææäº¤ç¹ï¼å¦D, F, G, Hï¼ï¼çæè·¯å¾ï¼ |
| | | * A -> D -> (沿边çDå°F) -> F -> G -> (沿边çGå°H) -> H -> B |
| | | */ |
| | | private static List<PathSegment> generateSmartConnectionPath(Point A, Point B, List<Point> polygon) { |
| | | List<PathSegment> result = new ArrayList<>(); |
| | | |
| | | // 1. æ£æ¥ABç´çº¿æ¯å¦å®å
¨ï¼ä¸ç©¿è¶è¾¹çä¸ä¸ç¹å¨å
é¨ï¼ï¼ä¸æ¯å¦å¼ºå¶æ²¿è¾¹çæ å
³ |
| | | if (isSegmentSafe(A, B, polygon)) { |
| | | result.add(new PathSegment(A, B, false)); |
| | | return result; |
| | | } |
| | | |
| | | // 2. è·åABä¸è¾¹ççææäº¤ç¹ |
| | | List<IntersectionPoint> intersections = getSegmentBoundaryIntersections(A, B, polygon); |
| | | |
| | | // 3. å¦ææ²¡æäº¤ç¹ä½ä¸å®å
¨ï¼è¯´ææ´æ¡çº¿é½å¨å¤é¨ï¼å¼ºå¶æ²¿è¾¹ç |
| | | if (intersections.isEmpty()) { |
| | | List<Point> path = getBoundaryPathWithSnap(A, B, polygon); |
| | | for (int i = 0; i < path.size() - 1; i++) { |
| | | segments.add(new PathSegment(path.get(i), path.get(i+1), false)); |
| | | result.add(new PathSegment(path.get(i), path.get(i+1), false)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | // 4. æ ¹æ®äº¤ç¹æå¯¹å¤çï¼èèA/Bæ¯å¦å¨å
ä¾§ |
| | | Point currentPos = A; |
| | | boolean startInside = isPointInPolygon(A, polygon); |
| | | boolean endInside = isPointInPolygon(B, polygon); |
| | | |
| | | for (int i = 0; i < intersections.size(); i += 2) { |
| | | IntersectionPoint entry = intersections.get(i); |
| | | |
| | | // ä»å½åä½ç½®å°ç¬¬ä¸ä¸ªäº¤ç¹ï¼èµ·ç¹å¨å
âç´çº¿ï¼èµ·ç¹å¨å¤â沿边çå°äº¤ç¹ |
| | | if (Math.hypot(currentPos.x - entry.point.x, currentPos.y - entry.point.y) > 1e-6) { |
| | | if (startInside) { |
| | | result.add(new PathSegment(currentPos, entry.point, false)); |
| | | } else { |
| | | List<Point> pathToEntry = getBoundaryPathWithSnap(currentPos, entry.point, polygon); |
| | | for (int j = 0; j < pathToEntry.size() - 1; j++) { |
| | | result.add(new PathSegment(pathToEntry.get(j), pathToEntry.get(j+1), false)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥æ¯å¦æé
对ç离å¼ç¹ |
| | | if (i + 1 < intersections.size()) { |
| | | IntersectionPoint exit = intersections.get(i + 1); |
| | | |
| | | // ä»è¿å
¥ç¹Då°ç¦»å¼ç¹Fï¼æ²¿è¾¹çè¡èµ° |
| | | List<Point> boundaryPath = getBoundaryPathBetweenPoints( |
| | | entry.point, entry.edgeIndex, |
| | | exit.point, exit.edgeIndex, |
| | | polygon |
| | | ); |
| | | |
| | | for (int j = 0; j < boundaryPath.size() - 1; j++) { |
| | | result.add(new PathSegment(boundaryPath.get(j), boundaryPath.get(j+1), false)); |
| | | } |
| | | |
| | | currentPos = exit.point; |
| | | // ä¹åçèµ·ç¹è§ä¸ºå
ä¾§ |
| | | startInside = true; |
| | | } else { |
| | | // å¦ææ²¡æé
对ç离å¼ç¹ï¼è¯´æç»ç¹å¨å¤é¨ï¼æ²¿è¾¹çå°B |
| | | List<Point> path = getBoundaryPathWithSnap(entry.point, B, polygon); |
| | | for (int j = 0; j < path.size() - 1; j++) { |
| | | result.add(new PathSegment(path.get(j), path.get(j+1), false)); |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | // 5. 仿åä¸ä¸ªç¦»å¼ç¹å°ç»ç¹Bï¼ç´çº¿æ®µï¼å¨å
é¨ï¼ |
| | | if (Math.hypot(currentPos.x - B.x, currentPos.y - B.y) > 1e-6) { |
| | | if (endInside) { |
| | | result.add(new PathSegment(currentPos, B, false)); |
| | | } else { |
| | | List<Point> pathToB = getBoundaryPathWithSnap(currentPos, B, polygon); |
| | | for (int j = 0; j < pathToB.size() - 1; j++) { |
| | | result.add(new PathSegment(pathToB.get(j), pathToB.get(j+1), false)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * å¨å·²ç¥è¾¹ç边索å¼çæ
åµä¸ï¼æ²¿è¾¹çè·å两ç¹ä¹é´çæçè·¯å¾ |
| | | */ |
| | | private static List<Point> getBoundaryPathBetweenPoints( |
| | | Point start, int startEdgeIdx, |
| | | Point end, int endEdgeIdx, |
| | | List<Point> polygon) { |
| | | |
| | | int n = polygon.size(); |
| | | |
| | | // 妿å¨å䏿¡è¾¹ä¸ï¼ç´æ¥è¿æ¥ |
| | | if (startEdgeIdx == endEdgeIdx) { |
| | | return Arrays.asList(start, end); |
| | | } |
| | | |
| | | // æ£åè·¯å¾ï¼é¡ºè¾¹ï¼ |
| | | List<Point> pathFwd = new ArrayList<>(); |
| | | pathFwd.add(start); |
| | | int curr = startEdgeIdx; |
| | | while (curr != endEdgeIdx) { |
| | | pathFwd.add(polygon.get((curr + 1) % n)); |
| | | curr = (curr + 1) % n; |
| | | } |
| | | pathFwd.add(end); |
| | | |
| | | // ååè·¯å¾ï¼éè¾¹ï¼ |
| | | List<Point> pathRev = new ArrayList<>(); |
| | | pathRev.add(start); |
| | | curr = startEdgeIdx; |
| | | while (curr != endEdgeIdx) { |
| | | pathRev.add(polygon.get(curr)); |
| | | curr = (curr - 1 + n) % n; |
| | | } |
| | | pathRev.add(end); |
| | | |
| | | // è¿åå ä½é¿åº¦æ´ççè·¯å¾ |
| | | return getPathLength(pathFwd) <= getPathLength(pathRev) ? pathFwd : pathRev; |
| | | } |
| | | |
| | | // å¼ºå¶æ²¿è¾¹çç»è¡çè¿æ¥ï¼ä¸åç´çº¿å®å
¨å¤æï¼ï¼ç¨æ¥å¨å䏿«æè¡çå¤ä¸ªä½ä¸æ®µä¹é´è·³è½¬ |
| | |
| | | return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d); |
| | | } |
| | | |
| | | // è·å线段ABä¸è¾¹çå¤è¾¹å½¢çææäº¤ç¹ï¼æç
§è·ç¦»Aç¹çé¡ºåºæåº |
| | | private static class IntersectionPoint { |
| | | Point point; // 交ç¹åæ |
| | | int edgeIndex; // äº¤ç¹æå¨çè¾¹çè¾¹ç´¢å¼ |
| | | double distFromStart; // è·ç¦»èµ·ç¹Açè·ç¦» |
| | | boolean isEntry; // true表示è¿å
¥è¾¹çï¼false表示离å¼è¾¹ç |
| | | |
| | | IntersectionPoint(Point p, int idx, double dist, boolean entry) { |
| | | this.point = p; |
| | | this.edgeIndex = idx; |
| | | this.distFromStart = dist; |
| | | this.isEntry = entry; |
| | | } |
| | | } |
| | | |
| | | // 计ç®çº¿æ®µABä¸å¤è¾¹å½¢è¾¹ççææäº¤ç¹ |
| | | private static List<IntersectionPoint> getSegmentBoundaryIntersections(Point A, Point B, List<Point> polygon) { |
| | | List<IntersectionPoint> intersections = new ArrayList<>(); |
| | | for (int i = 0; i < polygon.size(); i++) { |
| | | Point C = polygon.get(i); |
| | | Point D = polygon.get((i + 1) % polygon.size()); |
| | | Point intersection = getLineSegmentIntersection(A, B, C, D); |
| | | if (intersection != null) { |
| | | if (isSamePoint(intersection, A) || isSamePoint(intersection, B)) continue; |
| | | double dist = Math.hypot(intersection.x - A.x, intersection.y - A.y); |
| | | intersections.add(new IntersectionPoint(intersection, i, dist, false)); |
| | | } |
| | | } |
| | | // è·ç¦»æåº |
| | | Collections.sort(intersections, (i1, i2) -> Double.compare(i1.distFromStart, i2.distFromStart)); |
| | | // å»éè¿ä¼¼ç¸å交ç¹ï¼é¡¶ç¹åäº¤ï¼ |
| | | List<IntersectionPoint> deduped = new ArrayList<>(); |
| | | for (IntersectionPoint ip : intersections) { |
| | | boolean dup = false; |
| | | for (IntersectionPoint kept : deduped) { |
| | | if (Math.abs(ip.point.x - kept.point.x) < 1e-6 && Math.abs(ip.point.y - kept.point.y) < 1e-6) { dup = true; break; } |
| | | } |
| | | if (!dup) deduped.add(ip); |
| | | } |
| | | return deduped; |
| | | } |
| | | |
| | | // 计ç®ä¸¤æ¡çº¿æ®µç交ç¹ï¼å¦æåå¨ï¼ |
| | | private static Point getLineSegmentIntersection(Point p1, Point p2, Point p3, Point p4) { |
| | | double x1 = p1.x, y1 = p1.y; |
| | | double x2 = p2.x, y2 = p2.y; |
| | | double x3 = p3.x, y3 = p3.y; |
| | | double x4 = p4.x, y4 = p4.y; |
| | | |
| | | double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); |
| | | if (Math.abs(denom) < 1e-10) return null; // å¹³è¡æéå |
| | | |
| | | double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom; |
| | | double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom; |
| | | |
| | | // æ£æ¥äº¤ç¹æ¯å¦å¨ä¸¤æ¡çº¿æ®µä¸ï¼ä½¿ç¨ç¥å¾®å®½æ¾çèå´ä»¥å¤çæµ®ç¹è¯¯å·®ï¼ |
| | | double epsilon = 1e-6; |
| | | if (t >= -epsilon && t <= 1 + epsilon && u >= -epsilon && u <= 1 + epsilon) { |
| | | return new Point(x1 + t * (x2 - x1), y1 + t * (y2 - y1)); |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | private static boolean ccw(Point a, Point b, Point c) { |
| | | return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x); |
| | | } |
| | |
| | | * æ·»å AoxinglujingNoObstacle.Point å°å符串æå»ºå¨ |
| | | */ |
| | | private void appendAoxingPoint(StringBuilder sb, AoxinglujingNoObstacle.Point point) { |
| | | private void appendAoxingPointWithType(StringBuilder sb, AoxinglujingNoObstacle.Point point, boolean isMowing) { |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(String.format(java.util.Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T")); |
| | | } |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * æ·»å AoxinglujingNoObstacle.Point å°å符串æå»ºå¨ï¼å¸¦ç±»åæ è®°ï¼ |
| | | */ |
| | | private void appendAoxingPointWithType(StringBuilder sb, AoxinglujingNoObstacle.Point point, boolean isMowing) { |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(String.format(Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T")); |
| | | } |
| | | |
| | | /** |
| | | * æ¯è¾ä¸¤ä¸ª YixinglujingNoObstacle.Point æ¯å¦ç¸åï¼ä½¿ç¨å°çå®¹å·®ï¼ |
| | | */ |
| | | private boolean equalsYixingPoint(YixinglujingNoObstacle.Point p1, YixinglujingNoObstacle.Point p2) { |
| | |
| | | * æ·»å YixinglujingNoObstacle.Point å°å符串æå»ºå¨ |
| | | */ |
| | | private void appendYixingPoint(StringBuilder sb, YixinglujingNoObstacle.Point point) { |
| | | private void appendYixingPointWithType(StringBuilder sb, YixinglujingNoObstacle.Point point, boolean isMowing) { |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(String.format(java.util.Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T")); |
| | | } |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y)); |
| | | } |
| | | |
| | | /** |
| | | * æ·»å YixinglujingNoObstacle.Point å°å符串æå»ºå¨ï¼å¸¦ç±»åæ è®°ï¼ |
| | | */ |
| | | private void appendYixingPointWithType(StringBuilder sb, YixinglujingNoObstacle.Point point, boolean isMowing) { |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(String.format(Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T")); |
| | | } |
| | | |
| | | private void previewMowingPath() { |
| | | if (!hasGeneratedPath()) { |
| | | showPathGenerationMessage("请å
çæå²èè·¯å¾ååé¢è§ã", false); |
| | |
| | | |
| | | drawMower(g2d); |
| | | |
| | | // ç»å¶å¯¼èªé¢è§é度ï¼å¦ææ£å¨å¯¼èªé¢è§ï¼ |
| | | if (navigationPreviewSpeed > 0 && mower != null && mower.hasValidPosition()) { |
| | | drawNavigationPreviewSpeed(g2d, scale); |
| | | } |
| | | // å·²æéæ±ç§»é¤ï¼ä¸å¨å²èæºå¾æ 䏿¹æ¾ç¤ºé度 |
| | | |
| | | // ç»å¶æµé模å¼ï¼å¦ææ¿æ´»ï¼ |
| | | if (measurementModeActive) { |
| | |
| | | g2d.setFont(labelFont); |
| | | FontMetrics metrics = g2d.getFontMetrics(labelFont); |
| | | |
| | | // è®¡ç®æåä½ç½®ï¼å¨å²èæºå¾æ 䏿¹ï¼ |
| | | // è®¡ç®æåä½ç½®ï¼å¨å²èæºå¾æ æ£ä¸æ¹ï¼é´é20åç´ åºå®ï¼ |
| | | int textWidth = metrics.stringWidth(speedText); |
| | | int textHeight = metrics.getHeight(); |
| | | int textX = (int)Math.round(screenPos.x - textWidth / 2.0); |
| | | // å¨å²èæºå¾æ 䏿¹ï¼çåºä¸å®é´è· |
| | | // 徿 å¨ä¸çåæ ç³»ä¸ç大å°çº¦ä¸º 48 * 0.8 / scale ç±³ |
| | | // 转æ¢ä¸ºå±å¹åç´ ï¼å¾æ é«åº¦ï¼åç´ ï¼= (48 * 0.8 / scale) * scale = 48 * 0.8 = 38.4 åç´ |
| | | double iconSizePixels = 48.0 * 0.8; // 徿 å¨å±å¹ä¸ç大å°ï¼åç´ ï¼ |
| | | int spacing = 8; // é´è·ï¼åç´ ï¼ |
| | | int textY = (int)Math.round(screenPos.y - iconSizePixels / 2.0 - spacing - textHeight); |
| | | // å¨å²èæºå¾æ æ£ä¸æ¹ï¼é´é20åç´ |
| | | // ä»mower对象è·å徿 å¨ä¸çåæ ç³»ä¸çåå¾ï¼ç¶å转æ¢ä¸ºå±å¹åç´ |
| | | double iconWorldRadius = mower.getWorldRadius(scale); |
| | | double iconSizePixels = Double.isNaN(iconWorldRadius) ? 38.4 : (iconWorldRadius * 2.0 * scale); // 徿 å¨å±å¹ä¸ç大å°ï¼åç´ ï¼ |
| | | int spacing = 20; // é´è·ï¼åç´ ï¼ |
| | | // 徿 é¡¶é¨ä½ç½® = 徿 ä¸å¿Y - 徿 é«åº¦/2 |
| | | double iconTopY = screenPos.y - iconSizePixels / 2.0; |
| | | // 精确约æï¼è®©èæ¯ç©å½¢çåºè¾¹ä¸å¾æ é¡¶é¨ç¸è·åºå®spacingåç´ |
| | | // èæ¯åºè¾¹ = textY + metrics.getDescent() + 2ï¼ç©å½¢ä¸æ¹å
è¾¹è·ï¼ |
| | | // 令 èæ¯åºè¾¹ = iconTopY - spacingï¼è§£å¾ï¼ |
| | | int textY = (int)Math.round(iconTopY - spacing - metrics.getDescent() - 2); |
| | | |
| | | // ç»å¶æåèæ¯ï¼åéæç½è²ï¼å¢å¼ºå¯è¯»æ§ï¼ |
| | | g2d.setColor(new Color(255, 255, 255, 200)); |
| | |
| | | mowingProgressLabel.setForeground(THEME_COLOR); |
| | | |
| | | mowerSpeedValueLabel = new JLabel("--"); |
| | | mowerSpeedValueLabel.setFont(new Font("微软é
é»", Font.BOLD, 14)); |
| | | // æ°å¼å¤§å°ä¸åä½ km/h æ¾ç¤ºå¤§å°ä¸è´ |
| | | mowerSpeedValueLabel.setFont(new Font("微软é
é»", Font.BOLD, 9)); |
| | | mowerSpeedValueLabel.setForeground(THEME_COLOR); |
| | | |
| | | mowerSpeedUnitLabel = new JLabel("km/h"); |
| | |
| | | String display = "--"; |
| | | Device device = Device.getGecaoji(); |
| | | if (device != null) { |
| | | String sanitized = sanitizeSpeedValue(device.getRealtimeSpeed()); |
| | | // ä½¿ç¨ yaw å¼ä½ä¸ºé度æ¾ç¤ºï¼åä½ï¼km/hï¼ |
| | | String sanitized = sanitizeSpeedValue(device.getYaw()); |
| | | if (sanitized != null) { |
| | | // yaw为0æ¶æ¾ç¤ºä¸º"0" |
| | | try { |
| | | double v = Double.parseDouble(sanitized); |
| | | if (Math.abs(v) < 1e-9) { |
| | | display = "0"; |
| | | } else { |
| | | display = sanitized; |
| | | } |
| | | } catch (NumberFormatException ex) { |
| | | display = sanitized; |
| | | } |
| | | } |
| | | } |
| | | mowerSpeedValueLabel.setText(display); |
| | | if (mowerSpeedUnitLabel != null) { |