1+ import matplotlib .pyplot as plt
2+ import numpy as np
3+ import heapq
4+
5+ class Node :
6+ def __init__ (self , x , y ):
7+ self .pos = (x , y )
8+ self .g = float ('inf' )
9+ self .rhs = float ('inf' )
10+ self .is_obstacle = False
11+
12+ def __eq__ (self , other ):
13+ return isinstance (other , Node ) and self .pos == other .pos
14+
15+ def __hash__ (self ):
16+ return hash (self .pos )
17+
18+ def __lt__ (self , other ):
19+ return self .pos < other .pos
20+
21+ grid_size = 20
22+ grid = np .zeros ((grid_size , grid_size )) # 0: free, 1: obstacle
23+ node_dict = {}
24+ for x in range (grid_size ):
25+ for y in range (grid_size ):
26+ node_dict [(x , y )] = Node (x , y )
27+
28+ # 障礙物設定
29+ grid [5 :10 , 8 ] = 1
30+ grid [15 , 5 :15 ] = 1
31+ grid [12 , 2 :7 ] = 1
32+ grid [8 , 12 :16 ] = 1
33+ grid [1 :4 , 10 ] = 1
34+ for (x , y ), node in node_dict .items ():
35+ if grid [x ][y ] == 1 :
36+ node .is_obstacle = True
37+
38+ start = node_dict [(2 , 3 )]
39+ goal = node_dict [(17 , 14 )]
40+
41+ def h (a , b ):
42+ return abs (a .pos [0 ] - b .pos [0 ]) + abs (a .pos [1 ] - b .pos [1 ])
43+
44+ def cost (u , v ):
45+ return float ('inf' ) if v .is_obstacle else 1
46+
47+ def key (s ):
48+ return (min (s .g , s .rhs ) + h (start , s ), min (s .g , s .rhs ))
49+
50+ open_list = []
51+ open_set = set ()
52+ visited_nodes = set () # 記錄所有試圖拜訪的節點
53+
54+ def push_to_open_list (node ):
55+ if node not in open_set :
56+ heapq .heappush (open_list , (key (node ), node ))
57+ open_set .add (node )
58+ visited_nodes .add (node )
59+
60+ def pop_from_open_list ():
61+ while open_list :
62+ k , node = heapq .heappop (open_list )
63+ if node in open_set :
64+ open_set .remove (node )
65+ visited_nodes .add (node )
66+ return node
67+ return None
68+
69+ def neighbor (u ):
70+ dirs = [[0 , 1 ], [1 , 0 ], [- 1 , 0 ], [0 , - 1 ]]
71+ ns = []
72+ for dx , dy in dirs :
73+ nx , ny = u .pos [0 ] + dx , u .pos [1 ] + dy
74+ if 0 <= nx < grid_size and 0 <= ny < grid_size :
75+ ns .append (node_dict [(nx , ny )])
76+ return ns
77+
78+ def update_vertex (u ):
79+ if u != goal :
80+ u .rhs = min (cost (u , s ) + s .g for s in neighbor (u ))
81+ if u in open_set :
82+ open_set .remove (u )
83+ if u .g != u .rhs :
84+ push_to_open_list (u )
85+
86+ def compute_shortest_path ():
87+ while open_list and (key (start ) > open_list [0 ][0 ] or start .rhs != start .g ):
88+ u = pop_from_open_list ()
89+ if u is None :
90+ break
91+ if u .g > u .rhs :
92+ u .g = u .rhs
93+ for s in neighbor (u ):
94+ update_vertex (s )
95+ else :
96+ u .g = float ('inf' )
97+ update_vertex (u )
98+ for s in neighbor (u ):
99+ update_vertex (s )
100+
101+ def get_next_move (current ):
102+ candidates = []
103+ for s in neighbor (current ):
104+ if not s .is_obstacle :
105+ candidates .append ((cost (current , s ) + s .g , s ))
106+ if not candidates :
107+ return None
108+ return min (candidates )[1 ]
109+
110+ def detect_new_obstacles (current ):
111+ new_obs = []
112+ for s in neighbor (current ):
113+ if grid [s .pos [0 ]][s .pos [1 ]] == 1 and not s .is_obstacle :
114+ s .is_obstacle = True
115+ new_obs .append (s )
116+ return new_obs
117+
118+ fig , ax = plt .subplots (figsize = (6 , 6 ))
119+ plt .ion () # 開啟交互模式
120+
121+ def update_plot (current_path , visited_nodes , new_obstacles = None ):
122+ ax .clear ()
123+ display_grid = np .zeros ((grid_size , grid_size ))
124+ # 繪製試圖拜訪的節點
125+ max_g = max ([n .g for n in visited_nodes if n .g != float ('inf' )] or [1 ])
126+ for node in visited_nodes :
127+ g_value = node .g if node .g != float ('inf' ) else 0
128+ normalized_g = 0.3 + 0.5 * (g_value / max_g )
129+ display_grid [node .pos [0 ]][node .pos [1 ]] = normalized_g
130+
131+ # 標記起點、終點和障礙物
132+ display_grid [start .pos [0 ]][start .pos [1 ]] = 0.2
133+ display_grid [goal .pos [0 ]][goal .pos [1 ]] = 0.1
134+ display_grid [grid == 1 ] = 1.0
135+
136+ # 繪製地圖
137+ ax .imshow (display_grid , cmap = 'Greens' )
138+ ax .set_title ("D* Lite Dynamic Path Planning" )
139+ ax .set_xticks (np .arange (0 , grid_size , 1 ))
140+ ax .set_yticks (np .arange (0 , grid_size , 1 ))
141+ ax .set_xticks (np .arange (- 0.5 , grid_size , 1 ), minor = True )
142+ ax .set_yticks (np .arange (- 0.5 , grid_size , 1 ), minor = True )
143+ ax .grid (which = 'minor' , color = 'black' , linewidth = 0.5 )
144+ ax .invert_yaxis ()
145+
146+ # 繪製當前路徑(紅線)
147+ path_x = [node .pos [1 ] for node in current_path ]
148+ path_y = [node .pos [0 ] for node in current_path ]
149+ ax .plot (path_x , path_y , 'r-' , linewidth = 2 , label = 'current path' )
150+ ax .scatter (path_x [0 ], path_y [0 ], c = 'red' , s = 100 , marker = 'o' , label = 'start' )
151+ ax .scatter (path_x [- 1 ], path_y [- 1 ], c = 'blue' , s = 100 , marker = '*' , label = 'goal' )
152+
153+ # 標記新障礙物(如果有)
154+ if new_obstacles :
155+ for obs in new_obstacles :
156+ ax .scatter (obs .pos [1 ], obs .pos [0 ], c = 'black' , s = 100 , marker = 'x' , label = 'new obstacle' )
157+
158+ ax .legend ()
159+ plt .draw ()
160+ plt .pause (0.5 ) # 暫停以顯示動畫效果
161+
162+ new_obstacles = []
163+ def on_click (event ):
164+ if event .inaxes == ax and event .button == 1 : # 左鍵點擊
165+ x , y = int (event .ydata + 0.5 ), int (event .xdata + 0.5 )
166+ if (x , y ) != start .pos and (x , y ) != goal .pos and grid [x , y ] == 0 :
167+ grid [x , y ] = 1
168+ node_dict [(x , y )].is_obstacle = True
169+ new_obstacles .append (node_dict [(x , y )])
170+ print (f"新增障礙物: ({ x } , { y } )" )
171+
172+ fig .canvas .mpl_connect ('button_press_event' , on_click )
173+
174+ goal .rhs = 0
175+ push_to_open_list (goal )
176+ compute_shortest_path ()
177+
178+ current = start
179+ path = [current ]
180+ step_count = 0
181+
182+ print ("按左鍵點擊地圖添加新障礙物,按任意鍵繼續下一步,關閉窗口結束模擬。" )
183+ while current != goal :
184+ # 計算當前路徑
185+ next_node = get_next_move (current )
186+ if next_node is None :
187+ print ("無路徑到達終點!" )
188+ break
189+ current = next_node
190+ path .append (current )
191+ step_count += 1
192+
193+ # 檢查新障礙物(包括點擊添加的)
194+ detected_obs = detect_new_obstacles (current ) + new_obstacles
195+ if detected_obs :
196+ for u in detected_obs :
197+ for s in neighbor (u ):
198+ update_vertex (s )
199+ update_vertex (u )
200+ compute_shortest_path ()
201+ new_obstacles = [] # 清空點擊添加的障礙物
202+
203+ # 更新路徑顯示
204+ temp_path = [current ]
205+ temp_node = current
206+ while temp_node != goal :
207+ next_node = get_next_move (temp_node )
208+ if next_node is None :
209+ break
210+ temp_path .append (next_node )
211+ temp_node = next_node
212+
213+ # 顯示當前進度
214+ print (f"\n 步驟 { step_count } : 當前位置 { current .pos } " )
215+ print ("當前路徑節點:" )
216+ for node in temp_path :
217+ print (f"節點: { node .pos } , g 分數: { node .g } " )
218+ update_plot (temp_path , visited_nodes , detected_obs )
219+
220+ # 等待用戶輸入以繼續
221+ plt .waitforbuttonpress ()
222+
223+ print ("\n 最終走過的路徑節點(從起點到終點):" )
224+ for node in path :
225+ print (f"節點: { node .pos } , g 分數: { node .g } " )
226+ print ("\n 所有試圖拜訪的節點:" )
227+ for node in visited_nodes :
228+ print (f"節點: { node .pos } , g 分數: { node .g } " )
229+
230+ plt .ioff ()
231+ plt .show ()
0 commit comments