yincheng.zhong
3 天以前 30303d366d1a0d857357c90bed876686f2d1e603
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
"""
Simple path planner to generate approach path from mower start position to planned path start.
"""
import numpy as np
from math import hypot, atan2, cos, sin, pi
 
 
def plan_approach_path(start_pos, start_heading, target_pos, target_heading, method='line'):
    """
    Generate approach path from mower start to planned path start.
    
    Args:
        start_pos: (x, y) mower starting position
        start_heading: mower starting heading in radians
        target_pos: (x, y) planned path starting position
        target_heading: planned path starting heading in radians
        method: 'line' | 'dubins' | 'smooth'
    
    Returns:
        List of (x, y) waypoints forming the approach path
    """
    sx, sy = start_pos
    tx, ty = target_pos
    dist = hypot(tx - sx, ty - sy)
    
    if dist < 0.05:  # Already at start, no approach needed
        return [start_pos, target_pos]
    
    if method == 'line':
        # Simple straight line with waypoints every 0.5m
        num_points = max(3, int(dist / 0.5) + 1)
        path = []
        for i in range(num_points):
            t = i / (num_points - 1)
            x = sx + t * (tx - sx)
            y = sy + t * (ty - sy)
            path.append((x, y))
        return path
    
    elif method == 'smooth':
        # Smooth curve considering start and target headings
        # Use cubic bezier curve
        # Control points based on headings
        ctrl_dist = min(dist * 0.4, 2.0)  # Control point distance
        
        # Start control point along start heading
        c1x = sx + ctrl_dist * cos(start_heading)
        c1y = sy + ctrl_dist * sin(start_heading)
        
        # End control point along target heading (backwards)
        c2x = tx - ctrl_dist * cos(target_heading)
        c2y = ty - ctrl_dist * sin(target_heading)
        
        # Generate bezier curve points
        num_points = max(5, int(dist / 0.3) + 1)
        path = []
        for i in range(num_points):
            t = i / (num_points - 1)
            # Cubic bezier formula
            x = (1-t)**3 * sx + 3*(1-t)**2*t * c1x + 3*(1-t)*t**2 * c2x + t**3 * tx
            y = (1-t)**3 * sy + 3*(1-t)**2*t * c1y + 3*(1-t)*t**2 * c2y + t**3 * ty
            path.append((x, y))
        return path
    
    else:
        raise ValueError(f"Unknown method: {method}")
 
 
def compute_path_heading(path, idx=0):
    """Compute heading of path at given index."""
    if idx >= len(path) - 1:
        idx = len(path) - 2
    if idx < 0:
        idx = 0
    
    dx = path[idx + 1][0] - path[idx][0]
    dy = path[idx + 1][1] - path[idx][1]
    return atan2(dy, dx)
 
 
def combine_paths(approach_path, work_path):
    """
    Combine approach path and work path into single path with metadata.
    
    Returns:
        combined_path: List of (x, y) waypoints
        approach_end_idx: Index where approach path ends (work path starts)
    """
    # Remove duplicate point at junction if exists
    if len(approach_path) > 0 and len(work_path) > 0:
        if hypot(approach_path[-1][0] - work_path[0][0], 
                 approach_path[-1][1] - work_path[0][1]) < 0.01:
            combined = approach_path[:-1] + work_path
            approach_end_idx = len(approach_path) - 1
        else:
            combined = approach_path + work_path
            approach_end_idx = len(approach_path)
    else:
        combined = approach_path + work_path
        approach_end_idx = len(approach_path)
    
    return combined, approach_end_idx
 
 
if __name__ == '__main__':
    # Test path planning
    import json
    import matplotlib.pyplot as plt
    
    # Load planned path
    with open('example_path.json', 'r') as f:
        work_path = json.load(f)
    
    # Mower starts 2m away from path start
    mower_start = (work_path[0][0] - 1.5, work_path[0][1] - 1.5)
    mower_heading = pi / 4  # 45 degrees
    
    # Plan path heading
    plan_heading = compute_path_heading(work_path, 0)
    
    # Generate approach paths with different methods
    approach_line = plan_approach_path(mower_start, mower_heading, 
                                       work_path[0], plan_heading, 'line')
    approach_smooth = plan_approach_path(mower_start, mower_heading,
                                        work_path[0], plan_heading, 'smooth')
    
    # Plot comparison
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
    
    # Line method
    ax1.plot([p[0] for p in work_path], [p[1] for p in work_path], 
             'b-', linewidth=2, label='Work Path')
    ax1.plot([p[0] for p in approach_line], [p[1] for p in approach_line],
             'r--', linewidth=2, marker='o', markersize=4, label='Approach (Line)')
    ax1.plot(mower_start[0], mower_start[1], 'go', markersize=10, label='Mower Start')
    ax1.arrow(mower_start[0], mower_start[1], 0.3*cos(mower_heading), 0.3*sin(mower_heading),
              head_width=0.15, head_length=0.1, fc='g', ec='g')
    ax1.set_xlabel('X (m)')
    ax1.set_ylabel('Y (m)')
    ax1.set_title('Line Approach')
    ax1.legend()
    ax1.grid(True)
    ax1.axis('equal')
    
    # Smooth method
    ax2.plot([p[0] for p in work_path], [p[1] for p in work_path],
             'b-', linewidth=2, label='Work Path')
    ax2.plot([p[0] for p in approach_smooth], [p[1] for p in approach_smooth],
             'r--', linewidth=2, marker='o', markersize=4, label='Approach (Smooth)')
    ax2.plot(mower_start[0], mower_start[1], 'go', markersize=10, label='Mower Start')
    ax2.arrow(mower_start[0], mower_start[1], 0.3*cos(mower_heading), 0.3*sin(mower_heading),
              head_width=0.15, head_length=0.1, fc='g', ec='g')
    ax2.set_xlabel('X (m)')
    ax2.set_ylabel('Y (m)')
    ax2.set_title('Smooth Approach')
    ax2.legend()
    ax2.grid(True)
    ax2.axis('equal')
    
    plt.tight_layout()
    plt.savefig('approach_path_test.png', dpi=150)
    print("Saved approach_path_test.png")
    print(f"Line approach: {len(approach_line)} points")
    print(f"Smooth approach: {len(approach_smooth)} points")