python-motion-planning 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_motion_planning/__init__.py +4 -0
- python_motion_planning/curve_generation/__init__.py +9 -0
- python_motion_planning/curve_generation/bezier_curve.py +131 -0
- python_motion_planning/curve_generation/bspline_curve.py +271 -0
- python_motion_planning/curve_generation/cubic_spline.py +128 -0
- python_motion_planning/curve_generation/curve.py +64 -0
- python_motion_planning/curve_generation/dubins_curve.py +348 -0
- python_motion_planning/curve_generation/fem_pos_smooth.py +114 -0
- python_motion_planning/curve_generation/polynomial_curve.py +226 -0
- python_motion_planning/curve_generation/reeds_shepp.py +736 -0
- python_motion_planning/global_planner/__init__.py +3 -0
- python_motion_planning/global_planner/evolutionary_search/__init__.py +4 -0
- python_motion_planning/global_planner/evolutionary_search/aco.py +186 -0
- python_motion_planning/global_planner/evolutionary_search/evolutionary_search.py +87 -0
- python_motion_planning/global_planner/evolutionary_search/pso.py +356 -0
- python_motion_planning/global_planner/graph_search/__init__.py +28 -0
- python_motion_planning/global_planner/graph_search/a_star.py +124 -0
- python_motion_planning/global_planner/graph_search/d_star.py +291 -0
- python_motion_planning/global_planner/graph_search/d_star_lite.py +188 -0
- python_motion_planning/global_planner/graph_search/dijkstra.py +77 -0
- python_motion_planning/global_planner/graph_search/gbfs.py +78 -0
- python_motion_planning/global_planner/graph_search/graph_search.py +87 -0
- python_motion_planning/global_planner/graph_search/jps.py +165 -0
- python_motion_planning/global_planner/graph_search/lazy_theta_star.py +114 -0
- python_motion_planning/global_planner/graph_search/lpa_star.py +230 -0
- python_motion_planning/global_planner/graph_search/s_theta_star.py +133 -0
- python_motion_planning/global_planner/graph_search/theta_star.py +171 -0
- python_motion_planning/global_planner/graph_search/voronoi.py +200 -0
- python_motion_planning/global_planner/sample_search/__init__.py +6 -0
- python_motion_planning/global_planner/sample_search/informed_rrt.py +152 -0
- python_motion_planning/global_planner/sample_search/rrt.py +151 -0
- python_motion_planning/global_planner/sample_search/rrt_connect.py +147 -0
- python_motion_planning/global_planner/sample_search/rrt_star.py +77 -0
- python_motion_planning/global_planner/sample_search/sample_search.py +135 -0
- python_motion_planning/local_planner/__init__.py +19 -0
- python_motion_planning/local_planner/apf.py +144 -0
- python_motion_planning/local_planner/ddpg.py +630 -0
- python_motion_planning/local_planner/dqn.py +687 -0
- python_motion_planning/local_planner/dwa.py +212 -0
- python_motion_planning/local_planner/local_planner.py +262 -0
- python_motion_planning/local_planner/lqr.py +146 -0
- python_motion_planning/local_planner/mpc.py +214 -0
- python_motion_planning/local_planner/pid.py +158 -0
- python_motion_planning/local_planner/rpp.py +147 -0
- python_motion_planning/utils/__init__.py +19 -0
- python_motion_planning/utils/agent/__init__.py +0 -0
- python_motion_planning/utils/agent/agent.py +135 -0
- python_motion_planning/utils/environment/__init__.py +0 -0
- python_motion_planning/utils/environment/env.py +134 -0
- python_motion_planning/utils/environment/node.py +85 -0
- python_motion_planning/utils/environment/point2d.py +96 -0
- python_motion_planning/utils/environment/pose2d.py +91 -0
- python_motion_planning/utils/helper/__init__.py +3 -0
- python_motion_planning/utils/helper/math_helper.py +65 -0
- python_motion_planning/utils/planner/__init__.py +0 -0
- python_motion_planning/utils/planner/control_factory.py +31 -0
- python_motion_planning/utils/planner/curve_factory.py +29 -0
- python_motion_planning/utils/planner/planner.py +40 -0
- python_motion_planning/utils/planner/search_factory.py +51 -0
- python_motion_planning/utils/plot/__init__.py +0 -0
- python_motion_planning/utils/plot/plot.py +274 -0
- python_motion_planning-0.1.dist-info/LICENSE +674 -0
- python_motion_planning-0.1.dist-info/METADATA +873 -0
- python_motion_planning-0.1.dist-info/RECORD +66 -0
- python_motion_planning-0.1.dist-info/WHEEL +5 -0
- python_motion_planning-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: env.py
|
|
3
|
+
@breif: 2-dimension environment
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.1.13
|
|
6
|
+
"""
|
|
7
|
+
from math import sqrt
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from scipy.spatial import cKDTree
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from .node import Node
|
|
13
|
+
|
|
14
|
+
class Env(ABC):
|
|
15
|
+
"""
|
|
16
|
+
Class for building 2-d workspace of robots.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
x_range (int): x-axis range of enviroment
|
|
20
|
+
y_range (int): y-axis range of environmet
|
|
21
|
+
eps (float): tolerance for float comparison
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> from python_motion_planning.utils import Env
|
|
25
|
+
>>> env = Env(30, 40)
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self, x_range: int, y_range: int, eps: float = 1e-6) -> None:
|
|
28
|
+
# size of environment
|
|
29
|
+
self.x_range = x_range
|
|
30
|
+
self.y_range = y_range
|
|
31
|
+
self.eps = eps
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def grid_map(self) -> set:
|
|
35
|
+
return {(i, j) for i in range(self.x_range) for j in range(self.y_range)}
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def init(self) -> None:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
class Grid(Env):
|
|
42
|
+
"""
|
|
43
|
+
Class for discrete 2-d grid map.
|
|
44
|
+
"""
|
|
45
|
+
def __init__(self, x_range: int, y_range: int) -> None:
|
|
46
|
+
super().__init__(x_range, y_range)
|
|
47
|
+
# allowed motions
|
|
48
|
+
self.motions = [Node((-1, 0), None, 1, None), Node((-1, 1), None, sqrt(2), None),
|
|
49
|
+
Node((0, 1), None, 1, None), Node((1, 1), None, sqrt(2), None),
|
|
50
|
+
Node((1, 0), None, 1, None), Node((1, -1), None, sqrt(2), None),
|
|
51
|
+
Node((0, -1), None, 1, None), Node((-1, -1), None, sqrt(2), None)]
|
|
52
|
+
# obstacles
|
|
53
|
+
self.obstacles = None
|
|
54
|
+
self.obstacles_tree = None
|
|
55
|
+
self.init()
|
|
56
|
+
|
|
57
|
+
def init(self) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Initialize grid map.
|
|
60
|
+
"""
|
|
61
|
+
x, y = self.x_range, self.y_range
|
|
62
|
+
obstacles = set()
|
|
63
|
+
|
|
64
|
+
# boundary of environment
|
|
65
|
+
for i in range(x):
|
|
66
|
+
obstacles.add((i, 0))
|
|
67
|
+
obstacles.add((i, y - 1))
|
|
68
|
+
for i in range(y):
|
|
69
|
+
obstacles.add((0, i))
|
|
70
|
+
obstacles.add((x - 1, i))
|
|
71
|
+
|
|
72
|
+
# user-defined obstacles
|
|
73
|
+
for i in range(10, 21):
|
|
74
|
+
obstacles.add((i, 15))
|
|
75
|
+
for i in range(15):
|
|
76
|
+
obstacles.add((20, i))
|
|
77
|
+
for i in range(15, 30):
|
|
78
|
+
obstacles.add((30, i))
|
|
79
|
+
for i in range(16):
|
|
80
|
+
obstacles.add((40, i))
|
|
81
|
+
|
|
82
|
+
self.obstacles = obstacles
|
|
83
|
+
self.obstacles_tree = cKDTree(np.array(list(obstacles)))
|
|
84
|
+
|
|
85
|
+
def update(self, obstacles):
|
|
86
|
+
self.obstacles = obstacles
|
|
87
|
+
self.obstacles_tree = cKDTree(np.array(list(obstacles)))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Map(Env):
|
|
91
|
+
"""
|
|
92
|
+
Class for continuous 2-d map.
|
|
93
|
+
"""
|
|
94
|
+
def __init__(self, x_range: int, y_range: int) -> None:
|
|
95
|
+
super().__init__(x_range, y_range)
|
|
96
|
+
self.boundary = None
|
|
97
|
+
self.obs_circ = None
|
|
98
|
+
self.obs_rect = None
|
|
99
|
+
self.init()
|
|
100
|
+
|
|
101
|
+
def init(self):
|
|
102
|
+
"""
|
|
103
|
+
Initialize map.
|
|
104
|
+
"""
|
|
105
|
+
x, y = self.x_range, self.y_range
|
|
106
|
+
|
|
107
|
+
# boundary of environment
|
|
108
|
+
self.boundary = [
|
|
109
|
+
[0, 0, 1, y],
|
|
110
|
+
[0, y, x, 1],
|
|
111
|
+
[1, 0, x, 1],
|
|
112
|
+
[x, 1, 1, y]
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
# user-defined obstacles
|
|
116
|
+
self.obs_rect = [
|
|
117
|
+
[14, 12, 8, 2],
|
|
118
|
+
[18, 22, 8, 3],
|
|
119
|
+
[26, 7, 2, 12],
|
|
120
|
+
[32, 14, 10, 2]
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
self.obs_circ = [
|
|
124
|
+
[7, 12, 3],
|
|
125
|
+
[46, 20, 2],
|
|
126
|
+
[15, 5, 2],
|
|
127
|
+
[37, 7, 3],
|
|
128
|
+
[37, 23, 3]
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
def update(self, boundary, obs_circ, obs_rect):
|
|
132
|
+
self.boundary = boundary if boundary else self.boundary
|
|
133
|
+
self.obs_circ = obs_circ if obs_circ else self.obs_circ
|
|
134
|
+
self.obs_rect = obs_rect if obs_rect else self.obs_rect
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: node.py
|
|
3
|
+
@breif: 2-dimension node data stucture
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.3.15
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
class Node(object):
|
|
9
|
+
"""
|
|
10
|
+
Class for searching nodes.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
current (tuple): current coordinate
|
|
14
|
+
parent (tuple): coordinate of parent node
|
|
15
|
+
g (float): path cost
|
|
16
|
+
h (float): heuristic cost
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
>>> from env import Node
|
|
20
|
+
>>> node1 = Node((1, 0), (2, 3), 1, 2)
|
|
21
|
+
>>> node2 = Node((1, 0), (2, 5), 2, 8)
|
|
22
|
+
>>> node3 = Node((2, 0), (1, 6), 3, 1)
|
|
23
|
+
...
|
|
24
|
+
>>> node1 + node2
|
|
25
|
+
>>> Node((2, 0), (2, 3), 3, 2)
|
|
26
|
+
...
|
|
27
|
+
>>> node1 == node2
|
|
28
|
+
>>> True
|
|
29
|
+
...
|
|
30
|
+
>>> node1 != node3
|
|
31
|
+
>>> True
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, current: tuple, parent: tuple = None, g: float = 0, h: float = 0) -> None:
|
|
34
|
+
self.current = current
|
|
35
|
+
self.parent = parent
|
|
36
|
+
self.g = g
|
|
37
|
+
self.h = h
|
|
38
|
+
|
|
39
|
+
def __add__(self, node):
|
|
40
|
+
assert isinstance(node, Node)
|
|
41
|
+
return Node((self.x + node.x, self.y + node.y), self.parent, self.g + node.g, self.h)
|
|
42
|
+
|
|
43
|
+
def __eq__(self, node) -> bool:
|
|
44
|
+
if not isinstance(node, Node):
|
|
45
|
+
return False
|
|
46
|
+
return self.current == node.current
|
|
47
|
+
|
|
48
|
+
def __ne__(self, node) -> bool:
|
|
49
|
+
return not self.__eq__(node)
|
|
50
|
+
|
|
51
|
+
def __lt__(self, node) -> bool:
|
|
52
|
+
assert isinstance(node, Node)
|
|
53
|
+
return self.g + self.h < node.g + node.h or \
|
|
54
|
+
(self.g + self.h == node.g + node.h and self.h < node.h)
|
|
55
|
+
|
|
56
|
+
def __hash__(self) -> int:
|
|
57
|
+
return hash(self.current)
|
|
58
|
+
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
return "Node({}, {}, {}, {})".format(self.current, self.parent, self.g, self.h)
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
return self.__str__()
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def x(self) -> float:
|
|
67
|
+
return self.current[0]
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def y(self) -> float:
|
|
71
|
+
return self.current[1]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def px(self) -> float:
|
|
75
|
+
if self.parent:
|
|
76
|
+
return self.parent[0]
|
|
77
|
+
else:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def py(self) -> float:
|
|
82
|
+
if self.parent:
|
|
83
|
+
return self.parent[1]
|
|
84
|
+
else:
|
|
85
|
+
return None
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: point2d.py
|
|
3
|
+
@breif: 2-dimension point data stucture
|
|
4
|
+
@author: Wu Maojia
|
|
5
|
+
@update: 2024.3.15
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Point2D(object):
|
|
11
|
+
"""
|
|
12
|
+
Class for searching and manipulating 2-dimensional points.
|
|
13
|
+
|
|
14
|
+
Parameters:
|
|
15
|
+
x: x-coordinate of the 2d point
|
|
16
|
+
y: y-coordinate of the 2d point
|
|
17
|
+
eps: tolerance for float comparison
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
>>> from python_motion_planning import Point2D
|
|
21
|
+
>>> p1 = Point2D(1, 2)
|
|
22
|
+
>>> p2 = Point2D(3, 4)
|
|
23
|
+
...
|
|
24
|
+
>>> p1
|
|
25
|
+
>>> Point2D(1, 2)
|
|
26
|
+
...
|
|
27
|
+
>>> p1 + p2
|
|
28
|
+
>>> Point2D(4, 6)
|
|
29
|
+
...
|
|
30
|
+
>>> p1 - p2
|
|
31
|
+
>>> Point2D(-2, -2)
|
|
32
|
+
...
|
|
33
|
+
>>> p1 == p2
|
|
34
|
+
>>> False
|
|
35
|
+
...
|
|
36
|
+
>>> p1!= p2
|
|
37
|
+
>>> True
|
|
38
|
+
...
|
|
39
|
+
>>> p1.dist(p2)
|
|
40
|
+
>>> 2.8284271247461903
|
|
41
|
+
...
|
|
42
|
+
>>> p1.angle(p2)
|
|
43
|
+
>>> 0.7853981633974483
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, x: float, y: float, eps: float = 1e-6) -> None:
|
|
47
|
+
self.x = x
|
|
48
|
+
self.y = y
|
|
49
|
+
self.eps = eps
|
|
50
|
+
|
|
51
|
+
if abs(self.x - round(self.x)) < self.eps:
|
|
52
|
+
self.x = round(self.x)
|
|
53
|
+
|
|
54
|
+
if abs(self.y - round(self.y)) < self.eps:
|
|
55
|
+
self.y = round(self.y)
|
|
56
|
+
|
|
57
|
+
def __add__(self, point):
|
|
58
|
+
assert isinstance(point, Point2D)
|
|
59
|
+
return Point2D(self.x + point.x, self.y + point.y)
|
|
60
|
+
|
|
61
|
+
def __sub__(self, point):
|
|
62
|
+
assert isinstance(point, Point2D)
|
|
63
|
+
return Point2D(self.x - point.x, self.y - point.y)
|
|
64
|
+
|
|
65
|
+
def __eq__(self, point) -> bool:
|
|
66
|
+
if not isinstance(point, Point2D):
|
|
67
|
+
return False
|
|
68
|
+
return abs(self.x - point.x) < self.eps and abs(self.y - point.y) < self.eps
|
|
69
|
+
|
|
70
|
+
def __ne__(self, point) -> bool:
|
|
71
|
+
return not self.__eq__(point)
|
|
72
|
+
|
|
73
|
+
def __hash__(self) -> int:
|
|
74
|
+
return hash((self.x, self.y))
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
return "Point2D({}, {})".format(self.x, self.y)
|
|
78
|
+
|
|
79
|
+
def __repr__(self) -> str:
|
|
80
|
+
return self.__str__()
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def from_tuple(point: tuple):
|
|
84
|
+
return Point2D(point[0], point[1])
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def to_tuple(self) -> tuple:
|
|
88
|
+
return int(self.x), int(self.y)
|
|
89
|
+
|
|
90
|
+
def dist(self, point) -> float:
|
|
91
|
+
assert isinstance(point, Point2D)
|
|
92
|
+
return math.hypot(self.x - point.x, self.y - point.y)
|
|
93
|
+
|
|
94
|
+
def angle(self, point) -> float:
|
|
95
|
+
assert isinstance(point, Point2D)
|
|
96
|
+
return math.atan2(point.y - self.y, point.x - self.x)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: pose2d.py
|
|
3
|
+
@breif: 2-dimension pose data stucture
|
|
4
|
+
@author: Wu Maojia
|
|
5
|
+
@update: 2024.3.15
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Pose2D(object):
|
|
11
|
+
"""
|
|
12
|
+
Class for searching and manipulating 2-dimensional poses.
|
|
13
|
+
|
|
14
|
+
Parameters:
|
|
15
|
+
x: x-coordinate of the 2d pose
|
|
16
|
+
y: y-coordinate of the 2d pose
|
|
17
|
+
theta: orientation of the 2d pose in radians
|
|
18
|
+
eps: tolerance for float comparison
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> from python_motion_planning import Pose2D
|
|
22
|
+
>>> p1 = Pose2D(1, 2)
|
|
23
|
+
>>> p2 = Pose2D(3, 4, 1)
|
|
24
|
+
...
|
|
25
|
+
>>> p1
|
|
26
|
+
>>> Pose2D(1, 2, 0)
|
|
27
|
+
...
|
|
28
|
+
>>> p2
|
|
29
|
+
>>> Pose2D(3, 4, 1)
|
|
30
|
+
...
|
|
31
|
+
>>> p1 + p2
|
|
32
|
+
>>> Pose2D(4, 6, 1)
|
|
33
|
+
...
|
|
34
|
+
>>> p1 - p2
|
|
35
|
+
>>> Pose2D(-2, -2, -1)
|
|
36
|
+
...
|
|
37
|
+
>>> p1 == p2
|
|
38
|
+
>>> False
|
|
39
|
+
...
|
|
40
|
+
>>> p1!= p2
|
|
41
|
+
>>> True
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, x: float, y: float, theta: float = 0, eps: float = 1e-6) -> None:
|
|
45
|
+
self.x = x
|
|
46
|
+
self.y = y
|
|
47
|
+
self.theta = theta
|
|
48
|
+
self.eps = eps
|
|
49
|
+
|
|
50
|
+
if abs(self.x - round(self.x)) < self.eps:
|
|
51
|
+
self.x = round(self.x)
|
|
52
|
+
|
|
53
|
+
if abs(self.y - round(self.y)) < self.eps:
|
|
54
|
+
self.y = round(self.y)
|
|
55
|
+
|
|
56
|
+
if abs(self.theta - round(self.theta)) < self.eps:
|
|
57
|
+
self.theta = round(self.theta)
|
|
58
|
+
|
|
59
|
+
def __add__(self, pose):
|
|
60
|
+
assert isinstance(pose, Pose2D)
|
|
61
|
+
return Pose2D(self.x + pose.x, self.y + pose.y, self.theta + pose.theta)
|
|
62
|
+
|
|
63
|
+
def __sub__(self, pose):
|
|
64
|
+
assert isinstance(pose, Pose2D)
|
|
65
|
+
return Pose2D(self.x - pose.x, self.y - pose.y, self.theta - pose.theta)
|
|
66
|
+
|
|
67
|
+
def __eq__(self, pose) -> bool:
|
|
68
|
+
if not isinstance(pose, Pose2D):
|
|
69
|
+
return False
|
|
70
|
+
return (abs(self.x - pose.x) < self.eps and abs(self.y - pose.y) < self.eps
|
|
71
|
+
and abs(self.theta - pose.theta) < self.eps)
|
|
72
|
+
|
|
73
|
+
def __ne__(self, pose) -> bool:
|
|
74
|
+
return not self.__eq__(pose)
|
|
75
|
+
|
|
76
|
+
def __hash__(self) -> int:
|
|
77
|
+
return hash((self.x, self.y, self.theta))
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
return "Pose2D({}, {}, {})".format(self.x, self.y, self.theta)
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return self.__str__()
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def from_tuple(pose: tuple):
|
|
87
|
+
return Pose2D(pose[0], pose[1], pose[2])
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def to_tuple(self) -> tuple:
|
|
91
|
+
return self.x, self.y, self.theta
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: math_helper.py
|
|
3
|
+
@breif: Contains common/commonly used math function
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.5.20
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
class MathHelper:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def circleSegmentIntersection(p1: tuple, p2: tuple, r: float) -> list:
|
|
12
|
+
x1, x2 = p1[0], p2[0]
|
|
13
|
+
y1, y2 = p1[1], p2[1]
|
|
14
|
+
|
|
15
|
+
dx, dy = x2 - x1, y2 - y1
|
|
16
|
+
dr2 = dx * dx + dy * dy
|
|
17
|
+
D = x1 * y2 - x2 * y1
|
|
18
|
+
|
|
19
|
+
# the first element is the point within segment
|
|
20
|
+
d1 = x1 * x1 + y1 * y1
|
|
21
|
+
d2 = x2 * x2 + y2 * y2
|
|
22
|
+
dd = d2 - d1
|
|
23
|
+
|
|
24
|
+
delta_2 = r * r * dr2 - D * D
|
|
25
|
+
if delta_2 < 0: # no intersection
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
delta = math.sqrt(delta_2)
|
|
29
|
+
if (delta == 0):
|
|
30
|
+
return [(D * dy / dr2, -D * dx / dr2)]
|
|
31
|
+
else: # delta > 0
|
|
32
|
+
return [
|
|
33
|
+
((D * dy + math.copysign(1.0, dd) * dx * delta) / dr2,
|
|
34
|
+
(-D * dx + math.copysign(1.0, dd) * dy * delta) / dr2),
|
|
35
|
+
((D * dy - math.copysign(1.0, dd) * dx * delta) / dr2,
|
|
36
|
+
(-D * dx - math.copysign(1.0, dd) * dy * delta) / dr2)
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def closestPointOnLine(a: tuple, b: tuple, p: tuple = (0.0, 0.0)) -> tuple:
|
|
41
|
+
"""
|
|
42
|
+
Find the closest intersection point (foot of a perpendicular) between point p and the line ab.
|
|
43
|
+
|
|
44
|
+
Parameters:
|
|
45
|
+
a (tuple): point a of the line
|
|
46
|
+
b (tuple): point b of the line
|
|
47
|
+
p (tuple): point p to find the closest intersection point
|
|
48
|
+
|
|
49
|
+
References:
|
|
50
|
+
[1] method 2 of https://www.youtube.com/watch?v=TPDgB6136ZE
|
|
51
|
+
"""
|
|
52
|
+
ap = (p[0] - a[0], p[1] - a[1])
|
|
53
|
+
ab = (b[0] - a[0], b[1] - a[1])
|
|
54
|
+
af_coef = (ap[0] * ab[0] + ap[1] * ab[1]) / (ab[0] ** 2 + ab[1] ** 2)
|
|
55
|
+
af = (af_coef * ab[0], af_coef * ab[1])
|
|
56
|
+
f = (a[0] + af[0], a[1] + af[1])
|
|
57
|
+
return f
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def clamp(val: float, min_val: float, max_val: float) -> float:
|
|
61
|
+
if val < min_val:
|
|
62
|
+
val = min_val
|
|
63
|
+
if val > max_val :
|
|
64
|
+
val = max_val
|
|
65
|
+
return val
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: control_factory.py
|
|
3
|
+
@breif: Facotry class for local planner.
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.10.24
|
|
6
|
+
"""
|
|
7
|
+
from python_motion_planning.local_planner import *
|
|
8
|
+
|
|
9
|
+
class ControlFactory(object):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def __call__(self, planner_name, **config):
|
|
14
|
+
if planner_name == "dwa":
|
|
15
|
+
return DWA(**config)
|
|
16
|
+
elif planner_name == "pid":
|
|
17
|
+
return PID(**config)
|
|
18
|
+
elif planner_name == "apf":
|
|
19
|
+
return APF(**config)
|
|
20
|
+
elif planner_name == "rpp":
|
|
21
|
+
return RPP(**config)
|
|
22
|
+
elif planner_name == "lqr":
|
|
23
|
+
return LQR(**config)
|
|
24
|
+
elif planner_name == "mpc":
|
|
25
|
+
return MPC(**config)
|
|
26
|
+
elif planner_name == "ddpg":
|
|
27
|
+
return DDPG(actor_load_path="models/actor_best_example.pth",
|
|
28
|
+
critic_load_path="models/critic_best_example.pth",
|
|
29
|
+
**config)
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError("The `planner_name` must be set correctly.")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: curve_factory.py
|
|
3
|
+
@breif: Facotry class for curve generation.
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.7.25
|
|
6
|
+
"""
|
|
7
|
+
from python_motion_planning.curve_generation import *
|
|
8
|
+
|
|
9
|
+
class CurveFactory(object):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def __call__(self, curve_name, **config):
|
|
14
|
+
if curve_name == "dubins":
|
|
15
|
+
return Dubins(**config)
|
|
16
|
+
elif curve_name == "bezier":
|
|
17
|
+
return Bezier(**config)
|
|
18
|
+
elif curve_name == "polynomial":
|
|
19
|
+
return Polynomial(**config)
|
|
20
|
+
elif curve_name == "reeds_shepp":
|
|
21
|
+
return ReedsShepp(**config)
|
|
22
|
+
elif curve_name == "cubic_spline":
|
|
23
|
+
return CubicSpline(**config)
|
|
24
|
+
elif curve_name == "bspline":
|
|
25
|
+
return BSpline(**config)
|
|
26
|
+
elif curve_name == "fem_pos_smoother":
|
|
27
|
+
return FemPosSmoother(**config)
|
|
28
|
+
else:
|
|
29
|
+
raise ValueError("The `curve_name` must be set correctly.")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: planner.py
|
|
3
|
+
@breif: Abstract class for planner
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.1.17
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from abc import abstractmethod, ABC
|
|
9
|
+
from ..environment.env import Env, Node
|
|
10
|
+
from ..plot.plot import Plot
|
|
11
|
+
|
|
12
|
+
class Planner(ABC):
|
|
13
|
+
def __init__(self, start: tuple, goal: tuple, env: Env) -> None:
|
|
14
|
+
# plannig start and goal
|
|
15
|
+
self.start = Node(start, start, 0, 0)
|
|
16
|
+
self.goal = Node(goal, goal, 0, 0)
|
|
17
|
+
# environment
|
|
18
|
+
self.env = env
|
|
19
|
+
# graph handler
|
|
20
|
+
self.plot = Plot(start, goal, env)
|
|
21
|
+
|
|
22
|
+
def dist(self, node1: Node, node2: Node) -> float:
|
|
23
|
+
return math.hypot(node2.x - node1.x, node2.y - node1.y)
|
|
24
|
+
|
|
25
|
+
def angle(self, node1: Node, node2: Node) -> float:
|
|
26
|
+
return math.atan2(node2.y - node1.y, node2.x - node1.x)
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def plan(self):
|
|
30
|
+
'''
|
|
31
|
+
Interface for planning.
|
|
32
|
+
'''
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def run(self):
|
|
37
|
+
'''
|
|
38
|
+
Interface for running both plannig and animation.
|
|
39
|
+
'''
|
|
40
|
+
pass
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: search_factory.py
|
|
3
|
+
@breif: Factory class for global planner.
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.3.2
|
|
6
|
+
"""
|
|
7
|
+
from python_motion_planning.global_planner import *
|
|
8
|
+
|
|
9
|
+
class SearchFactory(object):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def __call__(self, planner_name, **config):
|
|
14
|
+
if planner_name == "a_star":
|
|
15
|
+
return AStar(**config)
|
|
16
|
+
elif planner_name == "dijkstra":
|
|
17
|
+
return Dijkstra(**config)
|
|
18
|
+
elif planner_name == "gbfs":
|
|
19
|
+
return GBFS(**config)
|
|
20
|
+
elif planner_name == "jps":
|
|
21
|
+
return JPS(**config)
|
|
22
|
+
elif planner_name == "d_star":
|
|
23
|
+
return DStar(**config)
|
|
24
|
+
elif planner_name == "lpa_star":
|
|
25
|
+
return LPAStar(**config)
|
|
26
|
+
elif planner_name == "d_star_lite":
|
|
27
|
+
return DStarLite(**config)
|
|
28
|
+
elif planner_name == "voronoi":
|
|
29
|
+
return VoronoiPlanner(**config)
|
|
30
|
+
elif planner_name == "theta_star":
|
|
31
|
+
return ThetaStar(**config)
|
|
32
|
+
elif planner_name == "lazy_theta_star":
|
|
33
|
+
return LazyThetaStar(**config)
|
|
34
|
+
elif planner_name == "s_theta_star":
|
|
35
|
+
return SThetaStar(**config)
|
|
36
|
+
elif planner_name == "anya":
|
|
37
|
+
return Anya(**config)
|
|
38
|
+
elif planner_name == "rrt":
|
|
39
|
+
return RRT(**config)
|
|
40
|
+
elif planner_name == "rrt_connect":
|
|
41
|
+
return RRTConnect(**config)
|
|
42
|
+
elif planner_name == "rrt_star":
|
|
43
|
+
return RRTStar(**config)
|
|
44
|
+
elif planner_name == "informed_rrt":
|
|
45
|
+
return InformedRRT(**config)
|
|
46
|
+
elif planner_name == "aco":
|
|
47
|
+
return ACO(**config)
|
|
48
|
+
elif planner_name == "pso":
|
|
49
|
+
return PSO(**config)
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError("The `planner_name` must be set correctly.")
|
|
File without changes
|