scenario_helper.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #!/usr/bin/env python
  2. # Copyright (c) 2019 Intel Corporation
  3. #
  4. # This work is licensed under the terms of the MIT license.
  5. # For a copy, see <https://opensource.org/licenses/MIT>.
  6. """
  7. Summary of useful helper functions for scenarios
  8. """
  9. import math
  10. import shapely.geometry
  11. import shapely.affinity
  12. import numpy as np
  13. import carla
  14. from agents.tools.misc import vector
  15. from agents.navigation.local_planner import RoadOption
  16. from srunner.scenariomanager.carla_data_provider import CarlaDataProvider
  17. def get_distance_along_route(route, target_location):
  18. """
  19. Calculate the distance of the given location along the route
  20. Note: If the location is not along the route, the route length will be returned
  21. """
  22. wmap = CarlaDataProvider.get_map()
  23. covered_distance = 0
  24. prev_position = None
  25. found = False
  26. # Don't use the input location, use the corresponding wp as location
  27. target_location_from_wp = wmap.get_waypoint(target_location).transform.location
  28. for position, _ in route:
  29. location = target_location_from_wp
  30. # Don't perform any calculations for the first route point
  31. if not prev_position:
  32. prev_position = position
  33. continue
  34. # Calculate distance between previous and current route point
  35. interval_length_squared = ((prev_position.x - position.x) ** 2) + ((prev_position.y - position.y) ** 2)
  36. distance_squared = ((location.x - prev_position.x) ** 2) + ((location.y - prev_position.y) ** 2)
  37. # Close to the current position? Stop calculation
  38. if distance_squared < 0.01:
  39. break
  40. if distance_squared < 400 and not distance_squared < interval_length_squared:
  41. # Check if a neighbor lane is closer to the route
  42. # Do this only in a close distance to correct route interval, otherwise the computation load is too high
  43. starting_wp = wmap.get_waypoint(location)
  44. wp = starting_wp.get_left_lane()
  45. while wp is not None:
  46. new_location = wp.transform.location
  47. new_distance_squared = ((new_location.x - prev_position.x) ** 2) + (
  48. (new_location.y - prev_position.y) ** 2)
  49. if np.sign(starting_wp.lane_id) != np.sign(wp.lane_id):
  50. break
  51. if new_distance_squared < distance_squared:
  52. distance_squared = new_distance_squared
  53. location = new_location
  54. else:
  55. break
  56. wp = wp.get_left_lane()
  57. wp = starting_wp.get_right_lane()
  58. while wp is not None:
  59. new_location = wp.transform.location
  60. new_distance_squared = ((new_location.x - prev_position.x) ** 2) + (
  61. (new_location.y - prev_position.y) ** 2)
  62. if np.sign(starting_wp.lane_id) != np.sign(wp.lane_id):
  63. break
  64. if new_distance_squared < distance_squared:
  65. distance_squared = new_distance_squared
  66. location = new_location
  67. else:
  68. break
  69. wp = wp.get_right_lane()
  70. if distance_squared < interval_length_squared:
  71. # The location could be inside the current route interval, if route/lane ids match
  72. # Note: This assumes a sufficiently small route interval
  73. # An alternative is to compare orientations, however, this also does not work for
  74. # long route intervals
  75. curr_wp = wmap.get_waypoint(position)
  76. prev_wp = wmap.get_waypoint(prev_position)
  77. wp = wmap.get_waypoint(location)
  78. if prev_wp and curr_wp and wp:
  79. if wp.road_id in (prev_wp.road_id, curr_wp.road_id):
  80. # Roads match, now compare the sign of the lane ids
  81. if (np.sign(wp.lane_id) == np.sign(prev_wp.lane_id) or
  82. np.sign(wp.lane_id) == np.sign(curr_wp.lane_id)):
  83. # The location is within the current route interval
  84. covered_distance += math.sqrt(distance_squared)
  85. found = True
  86. break
  87. covered_distance += math.sqrt(interval_length_squared)
  88. prev_position = position
  89. return covered_distance, found
  90. def get_crossing_point(actor):
  91. """
  92. Get the next crossing point location in front of the ego vehicle
  93. @return point of crossing
  94. """
  95. wp_cross = CarlaDataProvider.get_map().get_waypoint(actor.get_location())
  96. while not wp_cross.is_intersection:
  97. wp_cross = wp_cross.next(2)[0]
  98. crossing = carla.Location(x=wp_cross.transform.location.x,
  99. y=wp_cross.transform.location.y, z=wp_cross.transform.location.z)
  100. return crossing
  101. def get_geometric_linear_intersection(ego_actor, other_actor):
  102. """
  103. Obtain a intersection point between two actor's location by using their waypoints (wp)
  104. @return point of intersection of the two vehicles
  105. """
  106. wp_ego_1 = CarlaDataProvider.get_map().get_waypoint(ego_actor.get_location())
  107. wp_ego_2 = wp_ego_1.next(1)[0]
  108. x_ego_1 = wp_ego_1.transform.location.x
  109. y_ego_1 = wp_ego_1.transform.location.y
  110. x_ego_2 = wp_ego_2.transform.location.x
  111. y_ego_2 = wp_ego_2.transform.location.y
  112. wp_other_1 = CarlaDataProvider.get_world().get_map().get_waypoint(other_actor.get_location())
  113. wp_other_2 = wp_other_1.next(1)[0]
  114. x_other_1 = wp_other_1.transform.location.x
  115. y_other_1 = wp_other_1.transform.location.y
  116. x_other_2 = wp_other_2.transform.location.x
  117. y_other_2 = wp_other_2.transform.location.y
  118. s = np.vstack([(x_ego_1, y_ego_1), (x_ego_2, y_ego_2), (x_other_1, y_other_1), (x_other_2, y_other_2)])
  119. h = np.hstack((s, np.ones((4, 1))))
  120. line1 = np.cross(h[0], h[1])
  121. line2 = np.cross(h[2], h[3])
  122. x, y, z = np.cross(line1, line2)
  123. if z == 0:
  124. return (float('inf'), float('inf'))
  125. intersection = carla.Location(x=x / z, y=y / z, z=0)
  126. return intersection
  127. def get_location_in_distance(actor, distance):
  128. """
  129. Obtain a location in a given distance from the current actor's location.
  130. Note: Search is stopped on first intersection.
  131. @return obtained location and the traveled distance
  132. """
  133. waypoint = CarlaDataProvider.get_map().get_waypoint(actor.get_location())
  134. traveled_distance = 0
  135. while not waypoint.is_intersection and traveled_distance < distance:
  136. waypoint_new = waypoint.next(1.0)[-1]
  137. traveled_distance += waypoint_new.transform.location.distance(waypoint.transform.location)
  138. waypoint = waypoint_new
  139. return waypoint.transform.location, traveled_distance
  140. def get_location_in_distance_from_wp(waypoint, distance, stop_at_junction=True):
  141. """
  142. Obtain a location in a given distance from the current actor's location.
  143. Note: Search is stopped on first intersection.
  144. @return obtained location and the traveled distance
  145. """
  146. traveled_distance = 0
  147. while not (waypoint.is_intersection and stop_at_junction) and traveled_distance < distance:
  148. wp_next = waypoint.next(1.0)
  149. if wp_next:
  150. waypoint_new = wp_next[-1]
  151. traveled_distance += waypoint_new.transform.location.distance(waypoint.transform.location)
  152. waypoint = waypoint_new
  153. else:
  154. break
  155. return waypoint.transform.location, traveled_distance
  156. def get_waypoint_in_distance(waypoint, distance):
  157. """
  158. Obtain a waypoint in a given distance from the current actor's location.
  159. Note: Search is stopped on first intersection.
  160. @return obtained waypoint and the traveled distance
  161. """
  162. traveled_distance = 0
  163. while not waypoint.is_intersection and traveled_distance < distance:
  164. waypoint_new = waypoint.next(1.0)[-1]
  165. traveled_distance += waypoint_new.transform.location.distance(waypoint.transform.location)
  166. waypoint = waypoint_new
  167. return waypoint, traveled_distance
  168. def generate_target_waypoint_list(waypoint, turn=0):
  169. """
  170. This method follow waypoints to a junction and choose path based on turn input.
  171. Turn input: LEFT -> -1, RIGHT -> 1, STRAIGHT -> 0
  172. @returns a waypoint list from the starting point to the end point according to turn input
  173. """
  174. reached_junction = False
  175. threshold = math.radians(0.1)
  176. plan = []
  177. while True:
  178. wp_choice = waypoint.next(2)
  179. if len(wp_choice) > 1:
  180. reached_junction = True
  181. waypoint = choose_at_junction(waypoint, wp_choice, turn)
  182. else:
  183. waypoint = wp_choice[0]
  184. plan.append((waypoint, RoadOption.LANEFOLLOW))
  185. # End condition for the behavior
  186. if turn != 0 and reached_junction and len(plan) >= 3:
  187. v_1 = vector(
  188. plan[-2][0].transform.location,
  189. plan[-1][0].transform.location)
  190. v_2 = vector(
  191. plan[-3][0].transform.location,
  192. plan[-2][0].transform.location)
  193. angle_wp = math.acos(
  194. np.dot(v_1, v_2) / abs((np.linalg.norm(v_1) * np.linalg.norm(v_2))))
  195. if angle_wp < threshold:
  196. break
  197. elif reached_junction and not plan[-1][0].is_intersection:
  198. break
  199. return plan, plan[-1][0]
  200. def generate_target_waypoint_list_multilane(waypoint, change='left', # pylint: disable=too-many-return-statements
  201. distance_same_lane=10, distance_other_lane=25,
  202. total_lane_change_distance=25, check=True,
  203. lane_changes=1, step_distance=2):
  204. """
  205. This methods generates a waypoint list which leads the vehicle to a parallel lane.
  206. The change input must be 'left' or 'right', depending on which lane you want to change.
  207. The default step distance between waypoints on the same lane is 2m.
  208. The default step distance between the lane change is set to 25m.
  209. @returns a waypoint list from the starting point to the end point on a right or left parallel lane.
  210. The function might break before reaching the end point, if the asked behavior is impossible.
  211. """
  212. plan = []
  213. plan.append((waypoint, RoadOption.LANEFOLLOW)) # start position
  214. option = RoadOption.LANEFOLLOW
  215. # Same lane
  216. distance = 0
  217. while distance < distance_same_lane:
  218. next_wps = plan[-1][0].next(step_distance)
  219. if not next_wps:
  220. return None, None
  221. next_wp = next_wps[0]
  222. distance += next_wp.transform.location.distance(plan[-1][0].transform.location)
  223. plan.append((next_wp, RoadOption.LANEFOLLOW))
  224. if change == 'left':
  225. option = RoadOption.CHANGELANELEFT
  226. elif change == 'right':
  227. option = RoadOption.CHANGELANERIGHT
  228. else:
  229. # ERROR, input value for change must be 'left' or 'right'
  230. return None, None
  231. lane_changes_done = 0
  232. lane_change_distance = total_lane_change_distance / lane_changes
  233. # Lane change
  234. while lane_changes_done < lane_changes:
  235. # Move forward
  236. next_wps = plan[-1][0].next(lane_change_distance)
  237. if not next_wps:
  238. return None, None
  239. next_wp = next_wps[0]
  240. # Get the side lane
  241. if change == 'left':
  242. if check and str(next_wp.lane_change) not in ['Left', 'Both']:
  243. return None, None
  244. side_wp = next_wp.get_left_lane()
  245. else:
  246. if check and str(next_wp.lane_change) not in ['Right', 'Both']:
  247. return None, None
  248. side_wp = next_wp.get_right_lane()
  249. if not side_wp or side_wp.lane_type != carla.LaneType.Driving:
  250. return None, None
  251. # Update the plan
  252. plan.append((side_wp, option))
  253. lane_changes_done += 1
  254. # Other lane
  255. distance = 0
  256. while distance < distance_other_lane:
  257. next_wps = plan[-1][0].next(step_distance)
  258. if not next_wps:
  259. return None, None
  260. next_wp = next_wps[0]
  261. distance += next_wp.transform.location.distance(plan[-1][0].transform.location)
  262. plan.append((next_wp, RoadOption.LANEFOLLOW))
  263. target_lane_id = plan[-1][0].lane_id
  264. return plan, target_lane_id
  265. def generate_target_waypoint(waypoint, turn=0):
  266. """
  267. This method follow waypoints to a junction and choose path based on turn input.
  268. Turn input: LEFT -> -1, RIGHT -> 1, STRAIGHT -> 0
  269. @returns a waypoint list according to turn input
  270. """
  271. sampling_radius = 1
  272. reached_junction = False
  273. wp_list = []
  274. while True:
  275. wp_choice = waypoint.next(sampling_radius)
  276. # Choose path at intersection
  277. if not reached_junction and (len(wp_choice) > 1 or wp_choice[0].is_junction):
  278. reached_junction = True
  279. waypoint = choose_at_junction(waypoint, wp_choice, turn)
  280. else:
  281. waypoint = wp_choice[0]
  282. wp_list.append(waypoint)
  283. # End condition for the behavior
  284. if reached_junction and not wp_list[-1].is_junction:
  285. break
  286. return wp_list[-1]
  287. def generate_target_waypoint_in_route(waypoint, route):
  288. """
  289. This method follow waypoints to a junction
  290. @returns a waypoint list according to turn input
  291. """
  292. wmap = CarlaDataProvider.get_map()
  293. reached_junction = False
  294. # Get the route location
  295. shortest_distance = float('inf')
  296. for index, route_pos in enumerate(route):
  297. wp = route_pos[0]
  298. trigger_location = waypoint.transform.location
  299. dist_to_route = trigger_location.distance(wp)
  300. if dist_to_route <= shortest_distance:
  301. closest_index = index
  302. shortest_distance = dist_to_route
  303. route_location = route[closest_index][0]
  304. index = closest_index
  305. while True:
  306. # Get the next route location
  307. index = min(index + 1, len(route))
  308. route_location = route[index][0]
  309. road_option = route[index][1]
  310. # Enter the junction
  311. if not reached_junction and (road_option in (RoadOption.LEFT, RoadOption.RIGHT, RoadOption.STRAIGHT)):
  312. reached_junction = True
  313. # End condition for the behavior, at the end of the junction
  314. if reached_junction and (road_option not in (RoadOption.LEFT, RoadOption.RIGHT, RoadOption.STRAIGHT)):
  315. break
  316. return wmap.get_waypoint(route_location)
  317. def choose_at_junction(current_waypoint, next_choices, direction=0):
  318. """
  319. This function chooses the appropriate waypoint from next_choices based on direction
  320. """
  321. current_transform = current_waypoint.transform
  322. current_location = current_transform.location
  323. projected_location = current_location + \
  324. carla.Location(
  325. x=math.cos(math.radians(current_transform.rotation.yaw)),
  326. y=math.sin(math.radians(current_transform.rotation.yaw)))
  327. current_vector = vector(current_location, projected_location)
  328. cross_list = []
  329. cross_to_waypoint = {}
  330. for waypoint in next_choices:
  331. waypoint = waypoint.next(10)[0]
  332. select_vector = vector(current_location, waypoint.transform.location)
  333. cross = np.cross(current_vector, select_vector)[2]
  334. cross_list.append(cross)
  335. cross_to_waypoint[cross] = waypoint
  336. select_cross = None
  337. if direction > 0:
  338. select_cross = max(cross_list)
  339. elif direction < 0:
  340. select_cross = min(cross_list)
  341. else:
  342. select_cross = min(cross_list, key=abs)
  343. return cross_to_waypoint[select_cross]
  344. def get_intersection(ego_actor, other_actor):
  345. """
  346. Obtain a intersection point between two actor's location
  347. @return the intersection location
  348. """
  349. waypoint = CarlaDataProvider.get_map().get_waypoint(ego_actor.get_location())
  350. waypoint_other = CarlaDataProvider.get_map().get_waypoint(other_actor.get_location())
  351. max_dist = float("inf")
  352. distance = float("inf")
  353. while distance <= max_dist:
  354. max_dist = distance
  355. current_location = waypoint.transform.location
  356. waypoint_choice = waypoint.next(1)
  357. # Select the straighter path at intersection
  358. if len(waypoint_choice) > 1:
  359. max_dot = -1 * float('inf')
  360. loc_projection = current_location + carla.Location(
  361. x=math.cos(math.radians(waypoint.transform.rotation.yaw)),
  362. y=math.sin(math.radians(waypoint.transform.rotation.yaw)))
  363. v_current = vector(current_location, loc_projection)
  364. for wp_select in waypoint_choice:
  365. v_select = vector(current_location, wp_select.transform.location)
  366. dot_select = np.dot(v_current, v_select)
  367. if dot_select > max_dot:
  368. max_dot = dot_select
  369. waypoint = wp_select
  370. else:
  371. waypoint = waypoint_choice[0]
  372. distance = current_location.distance(waypoint_other.transform.location)
  373. return current_location
  374. def detect_lane_obstacle(actor, extension_factor=3, margin=1.02):
  375. """
  376. This function identifies if an obstacle is present in front of the reference actor
  377. """
  378. world = CarlaDataProvider.get_world()
  379. world_actors = world.get_actors().filter('vehicle.*')
  380. actor_bbox = actor.bounding_box
  381. actor_transform = actor.get_transform()
  382. actor_location = actor_transform.location
  383. actor_vector = actor_transform.rotation.get_forward_vector()
  384. actor_vector = np.array([actor_vector.x, actor_vector.y])
  385. actor_vector = actor_vector / np.linalg.norm(actor_vector)
  386. actor_vector = actor_vector * (extension_factor - 1) * actor_bbox.extent.x
  387. actor_location = actor_location + carla.Location(actor_vector[0], actor_vector[1])
  388. actor_yaw = actor_transform.rotation.yaw
  389. is_hazard = False
  390. for adversary in world_actors:
  391. if adversary.id != actor.id and \
  392. actor_transform.location.distance(adversary.get_location()) < 50:
  393. adversary_bbox = adversary.bounding_box
  394. adversary_transform = adversary.get_transform()
  395. adversary_loc = adversary_transform.location
  396. adversary_yaw = adversary_transform.rotation.yaw
  397. overlap_adversary = RotatedRectangle(
  398. adversary_loc.x, adversary_loc.y,
  399. 2 * margin * adversary_bbox.extent.x, 2 * margin * adversary_bbox.extent.y, adversary_yaw)
  400. overlap_actor = RotatedRectangle(
  401. actor_location.x, actor_location.y,
  402. 2 * margin * actor_bbox.extent.x * extension_factor, 2 * margin * actor_bbox.extent.y, actor_yaw)
  403. overlap_area = overlap_adversary.intersection(overlap_actor).area
  404. if overlap_area > 0:
  405. is_hazard = True
  406. break
  407. return is_hazard
  408. def get_offset_transform(transform, offset):
  409. """
  410. This function adjusts the give transform by offset and returns the new transform.
  411. """
  412. if offset != 0:
  413. forward_vector = transform.rotation.get_forward_vector()
  414. orthogonal_vector = carla.Vector3D(x=-forward_vector.y, y=forward_vector.x, z=forward_vector.z)
  415. transform.location.x = transform.location.x + offset * orthogonal_vector.x
  416. transform.location.y = transform.location.y + offset * orthogonal_vector.y
  417. return transform
  418. def get_troad_from_transform(actor_transform):
  419. """
  420. This function finds the lateral road position (t) from actor_transform
  421. """
  422. actor_loc = actor_transform.location
  423. c_wp = CarlaDataProvider.get_map().get_waypoint(actor_loc)
  424. left_lanes, right_lanes = [], []
  425. # opendrive standard: (left ==> +ve lane_id) and (right ==> -ve lane_id)
  426. ref_lane = CarlaDataProvider.get_map().get_waypoint_xodr(c_wp.road_id, 0, c_wp.s)
  427. for i in range(-50, 50):
  428. _wp = CarlaDataProvider.get_map().get_waypoint_xodr(c_wp.road_id, i, c_wp.s)
  429. if _wp:
  430. if i < 0:
  431. left_lanes.append(_wp)
  432. elif i > 0:
  433. right_lanes.append(_wp)
  434. if left_lanes:
  435. left_lane_ids = [ln.lane_id for ln in left_lanes]
  436. lm_id = min(left_lane_ids)
  437. lm_lane = left_lanes[left_lane_ids.index(lm_id)]
  438. lm_lane_offset = lm_lane.lane_width / 2
  439. else:
  440. lm_lane, lm_lane_offset = ref_lane, 0
  441. lm_tr = get_offset_transform(carla.Transform(lm_lane.transform.location, lm_lane.transform.rotation),
  442. lm_lane_offset)
  443. distance_from_lm_lane_edge = lm_tr.location.distance(actor_loc)
  444. distance_from_lm_lane_ref_lane = lm_tr.location.distance(ref_lane.transform.location)
  445. if right_lanes:
  446. right_lane_ids = [ln.lane_id for ln in right_lanes]
  447. rm_id = max(right_lane_ids)
  448. rm_lane = right_lanes[right_lane_ids.index(rm_id)]
  449. rm_lane_offset = -rm_lane.lane_width / 2
  450. else:
  451. rm_lane, rm_lane_offset = ref_lane, -distance_from_lm_lane_ref_lane
  452. distance_from_rm_lane_edge = get_offset_transform(carla.Transform(rm_lane.transform.location,
  453. rm_lane.transform.rotation),
  454. rm_lane_offset).location.distance(actor_loc)
  455. t_road = ref_lane.transform.location.distance(actor_loc)
  456. if right_lanes == [] or left_lanes == []:
  457. closest_road_edge = min(distance_from_lm_lane_edge, distance_from_rm_lane_edge)
  458. if closest_road_edge == distance_from_lm_lane_edge:
  459. t_road = -1*t_road
  460. else:
  461. if c_wp.lane_id < 0:
  462. t_road = -1*t_road
  463. return t_road
  464. def get_distance_between_actors(current, target, distance_type="euclidianDistance", freespace=False,
  465. global_planner=None):
  466. """
  467. This function finds the distance between actors for different use cases described by distance_type and freespace
  468. attributes
  469. """
  470. target_transform = CarlaDataProvider.get_transform(target)
  471. current_transform = CarlaDataProvider.get_transform(current)
  472. target_wp = CarlaDataProvider.get_map().get_waypoint(target_transform.location)
  473. current_wp = CarlaDataProvider.get_map().get_waypoint(current_transform.location)
  474. extent_sum_x, extent_sum_y = 0, 0
  475. if freespace:
  476. if isinstance(target, (carla.Vehicle, carla.Walker)):
  477. extent_sum_x = target.bounding_box.extent.x + current.bounding_box.extent.x
  478. extent_sum_y = target.bounding_box.extent.y + current.bounding_box.extent.y
  479. if distance_type == "longitudinal":
  480. if not current_wp.road_id == target_wp.road_id:
  481. distance = 0
  482. # Get the route
  483. route = global_planner.trace_route(current_transform.location, target_transform.location)
  484. # Get the distance of the route
  485. for i in range(1, len(route)):
  486. curr_loc = route[i][0].transform.location
  487. prev_loc = route[i - 1][0].transform.location
  488. distance += curr_loc.distance(prev_loc)
  489. else:
  490. distance = abs(current_wp.s - target_wp.s)
  491. if freespace:
  492. distance = distance - extent_sum_x
  493. elif distance_type == "lateral":
  494. target_t = get_troad_from_transform(target_transform)
  495. current_t = get_troad_from_transform(current_transform)
  496. distance = abs(target_t - current_t)
  497. if freespace:
  498. distance = distance - extent_sum_y
  499. elif distance_type in ["cartesianDistance", "euclidianDistance"]:
  500. distance = target_transform.location.distance(current_transform.location)
  501. if freespace:
  502. distance = distance - extent_sum_x
  503. else:
  504. raise TypeError("unknown distance_type: {}".format(distance_type))
  505. # distance will be negative for feeespace when there is overlap condition
  506. # truncate to 0.0 when this happens
  507. distance = 0.0 if distance < 0.0 else distance
  508. return distance
  509. class RotatedRectangle(object):
  510. """
  511. This class contains method to draw rectangle and find intersection point.
  512. """
  513. def __init__(self, c_x, c_y, width, height, angle):
  514. self.c_x = c_x
  515. self.c_y = c_y
  516. self.w = width # pylint: disable=invalid-name
  517. self.h = height # pylint: disable=invalid-name
  518. self.angle = angle
  519. def get_contour(self):
  520. """
  521. create contour
  522. """
  523. w = self.w
  524. h = self.h
  525. c = shapely.geometry.box(-w / 2.0, -h / 2.0, w / 2.0, h / 2.0)
  526. rc = shapely.affinity.rotate(c, self.angle)
  527. return shapely.affinity.translate(rc, self.c_x, self.c_y)
  528. def intersection(self, other):
  529. """
  530. Obtain a intersection point between two contour.
  531. """
  532. return self.get_contour().intersection(other.get_contour())