-
Notifications
You must be signed in to change notification settings - Fork 0
/
Astar_lineofsight_optimisation.py
228 lines (220 loc) · 10.6 KB
/
Astar_lineofsight_optimisation.py
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
import math
import copy
from icecream import ic
from route_opt_utils import CircleObstacle
from Astar_algorithm_multigoal import GridNode, GridPath
# smooth final path using Line of Sight (LOS) algorithm
# level 3: path takes all riskzones into account,
# uses path optimisations, such as
# transition nodes with local riskzones
# higher quality results
# check LOS to goal for each node walking from start to goal
# farthest node from goal with LOS becomes node in LOS path
# from this node check LOS for each node from start, etc.
def create_LOS_goalpaths_level3(goalpaths:GridPath,goalfound:bool) -> GridPath | bool:
# todo check goal found
if not goalfound:
return False
# create LOS path for each goalpath
goalpath:GridPath # todo declare in for statement?
for goalpath in goalpaths:
# mark all transitions in goalpath from inside to outside of a riskzone
# and from outside to inside of a riskzone
node:GridNode = goalpath.goalnode
goalpath.transition_nodes.append(goalpath.goalnode)
while node.location is not goalpath.startnode.location:
node_risk_multiplier = node.risk_multiplier
previousnode:GridNode = node.goalpath_parent
previousnode_risk_multiplier = previousnode.risk_multiplier
if (node_risk_multiplier > 1
and previousnode_risk_multiplier == 1.0
and previousnode.location is not goalpath.startnode.location):
goalpath.transition_nodes.append(previousnode)
if (previousnode_risk_multiplier > 1
and node_risk_multiplier == 1.0
and node.location is not goalpath.goalnode.location):
goalpath.transition_nodes.append(node)
# node moves 1 up the line
node = previousnode
# add startnode to transition_nodes
goalpath.transition_nodes.append(goalpath.startnode)
# copy list for use later
transition_nodes_list = goalpath.transition_nodes.copy()
# LOS checks in sections between transition nodes
# this gives a better final LOS path
# use transition nodes list and pop first until walked through entire list
while len(transition_nodes_list) > 1:
LOS_basenode:GridNode = transition_nodes_list.pop(0)
node = transition_nodes_list[0]
# find local riskzones, between LOS basenode and node
LOS_localpath = GridPath(LOS_basenode)
stepnode:GridNode = LOS_basenode
while stepnode.location is not node.location:
# todo update LOS_localpath riskzones
self.update_gridpath_riskzones(stepnode,LOS_localpath)
# node moves 1 up the line
previousnode = stepnode.goalpath_parent
stepnode = previousnode
# todo include last node, mainly when node is startnode
self.update_gridpath_riskzones(stepnode,LOS_localpath)
# Line of Sight checks
while LOS_basenode.location is not node.location:
# if node has LOS with basenode, add it to the LOS path
# todo node then becomes the next LOS basenode
if not self.cross_riskzone(node,LOS_basenode,LOS_localpath):
# node and LOS basenode become each others parent and child
LOS_basenode.LOSpath_parent = node
# node becomes next LOSbasenode
LOS_basenode = node
# LOS checks again from start
node = transition_nodes_list[0]
continue
# no LOS, move one node towards LOS basenode over goalpath
node = node.goalpath_child
# catch exception
if node.location == LOS_basenode.location:
print("node == LOS_basenode") # todo
break
# LOS path accessed through LOS path goalnode
# set LOS path cost
#?self.pathCostLOS(latestgoalnode)
print("LOS_goalpaths calculated")
return goalpaths
# smooth final path using Line of Sight (LOS) algorithm
# level 2: path takes all riskzones into account,
# also path direction inside riskzones covering goal
# medium quality results
# check LOS to goal for each node walking from start to goal
# farthest node from goal with LOS becomes node in LOS path
# from this node check LOS for each node from start, etc.
def create_LOS_goalpaths_level2(goalpaths:GridPath,goalfound:bool) -> GridPath | bool:
# check goal found
if not goalfound:
return False
# latestgoalnode is latest new node at goal location, becomes basenode
goalpath:GridPath # todo declare in for statement?
for goalpath in goalpaths:
# find first node outside of sams covering target
outsidenode:GridNode = goalpath.goalnode
while outsidenode.risk_multiplier > 1:
# sams covering goalnode are set to fully evade as riskzone
{goalpath.riskzones.pop(zone) for zone in outsidenode.riskzones if zone in goalpath.riskzones}
outsidenode = outsidenode.goalpath_parent
# in some cases the goalpath re-enters sams covering goalnode later
# these riskzones have to be put back in the goalpath riskzones
node:GridNode = outsidenode
while node.location is not goalpath.startnode.location:
# update goalpath riskzones
self.update_gridpath_riskzones(node,goalpath)
# node moves 1 up the line
previousnode = node.goalpath_parent
node = previousnode
# set outside node as first LOS path node after goalnode
goalpath.goalnode.LOSpath_parent = outsidenode
goalpath.highlightnode = outsidenode # todo debug
# start LOS checks at start node walking towards LOS basenode
node = goalpath.startnode
LOS_basenode:GridNode = outsidenode
while LOS_basenode.location is not goalpath.startnode.location:
# if node has LOS with basenode, add it to the LOS path
# node then becomes the next LOS basenode
if not self.cross_riskzone(node,LOS_basenode,goalpath):
# node and LOS basenode become each others parent and child
LOS_basenode.LOSpath_parent = node
# node becomes next LOSbasenode
LOS_basenode = node
# LOS checks again from start
node = goalpath.startnode
continue
# no LOS, move one node towards LOS basenode over goalpath
node = node.goalpath_child
# catch exception
if node.location == LOS_basenode.location:
print("node == LOS_basenode") # todo
break
# LOS path accessed through LOS path goalnode
# set LOS path cost
#?self.pathCostLOS(latestgoalnode)
print("LOS_goalpaths calculated")
return goalpaths
# smooth final path using Line of Sight (LOS) algorithm
# level 1: path takes all riskzones into account,
# except path direction inside riskzones covering goal
# simplest code, but lower quality results
# check LOS to goal for each node walking from start to goal
# farthest node from goal with LOS becomes node in LOS path
# from this node check LOS for each node from start, etc.
def create_LOS_goalpaths_level1(goalpaths:GridPath,goalfound:bool) -> GridPath | bool:
# check goal found
if not goalfound:
return False
# latestgoalnode is latest new node at goal location, becomes basenode
goalpath:GridPath # todo declare in for statement?
for goalpath in self.goalpaths:
# start LOS checks at start node walking towards LOS basenode
node:GridNode = goalpath.startnode
LOS_basenode:GridNode = goalpath.goalnode
while LOS_basenode.location is not goalpath.startnode.location:
# if node has LOS with basenode, add it to the LOS path
# node then becomes the next LOS basenode
if not self.cross_riskzone(node,LOS_basenode,goalpath):
# node and LOS basenode become each others parent and child
LOS_basenode.LOSpath_parent = node
# node becomes next LOSbasenode
LOS_basenode = node
# LOS checks again from start
node = goalpath.startnode
continue
# move one node towards LOS basenode over goalpath
node = node.goalpath_child
# catch exception
if node.location == LOS_basenode.location:
print("node == LOS_basenode") # todo
break
# LOS path accessed through LOS path goalnode
# set LOS path cost
#?self.pathCostLOS(latestgoalnode)
print("LOS_goalpaths calculated")
return goalpaths
# check if connection crosses a riskzone for 2 nodes
def cross_riskzone(self,node1:GridNode,node2:GridNode,goalpath:GridPath) -> bool:
# check for all obstacles
for circle in self.obstacles:
# get radius_multiplier
radius_multiplier = 1.0
if circle.location in goalpath.riskzones:
radius_multiplier = goalpath.riskzones.get(circle.location)
# check if cross circle
if self.cross_circle(node1,node2,circle,radius_multiplier):
return True
# no collision detected
return False
# check whether a line between two nodes crosses a circle
def cross_circle(self,
node1:GridNode,
node2:GridNode,
circle:CircleObstacle,
radius_multiplier:float
) -> bool:
# circle radius is zero
if radius_multiplier == 0.0:
return False
# check if line is far enough away from circle to not cross
line_length = math.dist(node1.location,node2.location)
dist1 = math.dist(node1.location,circle.location)
dist2 = math.dist(node2.location,circle.location)
if ((dist1 or dist2)
> (line_length + (circle.radius * radius_multiplier + self.SAFETYMARGIN))):
return False
# when line is close to circle
# divide connection in 100 points to check each
for i in range(0,101):
u = i/100
x = node1.location[0] * u + node2.location[0] * (1-u)
y = node1.location[1] * u + node2.location[1] * (1-u)
# collision when point is within circle radius + safety margin
if (math.dist((x,y),(circle.location))
< ((circle.radius * radius_multiplier + self.SAFETYMARGIN))):
return True
# no collision detected
return False