synchronization traffic lights
This commit is contained in:
parent
8ebb8a12a1
commit
6fde83fbc9
|
@ -5,7 +5,6 @@
|
||||||
#
|
#
|
||||||
# This work is licensed under the terms of the MIT license.
|
# This work is licensed under the terms of the MIT license.
|
||||||
# For a copy, see <https://opensource.org/licenses/MIT>.
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Script to integrate CARLA and SUMO simulations
|
Script to integrate CARLA and SUMO simulations
|
||||||
"""
|
"""
|
||||||
|
@ -27,9 +26,9 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sys.path.append(glob.glob('../../PythonAPI/carla/dist/carla-*%d.%d-%s.egg' % (
|
sys.path.append(
|
||||||
sys.version_info.major,
|
glob.glob('../../PythonAPI/carla/dist/carla-*%d.%d-%s.egg' %
|
||||||
sys.version_info.minor,
|
(sys.version_info.major, sys.version_info.minor,
|
||||||
'win-amd64' if os.name == 'nt' else 'linux-x86_64'))[0])
|
'win-amd64' if os.name == 'nt' else 'linux-x86_64'))[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
@ -62,13 +61,17 @@ class SimulationSynchronization(object):
|
||||||
SimulationSynchronization class is responsible for the synchronization of sumo and carla
|
SimulationSynchronization class is responsible for the synchronization of sumo and carla
|
||||||
simulations.
|
simulations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
self.sumo = SumoSimulation(args)
|
self.sumo = SumoSimulation(args)
|
||||||
self.carla = CarlaSimulation(args)
|
self.carla = CarlaSimulation(args)
|
||||||
|
|
||||||
|
if args.tls_manager == 'carla':
|
||||||
|
self.sumo.switch_off_traffic_lights()
|
||||||
|
elif args.tls_manager == 'sumo':
|
||||||
|
self.carla.switch_off_traffic_lights()
|
||||||
|
|
||||||
# Mapped actor ids.
|
# Mapped actor ids.
|
||||||
self.sumo2carla_ids = {} # Contains only actors controlled by sumo.
|
self.sumo2carla_ids = {} # Contains only actors controlled by sumo.
|
||||||
self.carla2sumo_ids = {} # Contains only actors controlled by carla.
|
self.carla2sumo_ids = {} # Contains only actors controlled by carla.
|
||||||
|
@ -94,8 +97,8 @@ class SimulationSynchronization(object):
|
||||||
carla_blueprint = BridgeHelper.get_carla_blueprint(sumo_actor,
|
carla_blueprint = BridgeHelper.get_carla_blueprint(sumo_actor,
|
||||||
self.args.sync_vehicle_color)
|
self.args.sync_vehicle_color)
|
||||||
if carla_blueprint is not None:
|
if carla_blueprint is not None:
|
||||||
carla_transform = BridgeHelper.get_carla_transform(
|
carla_transform = BridgeHelper.get_carla_transform(sumo_actor.transform,
|
||||||
sumo_actor.transform, sumo_actor.extent)
|
sumo_actor.extent)
|
||||||
|
|
||||||
carla_actor_id = self.carla.spawn_actor(carla_blueprint, carla_transform)
|
carla_actor_id = self.carla.spawn_actor(carla_blueprint, carla_transform)
|
||||||
if carla_actor_id != INVALID_ACTOR_ID:
|
if carla_actor_id != INVALID_ACTOR_ID:
|
||||||
|
@ -118,13 +121,22 @@ class SimulationSynchronization(object):
|
||||||
carla_transform = BridgeHelper.get_carla_transform(sumo_actor.transform,
|
carla_transform = BridgeHelper.get_carla_transform(sumo_actor.transform,
|
||||||
sumo_actor.extent)
|
sumo_actor.extent)
|
||||||
if self.args.sync_vehicle_lights:
|
if self.args.sync_vehicle_lights:
|
||||||
carla_lights = BridgeHelper.get_carla_lights_state(
|
carla_lights = BridgeHelper.get_carla_lights_state(carla_actor.get_light_state(),
|
||||||
carla_actor.get_light_state(), sumo_actor.signals)
|
sumo_actor.signals)
|
||||||
else:
|
else:
|
||||||
carla_lights = None
|
carla_lights = None
|
||||||
|
|
||||||
self.carla.synchronize_vehicle(carla_actor_id, carla_transform, carla_lights)
|
self.carla.synchronize_vehicle(carla_actor_id, carla_transform, carla_lights)
|
||||||
|
|
||||||
|
# Updates traffic lights in carla based on sumo information.
|
||||||
|
if self.args.tls_manager == 'sumo':
|
||||||
|
common_landmarks = self.sumo.traffic_light_ids & self.carla.traffic_light_ids
|
||||||
|
for landmark_id in common_landmarks:
|
||||||
|
sumo_tl_state = self.sumo.get_traffic_light_state(landmark_id)
|
||||||
|
carla_tl_state = BridgeHelper.get_carla_traffic_light_state(sumo_tl_state)
|
||||||
|
|
||||||
|
self.carla.synchronize_traffic_light(landmark_id, carla_tl_state)
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# carla-->sumo sync
|
# carla-->sumo sync
|
||||||
# -----------------
|
# -----------------
|
||||||
|
@ -159,8 +171,8 @@ class SimulationSynchronization(object):
|
||||||
if self.args.sync_vehicle_lights:
|
if self.args.sync_vehicle_lights:
|
||||||
carla_lights = self.carla.get_actor_light_state(carla_actor_id)
|
carla_lights = self.carla.get_actor_light_state(carla_actor_id)
|
||||||
if carla_lights is not None:
|
if carla_lights is not None:
|
||||||
sumo_lights = BridgeHelper.get_sumo_lights_state(
|
sumo_lights = BridgeHelper.get_sumo_lights_state(sumo_actor.signals,
|
||||||
sumo_actor.signals, carla_lights)
|
carla_lights)
|
||||||
else:
|
else:
|
||||||
sumo_lights = None
|
sumo_lights = None
|
||||||
else:
|
else:
|
||||||
|
@ -168,6 +180,16 @@ class SimulationSynchronization(object):
|
||||||
|
|
||||||
self.sumo.synchronize_vehicle(sumo_actor_id, sumo_transform, sumo_lights)
|
self.sumo.synchronize_vehicle(sumo_actor_id, sumo_transform, sumo_lights)
|
||||||
|
|
||||||
|
# Updates traffic lights in sumo based on carla information.
|
||||||
|
if self.args.tls_manager == 'carla':
|
||||||
|
common_landmarks = self.sumo.traffic_light_ids & self.carla.traffic_light_ids
|
||||||
|
for landmark_id in common_landmarks:
|
||||||
|
carla_tl_state = self.carla.get_traffic_light_state(landmark_id)
|
||||||
|
sumo_tl_state = BridgeHelper.get_sumo_traffic_light_state(carla_tl_state)
|
||||||
|
|
||||||
|
# Updates all the sumo links related to this landmark.
|
||||||
|
self.sumo.synchronize_traffic_light(landmark_id, sumo_tl_state)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Cleans up synchronization.
|
Cleans up synchronization.
|
||||||
|
@ -185,7 +207,8 @@ class SimulationSynchronization(object):
|
||||||
for sumo_actor_id in self.carla2sumo_ids.values():
|
for sumo_actor_id in self.carla2sumo_ids.values():
|
||||||
self.sumo.destroy_actor(sumo_actor_id)
|
self.sumo.destroy_actor(sumo_actor_id)
|
||||||
|
|
||||||
# Closing sumo client.
|
# Closing sumo and carla client.
|
||||||
|
self.carla.close()
|
||||||
self.sumo.close()
|
self.sumo.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -252,13 +275,18 @@ if __name__ == '__main__':
|
||||||
argparser.add_argument('--sync-vehicle-color',
|
argparser.add_argument('--sync-vehicle-color',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='synchronize vehicle color (default: False)')
|
help='synchronize vehicle color (default: False)')
|
||||||
argparser.add_argument('--sync-all',
|
argparser.add_argument('--sync-vehicle-all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='synchronize all vehicle properties (default: False)')
|
help='synchronize all vehicle properties (default: False)')
|
||||||
|
argparser.add_argument('--tls-manager',
|
||||||
|
type=str,
|
||||||
|
choices=['none', 'sumo', 'carla'],
|
||||||
|
help="select traffic light manager (default: none)",
|
||||||
|
default='none')
|
||||||
argparser.add_argument('--debug', action='store_true', help='enable debug messages')
|
argparser.add_argument('--debug', action='store_true', help='enable debug messages')
|
||||||
arguments = argparser.parse_args()
|
arguments = argparser.parse_args()
|
||||||
|
|
||||||
if arguments.sync_all is True:
|
if arguments.sync_vehicle_all is True:
|
||||||
arguments.sync_vehicle_lights = True
|
arguments.sync_vehicle_lights = True
|
||||||
arguments.sync_vehicle_color = True
|
arguments.sync_vehicle_color = True
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#
|
#
|
||||||
# This work is licensed under the terms of the MIT license.
|
# This work is licensed under the terms of the MIT license.
|
||||||
# For a copy, see <https://opensource.org/licenses/MIT>.
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
""" This module provides a helper for the co-simulation between sumo and carla ."""
|
""" This module provides a helper for the co-simulation between sumo and carla ."""
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
|
@ -20,7 +19,7 @@ import random
|
||||||
import carla # pylint: disable=import-error
|
import carla # pylint: disable=import-error
|
||||||
import traci # pylint: disable=import-error
|
import traci # pylint: disable=import-error
|
||||||
|
|
||||||
from .sumo_simulation import SumoVehSignal
|
from .sumo_simulation import SumoSignalState, SumoVehSignal
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
# -- Bridge helper (SUMO <=> CARLA) ----------------------------------------------------------------
|
# -- Bridge helper (SUMO <=> CARLA) ----------------------------------------------------------------
|
||||||
|
@ -126,11 +125,11 @@ class BridgeHelper(object):
|
||||||
blueprint = BridgeHelper._get_recommended_carla_blueprint(sumo_actor)
|
blueprint = BridgeHelper._get_recommended_carla_blueprint(sumo_actor)
|
||||||
if blueprint is not None:
|
if blueprint is not None:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
'sumo vtype %s not found in carla. The following blueprint will be used: %s'
|
'sumo vtype %s not found in carla. The following blueprint will be used: %s',
|
||||||
, type_id, blueprint.id)
|
type_id, blueprint.id)
|
||||||
else:
|
else:
|
||||||
logging.error(
|
logging.error('sumo vtype %s not supported. No vehicle will be spawned in carla',
|
||||||
'sumo vtype %s not supported. No vehicle will be spawned in carla', type_id)
|
type_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if blueprint.has_attribute('color'):
|
if blueprint.has_attribute('color'):
|
||||||
|
@ -327,3 +326,41 @@ class BridgeHelper(object):
|
||||||
current_lights ^= SumoVehSignal.BACKDRIVE
|
current_lights ^= SumoVehSignal.BACKDRIVE
|
||||||
|
|
||||||
return current_lights
|
return current_lights
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_carla_traffic_light_state(sumo_tl_state):
|
||||||
|
"""
|
||||||
|
Returns carla traffic light state based on sumo traffic light state.
|
||||||
|
"""
|
||||||
|
if sumo_tl_state == SumoSignalState.RED or sumo_tl_state == SumoSignalState.RED_YELLOW:
|
||||||
|
return carla.TrafficLightState.Red
|
||||||
|
|
||||||
|
elif sumo_tl_state == SumoSignalState.YELLOW:
|
||||||
|
return carla.TrafficLightState.Yellow
|
||||||
|
|
||||||
|
elif sumo_tl_state == SumoSignalState.GREEN or \
|
||||||
|
sumo_tl_state == SumoSignalState.GREEN_WITHOUT_PRIORITY:
|
||||||
|
return carla.TrafficLightState.Green
|
||||||
|
|
||||||
|
elif sumo_tl_state == SumoSignalState.OFF:
|
||||||
|
return carla.TrafficLightState.Off
|
||||||
|
|
||||||
|
else: # SumoSignalState.GREEN_RIGHT_TURN and SumoSignalState.OFF_BLINKING
|
||||||
|
return carla.TrafficLightState.Unknown
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_sumo_traffic_light_state(carla_tl_state):
|
||||||
|
"""
|
||||||
|
Returns sumo traffic light state based on carla traffic light state.
|
||||||
|
"""
|
||||||
|
if carla_tl_state == carla.TrafficLightState.Red:
|
||||||
|
return SumoSignalState.RED
|
||||||
|
|
||||||
|
elif carla_tl_state == carla.TrafficLightState.Yellow:
|
||||||
|
return SumoSignalState.YELLOW
|
||||||
|
|
||||||
|
elif carla_tl_state == carla.TrafficLightState.Green:
|
||||||
|
return SumoSignalState.GREEN
|
||||||
|
|
||||||
|
else: # carla.TrafficLightState.Off and carla.TrafficLightState.Unknown
|
||||||
|
return SumoSignalState.OFF
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#
|
#
|
||||||
# This work is licensed under the terms of the MIT license.
|
# This work is licensed under the terms of the MIT license.
|
||||||
# For a copy, see <https://opensource.org/licenses/MIT>.
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
""" This module is responsible for the management of the carla simulation. """
|
""" This module is responsible for the management of the carla simulation. """
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
|
@ -27,7 +26,6 @@ class CarlaSimulation(object):
|
||||||
"""
|
"""
|
||||||
CarlaSimulation is responsible for the management of the carla simulation.
|
CarlaSimulation is responsible for the management of the carla simulation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self.args = args
|
self.args = args
|
||||||
host = args.carla_host
|
host = args.carla_host
|
||||||
|
@ -50,6 +48,34 @@ class CarlaSimulation(object):
|
||||||
self.spawned_actors = set()
|
self.spawned_actors = set()
|
||||||
self.destroyed_actors = set()
|
self.destroyed_actors = set()
|
||||||
|
|
||||||
|
# This is a temporal workaround to avoid the issue of retrieving traffic lights from
|
||||||
|
# landmarks.
|
||||||
|
# Set traffic lights.
|
||||||
|
self._tls = {} # {landmark_id: traffic_ligth_actor}
|
||||||
|
|
||||||
|
self._location = {
|
||||||
|
'121': carla.Location(42.02934082, 101.56253906, 0.0),
|
||||||
|
'123': carla.Location(46.10708984, 92.04954102, 0.15238953),
|
||||||
|
|
||||||
|
'130': carla.Location(101.69419922, 61.59494141, 0.0),
|
||||||
|
'129': carla.Location(92.17053711, 57.36221191, 0.0),
|
||||||
|
|
||||||
|
'136': carla.Location(61.48416016, 50.7382666, 0.0),
|
||||||
|
'135': carla.Location(57.23968262, 59.23875977, 0.15238953)
|
||||||
|
}
|
||||||
|
for carla_actor in self.world.get_actors():
|
||||||
|
for landmark_id, landmark_location in self._location.items():
|
||||||
|
if carla_actor.get_location().distance(landmark_location) < 0.1:
|
||||||
|
self._tls[landmark_id] = carla_actor
|
||||||
|
|
||||||
|
# for landmark in self.world.get_map().get_all_landmarks_of_type('1000001'):
|
||||||
|
# if landmark.id != '':
|
||||||
|
# traffic_ligth = self.world.get_traffic_light(landmark)
|
||||||
|
# if traffic_ligth is not None:
|
||||||
|
# self._tls[landmark.id] = traffic_ligth
|
||||||
|
# else:
|
||||||
|
# logging.warning('Landmark %s is not linked to any traffic light', landmark.id)
|
||||||
|
|
||||||
def get_actor(self, actor_id):
|
def get_actor(self, actor_id):
|
||||||
"""
|
"""
|
||||||
Accessor for carla actor.
|
Accessor for carla actor.
|
||||||
|
@ -71,6 +97,31 @@ class CarlaSimulation(object):
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def traffic_light_ids(self):
|
||||||
|
return set(self._tls.keys())
|
||||||
|
|
||||||
|
def get_traffic_light_state(self, landmark_id):
|
||||||
|
"""
|
||||||
|
Accessor for traffic light state.
|
||||||
|
|
||||||
|
If the traffic ligth does not exist, returns None.
|
||||||
|
"""
|
||||||
|
if landmark_id not in self._tls:
|
||||||
|
return None
|
||||||
|
return self._tls[landmark_id].state
|
||||||
|
|
||||||
|
def switch_off_traffic_lights(self):
|
||||||
|
"""
|
||||||
|
Switch off all traffic lights.
|
||||||
|
"""
|
||||||
|
for actor in self.world.get_actors():
|
||||||
|
if actor.type_id == 'traffic.traffic_light':
|
||||||
|
actor.freeze(True)
|
||||||
|
# We set the traffic light to 'green' because 'off' state sets the traffic light to
|
||||||
|
# 'red'.
|
||||||
|
actor.set_state(carla.TrafficLightState.Green)
|
||||||
|
|
||||||
def spawn_actor(self, blueprint, transform):
|
def spawn_actor(self, blueprint, transform):
|
||||||
"""
|
"""
|
||||||
Spawns a new actor.
|
Spawns a new actor.
|
||||||
|
@ -79,13 +130,12 @@ class CarlaSimulation(object):
|
||||||
:param transform: transform where the actor will be spawned.
|
:param transform: transform where the actor will be spawned.
|
||||||
:return: actor id if the actor is successfully spawned. Otherwise, INVALID_ACTOR_ID.
|
:return: actor id if the actor is successfully spawned. Otherwise, INVALID_ACTOR_ID.
|
||||||
"""
|
"""
|
||||||
transform = carla.Transform(
|
transform = carla.Transform(transform.location + carla.Location(0, 0, SPAWN_OFFSET_Z),
|
||||||
transform.location + carla.Location(0, 0, SPAWN_OFFSET_Z),
|
|
||||||
transform.rotation)
|
transform.rotation)
|
||||||
|
|
||||||
batch = [
|
batch = [
|
||||||
carla.command.SpawnActor(blueprint, transform)
|
carla.command.SpawnActor(blueprint, transform).then(
|
||||||
.then(carla.command.SetSimulatePhysics(carla.command.FutureActor, False))
|
carla.command.SetSimulatePhysics(carla.command.FutureActor, False))
|
||||||
]
|
]
|
||||||
response = self.client.apply_batch_sync(batch, False)[0]
|
response = self.client.apply_batch_sync(batch, False)[0]
|
||||||
if response.error:
|
if response.error:
|
||||||
|
@ -121,6 +171,22 @@ class CarlaSimulation(object):
|
||||||
vehicle.set_light_state(carla.VehicleLightState(lights))
|
vehicle.set_light_state(carla.VehicleLightState(lights))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def synchronize_traffic_light(self, landmark_id, state):
|
||||||
|
"""
|
||||||
|
Updates traffic light state.
|
||||||
|
|
||||||
|
:param landmark_id: id of the landmark to be updated.
|
||||||
|
:param state: new traffic light state.
|
||||||
|
:return: True if successfully updated. Otherwise, False.
|
||||||
|
"""
|
||||||
|
if not landmark_id in self._tls:
|
||||||
|
logging.warning('Landmark %s not found in carla', landmark_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
traffic_light = self._tls[landmark_id]
|
||||||
|
traffic_light.set_state(state)
|
||||||
|
return True
|
||||||
|
|
||||||
def tick(self):
|
def tick(self):
|
||||||
"""
|
"""
|
||||||
Tick to carla simulation.
|
Tick to carla simulation.
|
||||||
|
@ -128,9 +194,16 @@ class CarlaSimulation(object):
|
||||||
self.world.tick()
|
self.world.tick()
|
||||||
|
|
||||||
# Update data structures for the current frame.
|
# Update data structures for the current frame.
|
||||||
current_actors = set([
|
current_actors = set(
|
||||||
vehicle.id for vehicle in self.world.get_actors().filter('vehicle.*')
|
[vehicle.id for vehicle in self.world.get_actors().filter('vehicle.*')])
|
||||||
])
|
|
||||||
self.spawned_actors = current_actors.difference(self._active_actors)
|
self.spawned_actors = current_actors.difference(self._active_actors)
|
||||||
self.destroyed_actors = self._active_actors.difference(current_actors)
|
self.destroyed_actors = self._active_actors.difference(current_actors)
|
||||||
self._active_actors = current_actors
|
self._active_actors = current_actors
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Closes carla client.
|
||||||
|
"""
|
||||||
|
for actor in self.world.get_actors():
|
||||||
|
if actor.type_id == 'traffic.traffic_light':
|
||||||
|
actor.freeze(False)
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#
|
#
|
||||||
# This work is licensed under the terms of the MIT license.
|
# This work is licensed under the terms of the MIT license.
|
||||||
# For a copy, see <https://opensource.org/licenses/MIT>.
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
""" This module defines constants used for the sumo-carla co-simulation. """
|
""" This module defines constants used for the sumo-carla co-simulation. """
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#
|
#
|
||||||
# This work is licensed under the terms of the MIT license.
|
# This work is licensed under the terms of the MIT license.
|
||||||
# For a copy, see <https://opensource.org/licenses/MIT>.
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
""" This module is responsible for the management of the sumo simulation. """
|
""" This module is responsible for the management of the sumo simulation. """
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
|
@ -27,6 +26,21 @@ from .constants import INVALID_ACTOR_ID
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
# https://sumo.dlr.de/docs/Simulation/Traffic_Lights.html#signal_state_definitions
|
||||||
|
class SumoSignalState(object):
|
||||||
|
"""
|
||||||
|
SumoSignalState contains the different traffic light states.
|
||||||
|
"""
|
||||||
|
RED = 'r'
|
||||||
|
YELLOW = 'y'
|
||||||
|
GREEN = 'G'
|
||||||
|
GREEN_WITHOUT_PRIORITY = 'g'
|
||||||
|
GREEN_RIGHT_TURN = 's'
|
||||||
|
RED_YELLOW = 'u'
|
||||||
|
OFF_BLINKING = 'o'
|
||||||
|
OFF = 'O'
|
||||||
|
|
||||||
|
|
||||||
# https://sumo.dlr.de/docs/TraCI/Vehicle_Signalling.html
|
# https://sumo.dlr.de/docs/TraCI/Vehicle_Signalling.html
|
||||||
class SumoVehSignal(object):
|
class SumoVehSignal(object):
|
||||||
"""
|
"""
|
||||||
|
@ -82,19 +96,191 @@ class SumoActorClass(enum.Enum):
|
||||||
CUSTOM2 = "custom2"
|
CUSTOM2 = "custom2"
|
||||||
|
|
||||||
|
|
||||||
SumoActor = collections.namedtuple(
|
SumoActor = collections.namedtuple('SumoActor', 'type_id vclass transform signals extent color')
|
||||||
'SumoActor', 'type_id vclass transform signals extent color')
|
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
# -- sumo simulation -------------------------------------------------------------------------------
|
# -- sumo simulation -------------------------------------------------------------------------------
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SumoTLLogic(object):
|
||||||
|
"""
|
||||||
|
SumoTLLogic holds the data relative to a traffic light in sumo.
|
||||||
|
"""
|
||||||
|
def __init__(self, tlid, states, parameters):
|
||||||
|
self.tlid = tlid
|
||||||
|
self.states = states
|
||||||
|
|
||||||
|
self._landmark2link = {}
|
||||||
|
self._link2landmark = {}
|
||||||
|
for link_index, landmark_id in parameters.items():
|
||||||
|
# Link index information is added in the parameter as 'linkSignalID:x'
|
||||||
|
link_index = int(link_index.split(':')[1])
|
||||||
|
|
||||||
|
if landmark_id not in self._landmark2link:
|
||||||
|
self._landmark2link[landmark_id] = []
|
||||||
|
self._landmark2link[landmark_id].append((tlid, link_index))
|
||||||
|
self._link2landmark[(tlid, link_index)] = landmark_id
|
||||||
|
|
||||||
|
def get_number_signals(self):
|
||||||
|
"""
|
||||||
|
Returns number of internal signals of the traffic light.
|
||||||
|
"""
|
||||||
|
if len(self.states) > 0:
|
||||||
|
return len(self.states[0])
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_all_signals(self):
|
||||||
|
"""
|
||||||
|
Returns all the signals of the traffic ligth.
|
||||||
|
:returns list: [(tlid, link_index), (tlid, link_index), ...]
|
||||||
|
"""
|
||||||
|
return [(self.tlid, i) for i in range(self.get_number_signals())]
|
||||||
|
|
||||||
|
def get_all_landmarks(self):
|
||||||
|
"""
|
||||||
|
Returns all the landmarks associated with this traffic light.
|
||||||
|
"""
|
||||||
|
return self._landmark2link.keys()
|
||||||
|
|
||||||
|
def get_associated_signals(self, landmark_id):
|
||||||
|
"""
|
||||||
|
Returns all the signals associated with the given landmark.
|
||||||
|
:returns list: [(tlid, link_index), (tlid), (link_index), ...]
|
||||||
|
"""
|
||||||
|
return self._landmark2link.get(landmark_id, [])
|
||||||
|
|
||||||
|
|
||||||
|
class SumoTLManager(object):
|
||||||
|
"""
|
||||||
|
SumoTLManager is responsible for the management of the sumo traffic lights (i.e., keeps control
|
||||||
|
of the current program, phase, ...)
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self._tls = {} # {tlid: {program_id: SumoTLLogic}
|
||||||
|
self._current_program = {} # {tlid: program_id}
|
||||||
|
self._current_phase = {} # {tlid: index_phase}
|
||||||
|
|
||||||
|
for tlid in traci.trafficlight.getIDList():
|
||||||
|
self.subscribe(tlid)
|
||||||
|
|
||||||
|
self._tls[tlid] = {}
|
||||||
|
for tllogic in traci.trafficlight.getAllProgramLogics(tlid):
|
||||||
|
states = [phase.state for phase in tllogic.getPhases()]
|
||||||
|
parameters = tllogic.getParameters()
|
||||||
|
tl = SumoTLLogic(tlid, states, parameters)
|
||||||
|
self._tls[tlid][tllogic.programID] = tl
|
||||||
|
|
||||||
|
# Get current status of the traffic lights.
|
||||||
|
self._current_program[tlid] = traci.trafficlight.getProgram(tlid)
|
||||||
|
self._current_phase[tlid] = traci.trafficlight.getPhase(tlid)
|
||||||
|
|
||||||
|
self._off = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def subscribe(tlid):
|
||||||
|
"""
|
||||||
|
Subscribe the given traffic ligth to the following variables:
|
||||||
|
|
||||||
|
* Current program.
|
||||||
|
* Current phase.
|
||||||
|
"""
|
||||||
|
traci.trafficlight.subscribe(tlid, [
|
||||||
|
traci.constants.TL_CURRENT_PROGRAM,
|
||||||
|
traci.constants.TL_CURRENT_PHASE,
|
||||||
|
])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unsubscribe(tlid):
|
||||||
|
"""
|
||||||
|
Unsubscribe the given traffic ligth from receiving updated information each step.
|
||||||
|
"""
|
||||||
|
traci.trafficlight.unsubscribe(tlid)
|
||||||
|
|
||||||
|
def get_all_signals(self):
|
||||||
|
"""
|
||||||
|
Returns all the traffic light signals.
|
||||||
|
"""
|
||||||
|
signals = set()
|
||||||
|
for tlid, program_id in self._current_program.items():
|
||||||
|
signals.update(self._tls[tlid][program_id].get_all_signals())
|
||||||
|
return signals
|
||||||
|
|
||||||
|
def get_all_landmarks(self):
|
||||||
|
"""
|
||||||
|
Returns all the landmarks associated with a traffic light in the simulation.
|
||||||
|
"""
|
||||||
|
landmarks = set()
|
||||||
|
for tlid, program_id in self._current_program.items():
|
||||||
|
landmarks.update(self._tls[tlid][program_id].get_all_landmarks())
|
||||||
|
return landmarks
|
||||||
|
|
||||||
|
def get_all_associated_signals(self, landmark_id):
|
||||||
|
"""
|
||||||
|
Returns all the signals associated with the given landmark.
|
||||||
|
:returns list: [(tlid, link_index), (tlid), (link_index), ...]
|
||||||
|
"""
|
||||||
|
signals = set()
|
||||||
|
for tlid, program_id in self._current_program.items():
|
||||||
|
signals.update(self._tls[tlid][program_id].get_associated_signals(landmark_id))
|
||||||
|
return signals
|
||||||
|
|
||||||
|
def get_state(self, landmark_id):
|
||||||
|
"""
|
||||||
|
Returns the traffic light state of the signals associated with the given landmark.
|
||||||
|
"""
|
||||||
|
states = set()
|
||||||
|
for tlid, link_index in self.get_all_associated_signals(landmark_id):
|
||||||
|
current_program = self._current_program[tlid]
|
||||||
|
current_phase = self._current_phase[tlid]
|
||||||
|
|
||||||
|
tl = self._tls[tlid][current_program]
|
||||||
|
states.update(tl.states[current_phase][link_index])
|
||||||
|
|
||||||
|
if len(states) == 1:
|
||||||
|
return states.pop()
|
||||||
|
elif len(states) > 1:
|
||||||
|
logging.warning('Landmark %s is associated with signals with different states',
|
||||||
|
landmark_id)
|
||||||
|
return SumoSignalState.RED
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_state(self, landmark_id, state):
|
||||||
|
"""
|
||||||
|
Updates the state of all the signals associated with the given landmark.
|
||||||
|
"""
|
||||||
|
for tlid, link_index in self.get_all_associated_signals(landmark_id):
|
||||||
|
traci.trafficlight.setLinkState(tlid, link_index, state)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def switch_off(self):
|
||||||
|
"""
|
||||||
|
Switch off all traffic lights.
|
||||||
|
"""
|
||||||
|
for tlid, link_index in self.get_all_signals():
|
||||||
|
traci.trafficlight.setLinkState(tlid, link_index, SumoSignalState.OFF)
|
||||||
|
self._off = True
|
||||||
|
|
||||||
|
def tick(self):
|
||||||
|
"""
|
||||||
|
Tick to traffic light manager
|
||||||
|
"""
|
||||||
|
if self._off is False:
|
||||||
|
for tl_id in traci.trafficlight.getIDList():
|
||||||
|
results = traci.trafficlight.getSubscriptionResults(tl_id)
|
||||||
|
current_program = results[traci.constants.TL_CURRENT_PROGRAM]
|
||||||
|
current_phase = results[traci.constants.TL_CURRENT_PHASE]
|
||||||
|
|
||||||
|
if current_program != 'online':
|
||||||
|
self._current_program[tl_id] = current_program
|
||||||
|
self._current_phase[tl_id] = current_phase
|
||||||
|
|
||||||
|
|
||||||
class SumoSimulation(object):
|
class SumoSimulation(object):
|
||||||
"""
|
"""
|
||||||
SumoSimulation is responsible for the management of the sumo simulation.
|
SumoSimulation is responsible for the management of the sumo simulation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self.args = args
|
self.args = args
|
||||||
host = args.sumo_host
|
host = args.sumo_host
|
||||||
|
@ -110,9 +296,8 @@ class SumoSimulation(object):
|
||||||
if args.sumo_gui is True:
|
if args.sumo_gui is True:
|
||||||
logging.info('Remember to press the play button to start the simulation')
|
logging.info('Remember to press the play button to start the simulation')
|
||||||
|
|
||||||
traci.start([
|
traci.start([sumo_binary,
|
||||||
sumo_binary,
|
'--configuration-file', args.sumo_cfg_file,
|
||||||
"-c", args.sumo_cfg_file,
|
|
||||||
'--step-length', str(args.step_length),
|
'--step-length', str(args.step_length),
|
||||||
'--lateral-resolution', '0.25',
|
'--lateral-resolution', '0.25',
|
||||||
'--collision.check-junctions'
|
'--collision.check-junctions'
|
||||||
|
@ -122,16 +307,23 @@ class SumoSimulation(object):
|
||||||
logging.info('Connection to sumo server. Host: %s Port: %s', host, port)
|
logging.info('Connection to sumo server. Host: %s Port: %s', host, port)
|
||||||
traci.init(host=host, port=port)
|
traci.init(host=host, port=port)
|
||||||
|
|
||||||
# Structures to keep track of the spawned and destroyed vehicles at each time step.
|
|
||||||
self.spawned_actors = set()
|
|
||||||
self.destroyed_actors = set()
|
|
||||||
|
|
||||||
# Creating a random route to be able to spawn carla actors.
|
# Creating a random route to be able to spawn carla actors.
|
||||||
traci.route.add("carla_route", [traci.edge.getIDList()[0]])
|
traci.route.add("carla_route", [traci.edge.getIDList()[0]])
|
||||||
|
|
||||||
# Variable to asign an id to new added actors.
|
# Variable to asign an id to new added actors.
|
||||||
self._sequential_id = 0
|
self._sequential_id = 0
|
||||||
|
|
||||||
|
# Structures to keep track of the spawned and destroyed vehicles at each time step.
|
||||||
|
self.spawned_actors = set()
|
||||||
|
self.destroyed_actors = set()
|
||||||
|
|
||||||
|
# Traffic light manager.
|
||||||
|
self.traffic_light_manager = SumoTLManager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def traffic_light_ids(self):
|
||||||
|
return self.traffic_light_manager.get_all_landmarks()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def subscribe(actor_id):
|
def subscribe(actor_id):
|
||||||
"""
|
"""
|
||||||
|
@ -148,12 +340,10 @@ class SumoSimulation(object):
|
||||||
* Signals.
|
* Signals.
|
||||||
"""
|
"""
|
||||||
traci.vehicle.subscribe(actor_id, [
|
traci.vehicle.subscribe(actor_id, [
|
||||||
traci.constants.VAR_TYPE, traci.constants.VAR_VEHICLECLASS,
|
traci.constants.VAR_TYPE, traci.constants.VAR_VEHICLECLASS, traci.constants.VAR_COLOR,
|
||||||
traci.constants.VAR_COLOR, traci.constants.VAR_LENGTH,
|
traci.constants.VAR_LENGTH, traci.constants.VAR_WIDTH, traci.constants.VAR_HEIGHT,
|
||||||
traci.constants.VAR_WIDTH, traci.constants.VAR_HEIGHT,
|
traci.constants.VAR_POSITION3D, traci.constants.VAR_ANGLE, traci.constants.VAR_SLOPE,
|
||||||
traci.constants.VAR_POSITION3D, traci.constants.VAR_ANGLE,
|
traci.constants.VAR_SPEED, traci.constants.VAR_SPEED_LAT, traci.constants.VAR_SIGNALS
|
||||||
traci.constants.VAR_SLOPE, traci.constants.VAR_SPEED,
|
|
||||||
traci.constants.VAR_SPEED_LAT, traci.constants.VAR_SIGNALS
|
|
||||||
])
|
])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -194,14 +384,9 @@ class SumoSimulation(object):
|
||||||
height = results[traci.constants.VAR_HEIGHT]
|
height = results[traci.constants.VAR_HEIGHT]
|
||||||
|
|
||||||
location = list(results[traci.constants.VAR_POSITION3D])
|
location = list(results[traci.constants.VAR_POSITION3D])
|
||||||
rotation = [
|
rotation = [results[traci.constants.VAR_SLOPE], results[traci.constants.VAR_ANGLE], 0.0]
|
||||||
results[traci.constants.VAR_SLOPE],
|
transform = carla.Transform(carla.Location(location[0], location[1], location[2]),
|
||||||
results[traci.constants.VAR_ANGLE], 0.0
|
carla.Rotation(rotation[0], rotation[1], rotation[2]))
|
||||||
]
|
|
||||||
transform = carla.Transform(
|
|
||||||
carla.Location(location[0], location[1], location[2]),
|
|
||||||
carla.Rotation(rotation[0], rotation[1], rotation[2])
|
|
||||||
)
|
|
||||||
|
|
||||||
signals = results[traci.constants.VAR_SIGNALS]
|
signals = results[traci.constants.VAR_SIGNALS]
|
||||||
extent = carla.Vector3D(length / 2.0, width / 2.0, height / 2.0)
|
extent = carla.Vector3D(length / 2.0, width / 2.0, height / 2.0)
|
||||||
|
@ -239,6 +424,20 @@ class SumoSimulation(object):
|
||||||
"""
|
"""
|
||||||
traci.vehicle.remove(actor_id)
|
traci.vehicle.remove(actor_id)
|
||||||
|
|
||||||
|
def get_traffic_light_state(self, landmark_id):
|
||||||
|
"""
|
||||||
|
Accessor for traffic light state.
|
||||||
|
|
||||||
|
If the traffic ligth does not exist, returns None.
|
||||||
|
"""
|
||||||
|
return self.traffic_light_manager.get_state(landmark_id)
|
||||||
|
|
||||||
|
def switch_off_traffic_lights(self):
|
||||||
|
"""
|
||||||
|
Switch off all traffic lights.
|
||||||
|
"""
|
||||||
|
self.traffic_light_manager.switch_off()
|
||||||
|
|
||||||
def synchronize_vehicle(self, vehicle_id, transform, signals=None):
|
def synchronize_vehicle(self, vehicle_id, transform, signals=None):
|
||||||
"""
|
"""
|
||||||
Updates vehicle state.
|
Updates vehicle state.
|
||||||
|
@ -256,11 +455,22 @@ class SumoSimulation(object):
|
||||||
traci.vehicle.setSignals(vehicle_id, signals)
|
traci.vehicle.setSignals(vehicle_id, signals)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def synchronize_traffic_light(self, landmark_id, state):
|
||||||
|
"""
|
||||||
|
Updates traffic light state.
|
||||||
|
|
||||||
|
:param tl_id: id of the traffic light to be updated (logic id, link index).
|
||||||
|
:param state: new traffic light state.
|
||||||
|
:return: True if successfully updated. Otherwise, False.
|
||||||
|
"""
|
||||||
|
self.traffic_light_manager.set_state(landmark_id, state)
|
||||||
|
|
||||||
def tick(self):
|
def tick(self):
|
||||||
"""
|
"""
|
||||||
Tick to sumo simulation.
|
Tick to sumo simulation.
|
||||||
"""
|
"""
|
||||||
traci.simulationStep()
|
traci.simulationStep()
|
||||||
|
self.traffic_light_manager.tick()
|
||||||
|
|
||||||
# Update data structures for the current frame.
|
# Update data structures for the current frame.
|
||||||
self.spawned_actors = set(traci.simulation.getDepartedIDList())
|
self.spawned_actors = set(traci.simulation.getDepartedIDList())
|
||||||
|
|
|
@ -0,0 +1,526 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2020 Computer Vision Center (CVC) at the Universitat Autonoma de
|
||||||
|
# Barcelona (UAB).
|
||||||
|
#
|
||||||
|
# This work is licensed under the terms of the MIT license.
|
||||||
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
"""
|
||||||
|
Script to generate sumo nets based on opendrive files. Internally, it uses netconvert to generate
|
||||||
|
the net and inserts, manually, the traffic light landmarks retrieved from the opendrive.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ==================================================================================================
|
||||||
|
# -- imports ---------------------------------------------------------------------------------------
|
||||||
|
# ==================================================================================================
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import bisect
|
||||||
|
import collections
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import lxml.etree as ET # pylint: disable=import-error
|
||||||
|
|
||||||
|
# ==================================================================================================
|
||||||
|
# -- find carla module -----------------------------------------------------------------------------
|
||||||
|
# ==================================================================================================
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.path.append(
|
||||||
|
glob.glob('../../../PythonAPI/carla/dist/carla-*%d.%d-%s.egg' %
|
||||||
|
(sys.version_info.major, sys.version_info.minor,
|
||||||
|
'win-amd64' if os.name == 'nt' else 'linux-x86_64'))[0])
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ==================================================================================================
|
||||||
|
# -- find sumo modules -----------------------------------------------------------------------------
|
||||||
|
# ==================================================================================================
|
||||||
|
|
||||||
|
if 'SUMO_HOME' in os.environ:
|
||||||
|
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
|
||||||
|
else:
|
||||||
|
sys.exit("please declare environment variable 'SUMO_HOME'")
|
||||||
|
|
||||||
|
# ==================================================================================================
|
||||||
|
# -- imports ---------------------------------------------------------------------------------------
|
||||||
|
# ==================================================================================================
|
||||||
|
|
||||||
|
import carla
|
||||||
|
import sumolib
|
||||||
|
|
||||||
|
# ==================================================================================================
|
||||||
|
# -- topology --------------------------------------------------------------------------------------
|
||||||
|
# ==================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SumoTopology(object):
|
||||||
|
"""
|
||||||
|
This object holds the topology of a sumo net. Internally, the information is structured as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
- topology: {
|
||||||
|
(road_id, lane_id): [(successor_road_id, succesor_lane_id), ...], ...}
|
||||||
|
- paths: {
|
||||||
|
(road_id, lane_id): [
|
||||||
|
((in_road_id, in_lane_id), (out_road_id, out_lane_id)), ...
|
||||||
|
], ...}
|
||||||
|
- odr2sumo_ids: {
|
||||||
|
(odr_road_id, odr_lane_id): [(sumo_edge_id, sumo_lane_id), ...], ...}
|
||||||
|
"""
|
||||||
|
def __init__(self, topology, paths, odr2sumo_ids):
|
||||||
|
# Contains only standard roads.
|
||||||
|
self._topology = topology
|
||||||
|
# Contaions only roads that belong to a junction.
|
||||||
|
self._paths = paths
|
||||||
|
# Mapped ids between sumo and opendrive.
|
||||||
|
self._odr2sumo_ids = odr2sumo_ids
|
||||||
|
|
||||||
|
# http://sumo.sourceforge.net/userdoc/Networks/Import/OpenDRIVE.html#dealing_with_lane_sections
|
||||||
|
def get_sumo_id(self, odr_road_id, odr_lane_id, s=0):
|
||||||
|
"""
|
||||||
|
Returns the pair (sumo_edge_id, sumo_lane index) corresponding to the provided odr pair. The
|
||||||
|
argument 's' allows selecting the better sumo edge when it has been split into different
|
||||||
|
edges due to different odr lane sections.
|
||||||
|
"""
|
||||||
|
if (odr_road_id, odr_lane_id) not in self._odr2sumo_ids:
|
||||||
|
return None
|
||||||
|
|
||||||
|
sumo_ids = list(self._odr2sumo_ids[(odr_road_id, odr_lane_id)])
|
||||||
|
|
||||||
|
if (len(sumo_ids)) == 1:
|
||||||
|
return sumo_ids[0]
|
||||||
|
|
||||||
|
# The edge is split into different lane sections. We return the nearest edge based on the
|
||||||
|
# s coordinate of the provided landmark.
|
||||||
|
else:
|
||||||
|
# Ensures that all the related sumo edges belongs to the same opendrive road but to
|
||||||
|
# different lane sections.
|
||||||
|
assert set([edge.split('.', 1)[0] for edge, lane_index in sumo_ids]) == 1
|
||||||
|
|
||||||
|
s_coords = [float(edge.split('.', 1)[1]) for edge, lane_index in sumo_ids]
|
||||||
|
|
||||||
|
s_coords, sumo_ids = zip(*sorted(zip(s_coords, sumo_ids)))
|
||||||
|
index = bisect.bisect_left(s_coords, s, lo=1) - 1
|
||||||
|
return sumo_ids[index]
|
||||||
|
|
||||||
|
def is_junction(self, odr_road_id, odr_lane_id):
|
||||||
|
"""
|
||||||
|
Checks whether the provided pair (odr_road_id, odr_lane_id) belongs to a junction.
|
||||||
|
"""
|
||||||
|
return (odr_road_id, odr_lane_id) in self._paths
|
||||||
|
|
||||||
|
def get_successors(self, sumo_edge_id, sumo_lane_index):
|
||||||
|
"""
|
||||||
|
Returns the successors (standard roads) of the provided pair (sumo_edge_id, sumo_lane_index)
|
||||||
|
"""
|
||||||
|
if self.is_junction(sumo_edge_id, sumo_lane_index):
|
||||||
|
return []
|
||||||
|
|
||||||
|
return list(self._topology.get((sumo_edge_id, sumo_lane_index), set()))
|
||||||
|
|
||||||
|
def get_incoming(self, odr_road_id, odr_lane_id):
|
||||||
|
"""
|
||||||
|
If the pair (odr_road_id, odr_lane_id) belongs to a junction, returns the incoming edges of
|
||||||
|
the path. Otherwise, return and empty list.
|
||||||
|
"""
|
||||||
|
if not self.is_junction(odr_road_id, odr_lane_id):
|
||||||
|
return []
|
||||||
|
|
||||||
|
result = set([(connection[0][0], connection[0][1])
|
||||||
|
for connection in self._paths[(odr_road_id, odr_lane_id)]])
|
||||||
|
return list(result)
|
||||||
|
|
||||||
|
def get_outgoing(self, odr_road_id, odr_lane_id):
|
||||||
|
"""
|
||||||
|
If the pair (odr_road_id, odr_lane_id) belongs to a junction, returns the outgoing edges of
|
||||||
|
the path. Otherwise, return and empty list.
|
||||||
|
"""
|
||||||
|
if not self.is_junction(odr_road_id, odr_lane_id):
|
||||||
|
return []
|
||||||
|
|
||||||
|
result = set([(connection[1][0], connection[1][1])
|
||||||
|
for connection in self._paths[(odr_road_id, odr_lane_id)]])
|
||||||
|
return list(result)
|
||||||
|
|
||||||
|
def get_path_connectivity(self, odr_road_id, odr_lane_id):
|
||||||
|
"""
|
||||||
|
Returns incoming and outgoing roads of the pair (odr_road_id, odr_lane_id). If the provided
|
||||||
|
pair not belongs to a junction, returns an empty list.
|
||||||
|
"""
|
||||||
|
return list(self._paths.get((odr_road_id, odr_lane_id), set()))
|
||||||
|
|
||||||
|
|
||||||
|
def build_topology(sumo_net):
|
||||||
|
"""
|
||||||
|
Builds sumo topology.
|
||||||
|
"""
|
||||||
|
# --------------------------
|
||||||
|
# OpenDrive->Sumo mapped ids
|
||||||
|
# --------------------------
|
||||||
|
# Only takes into account standard roads.
|
||||||
|
#
|
||||||
|
# odr2sumo_ids = {(odr_road_id, odr_lane_id) : [(sumo_edge_id, sumo_lane_index), ...], ...}
|
||||||
|
odr2sumo_ids = {}
|
||||||
|
for edge in sumo_net.getEdges():
|
||||||
|
for lane in edge.getLanes():
|
||||||
|
if lane.getParam('origId') is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
'Sumo lane {} does not have "origId" parameter. Make sure that the --output.original-names parameter is active when running netconvert.'
|
||||||
|
.format(lane.getID()))
|
||||||
|
|
||||||
|
if len(lane.getParam('origId').split()) > 1:
|
||||||
|
logging.warning('[Building topology] Sumo net contains joined opendrive roads.')
|
||||||
|
|
||||||
|
for odr_id in lane.getParam('origId').split():
|
||||||
|
odr_road_id, odr_lane_id = odr_id.split('_')
|
||||||
|
if (odr_road_id, int(odr_lane_id)) not in odr2sumo_ids:
|
||||||
|
odr2sumo_ids[(odr_road_id, int(odr_lane_id))] = set()
|
||||||
|
odr2sumo_ids[(odr_road_id, int(odr_lane_id))].add((edge.getID(), lane.getIndex()))
|
||||||
|
|
||||||
|
# -----------
|
||||||
|
# Connections
|
||||||
|
# -----------
|
||||||
|
#
|
||||||
|
# topology -- {(sumo_road_id, sumo_lane_index): [(sumo_road_id, sumo_lane_index), ...], ...}
|
||||||
|
# paths -- {(odr_road_id, odr_lane_id): [
|
||||||
|
# ((sumo_edge_id, sumo_lane_index), (sumo_edge_id, sumo_lane_index))
|
||||||
|
# ]}
|
||||||
|
topology = {}
|
||||||
|
paths = {}
|
||||||
|
|
||||||
|
for from_edge in sumo_net.getEdges():
|
||||||
|
for to_edge in sumo_net.getEdges():
|
||||||
|
connections = from_edge.getConnections(to_edge)
|
||||||
|
for connection in connections:
|
||||||
|
from_ = connection.getFromLane()
|
||||||
|
to_ = connection.getToLane()
|
||||||
|
from_edge_id, from_lane_index = from_.getEdge().getID(), from_.getIndex()
|
||||||
|
to_edge_id, to_lane_index = to_.getEdge().getID(), to_.getIndex()
|
||||||
|
|
||||||
|
if (from_edge_id, from_lane_index) not in topology:
|
||||||
|
topology[(from_edge_id, from_lane_index)] = set()
|
||||||
|
|
||||||
|
topology[(from_edge_id, from_lane_index)].add((to_edge_id, to_lane_index))
|
||||||
|
|
||||||
|
# Checking if the connection is an opendrive path.
|
||||||
|
conn_odr_ids = connection.getParam('origId')
|
||||||
|
if conn_odr_ids is not None:
|
||||||
|
if len(conn_odr_ids.split()) > 1:
|
||||||
|
logging.warning(
|
||||||
|
'[Building topology] Sumo net contains joined opendrive paths.')
|
||||||
|
|
||||||
|
for odr_id in conn_odr_ids.split():
|
||||||
|
|
||||||
|
odr_road_id, odr_lane_id = odr_id.split('_')
|
||||||
|
if (odr_road_id, int(odr_lane_id)) not in paths:
|
||||||
|
paths[(odr_road_id, int(odr_lane_id))] = set()
|
||||||
|
|
||||||
|
paths[(odr_road_id, int(odr_lane_id))].add(
|
||||||
|
((from_edge_id, from_lane_index), (to_edge_id, to_lane_index)))
|
||||||
|
|
||||||
|
return SumoTopology(topology, paths, odr2sumo_ids)
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================================================
|
||||||
|
# -- sumo definitions ------------------------------------------------------------------------------
|
||||||
|
# ==================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SumoTrafficLight(object):
|
||||||
|
"""
|
||||||
|
SumoTrafficLight holds all the necessary data to define a traffic light in sumo:
|
||||||
|
|
||||||
|
* connections (tlid, from_road, to_road, from_lane, to_lane, link_index).
|
||||||
|
* phases (duration, state, min_dur, max_dur, nex, name).
|
||||||
|
* parameters.
|
||||||
|
"""
|
||||||
|
DEFAULT_DURATION_GREEN_PHASE = 42
|
||||||
|
DEFAULT_DURATION_YELLOW_PHASE = 3
|
||||||
|
DEFAULT_DURATION_RED_PHASE = 3
|
||||||
|
|
||||||
|
Phase = collections.namedtuple('Phase', 'duration state min_dur max_dur next name')
|
||||||
|
Connection = collections.namedtuple('Connection',
|
||||||
|
'tlid from_road to_road from_lane to_lane link_index')
|
||||||
|
|
||||||
|
def __init__(self, id, program_id='0', offset=0, type='static'):
|
||||||
|
self.id = id
|
||||||
|
self.program_id = program_id
|
||||||
|
self.offset = offset
|
||||||
|
self.type = type
|
||||||
|
|
||||||
|
self.phases = []
|
||||||
|
self.parameters = set()
|
||||||
|
self.connections = set()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_tl_id(from_edge, to_edge):
|
||||||
|
"""
|
||||||
|
Generates sumo traffic light id based on the junction connectivity.
|
||||||
|
"""
|
||||||
|
return '{}:{}'.format(from_edge, to_edge)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_default_program(tl):
|
||||||
|
"""
|
||||||
|
Generates a default program for the given sumo traffic light
|
||||||
|
"""
|
||||||
|
incoming_roads = [connection.from_road for connection in tl.connections]
|
||||||
|
for road in set(incoming_roads):
|
||||||
|
phase_green = ['r'] * len(tl.connections)
|
||||||
|
phase_yellow = ['r'] * len(tl.connections)
|
||||||
|
phase_red = ['r'] * len(tl.connections)
|
||||||
|
|
||||||
|
for connection in tl.connections:
|
||||||
|
if connection.from_road == road:
|
||||||
|
phase_green[connection.link_index] = 'g'
|
||||||
|
phase_yellow[connection.link_index] = 'y'
|
||||||
|
|
||||||
|
tl.add_phase(SumoTrafficLight.DEFAULT_DURATION_GREEN_PHASE, ''.join(phase_green))
|
||||||
|
tl.add_phase(SumoTrafficLight.DEFAULT_DURATION_YELLOW_PHASE, ''.join(phase_yellow))
|
||||||
|
tl.add_phase(SumoTrafficLight.DEFAULT_DURATION_RED_PHASE, ''.join(phase_red))
|
||||||
|
|
||||||
|
def add_phase(self, duration, state, min_dur=-1, max_dur=-1, next=None, name=''):
|
||||||
|
"""
|
||||||
|
Adds a new phase.
|
||||||
|
"""
|
||||||
|
self.phases.append(SumoTrafficLight.Phase(duration, state, min_dur, max_dur, next, name))
|
||||||
|
|
||||||
|
def add_parameter(self, key, value):
|
||||||
|
"""
|
||||||
|
Adds a new parameter.
|
||||||
|
"""
|
||||||
|
self.parameters.add((key, value))
|
||||||
|
|
||||||
|
def add_connection(self, connection):
|
||||||
|
"""
|
||||||
|
Adds a new connection.
|
||||||
|
"""
|
||||||
|
self.connections.add(connection)
|
||||||
|
|
||||||
|
def add_landmark(self,
|
||||||
|
landmark_id,
|
||||||
|
tlid,
|
||||||
|
from_road,
|
||||||
|
to_road,
|
||||||
|
from_lane,
|
||||||
|
to_lane,
|
||||||
|
link_index=-1):
|
||||||
|
"""
|
||||||
|
Adds a new landmark.
|
||||||
|
|
||||||
|
Returns True if the landmark is successfully included. Otherwise, returns False.
|
||||||
|
"""
|
||||||
|
if link_index == -1:
|
||||||
|
link_index = len(self.connections)
|
||||||
|
|
||||||
|
def is_same_connection(c1, c2):
|
||||||
|
return c1.from_road == c2.from_road and c1.to_road == c2.to_road and \
|
||||||
|
c1.from_lane == c2.from_lane and c1.to_lane == c2.to_lane
|
||||||
|
|
||||||
|
connection = SumoTrafficLight.Connection(tlid, from_road, to_road, from_lane, to_lane,
|
||||||
|
link_index)
|
||||||
|
if any([is_same_connection(connection, c) for c in self.connections]):
|
||||||
|
logging.warning(
|
||||||
|
'Different landmarks controlling the same connection. Only one will be included.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.add_connection(connection)
|
||||||
|
self.add_parameter(link_index, landmark_id)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def to_xml(self):
|
||||||
|
info = {
|
||||||
|
'id': self.id,
|
||||||
|
'type': self.type,
|
||||||
|
'programID': self.program_id,
|
||||||
|
'offset': str(self.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_tag = ET.Element('tlLogic', info)
|
||||||
|
for phase in self.phases:
|
||||||
|
ET.SubElement(xml_tag, 'phase', {'state': phase.state, 'duration': str(phase.duration)})
|
||||||
|
for parameter in sorted(self.parameters, key=lambda x: x[0]):
|
||||||
|
ET.SubElement(xml_tag, 'param', {
|
||||||
|
'key': 'linkSignalID:' + str(parameter[0]),
|
||||||
|
'value': str(parameter[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
return xml_tag
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================================================
|
||||||
|
# -- main ------------------------------------------------------------------------------------------
|
||||||
|
# ==================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
def netconvert_carla(args, tmp_path):
|
||||||
|
"""
|
||||||
|
Generates sumo net.
|
||||||
|
"""
|
||||||
|
# ----------
|
||||||
|
# netconvert
|
||||||
|
# ----------
|
||||||
|
tmp_file = os.path.splitext(os.path.basename(args.xodr_file))[0]
|
||||||
|
tmp_sumo_net = os.path.join(tmp_path, tmp_file + '.net.xml')
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.call(['netconvert',
|
||||||
|
'--opendrive', args.xodr_file,
|
||||||
|
'--output-file', tmp_sumo_net,
|
||||||
|
'--geometry.min-radius.fix',
|
||||||
|
'--geometry.remove',
|
||||||
|
'--opendrive.curve-resolution', '1',
|
||||||
|
'--opendrive.import-all-lanes',
|
||||||
|
'--type-files', 'data/opendrive_netconvert.typ.xml',
|
||||||
|
# Necessary to link odr and sumo ids.
|
||||||
|
'--output.original-names',
|
||||||
|
# Discard loading traffic lights as them will be inserted manually afterwards.
|
||||||
|
'--tls.discard-loaded', 'true'
|
||||||
|
])
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
raise RuntimeError('There was an error when executing netconvert.')
|
||||||
|
else:
|
||||||
|
if result != 0:
|
||||||
|
raise RuntimeError('There was an error when executing netconvert.')
|
||||||
|
|
||||||
|
# --------
|
||||||
|
# Sumo net
|
||||||
|
# --------
|
||||||
|
sumo_net = sumolib.net.readNet(tmp_sumo_net)
|
||||||
|
sumo_topology = build_topology(sumo_net)
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Carla map
|
||||||
|
# ---------
|
||||||
|
with open(args.xodr_file, 'r') as f:
|
||||||
|
carla_map = carla.Map('netconvert', str(f.read()))
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Landmarks
|
||||||
|
# ---------
|
||||||
|
tls = {} # {tlsid: SumoTrafficLight}
|
||||||
|
|
||||||
|
landmarks = carla_map.get_all_landmarks_of_type('1000001')
|
||||||
|
for landmark in landmarks:
|
||||||
|
if landmark.name == '':
|
||||||
|
# This is a workaround to avoid adding traffic lights without controllers.
|
||||||
|
logging.warning('Landmark %s has not a valid name.', landmark.name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
road_id = str(landmark.road_id)
|
||||||
|
for from_lane, to_lane in landmark.get_lane_validities():
|
||||||
|
for lane_id in range(from_lane, to_lane + 1):
|
||||||
|
if lane_id == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
wp = carla_map.get_waypoint_xodr(landmark.road_id, lane_id, landmark.s)
|
||||||
|
if wp is None:
|
||||||
|
logging.warning(
|
||||||
|
'Could not find waypoint for landmark {} (road_id: {}, lane_id: {}, s:{}'
|
||||||
|
.format(landmark.id, landmark.road_id, lane_id, landmark.s))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# When the landmark belongs to a junction, we place te traffic light at the
|
||||||
|
# entrance of the junction.
|
||||||
|
if wp.is_junction and sumo_topology.is_junction(road_id, lane_id):
|
||||||
|
tlid = str(wp.get_junction().id)
|
||||||
|
if tlid not in tls:
|
||||||
|
tls[tlid] = SumoTrafficLight(tlid)
|
||||||
|
tl = tls[tlid]
|
||||||
|
|
||||||
|
if args.guess_tls:
|
||||||
|
for from_edge, from_lane in sumo_topology.get_incoming(road_id, lane_id):
|
||||||
|
successors = sumo_topology.get_successors(from_edge, from_lane)
|
||||||
|
for to_edge, to_lane in successors:
|
||||||
|
tl.add_landmark(landmark.id, tl.id, from_edge, to_edge, from_lane,
|
||||||
|
to_lane)
|
||||||
|
|
||||||
|
else:
|
||||||
|
connections = sumo_topology.get_path_connectivity(road_id, lane_id)
|
||||||
|
for from_, to_ in connections:
|
||||||
|
from_edge, from_lane = from_
|
||||||
|
to_edge, to_lane = to_
|
||||||
|
|
||||||
|
tl.add_landmark(landmark.id, tl.id, from_edge, to_edge, from_lane,
|
||||||
|
to_lane)
|
||||||
|
|
||||||
|
# When the landmarks does not belong to a junction (i.e., belongs to a std road),
|
||||||
|
# we place the traffic light between that std road and its successor.
|
||||||
|
elif not wp.is_junction and not sumo_topology.is_junction(road_id, lane_id):
|
||||||
|
from_edge, from_lane = sumo_topology.get_sumo_id(road_id, lane_id, landmark.s)
|
||||||
|
|
||||||
|
for to_edge, to_lane in sumo_topology.get_successors(from_edge, from_lane):
|
||||||
|
tlid = SumoTrafficLight.generate_tl_id(from_edge, to_edge)
|
||||||
|
if tlid not in tls:
|
||||||
|
tls[tlid] = SumoTrafficLight(tlid)
|
||||||
|
tl = tls[tlid]
|
||||||
|
|
||||||
|
tl.add_landmark(landmark.id, tl.id, from_edge, to_edge, from_lane, to_lane)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.warning('Landmark %s could not be added.', landmark.id)
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# Modify sumo net
|
||||||
|
# ---------------
|
||||||
|
parser = ET.XMLParser(remove_blank_text=True)
|
||||||
|
tree = ET.parse(tmp_sumo_net, parser)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
for tl in tls.values():
|
||||||
|
SumoTrafficLight.generate_default_program(tl)
|
||||||
|
edges_tags = tree.xpath('//edge')
|
||||||
|
if not edges_tags:
|
||||||
|
raise RuntimeError('No edges found in sumo net.')
|
||||||
|
root.insert(root.index(edges_tags[-1]) + 1, tl.to_xml())
|
||||||
|
|
||||||
|
for connection in tl.connections:
|
||||||
|
tags = tree.xpath(
|
||||||
|
'//connection[@from="{}" and @to="{}" and @fromLane="{}" and @toLane="{}"]'.format(
|
||||||
|
connection.from_road, connection.to_road, connection.from_lane,
|
||||||
|
connection.to_lane))
|
||||||
|
|
||||||
|
if tags:
|
||||||
|
if len(tags) > 1:
|
||||||
|
logging.warning(
|
||||||
|
'Found repeated connections from={} to={} fromLane={} toLane={}.'.format(
|
||||||
|
connection.from_road, connection.to_road, connection.from_lane,
|
||||||
|
connection.to_lane))
|
||||||
|
|
||||||
|
tags[0].set('tl', str(connection.tlid))
|
||||||
|
tags[0].set('linkIndex', str(connection.link_index))
|
||||||
|
else:
|
||||||
|
logging.warning('Not found connection from={} to={} fromLane={} toLane={}.'.format(
|
||||||
|
connection.from_road, connection.to_road, connection.from_lane,
|
||||||
|
connection.to_lane))
|
||||||
|
|
||||||
|
tree.write(args.output, pretty_print=True, encoding='UTF-8', xml_declaration=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
argparser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
argparser.add_argument('xodr_file', help='open drive file (*.xodr')
|
||||||
|
argparser.add_argument('--output', '-o', type=str, help='output file (*.net.xml)')
|
||||||
|
argparser.add_argument('--guess-tls',
|
||||||
|
action='store_true',
|
||||||
|
help='guess traffic lights at intersections (default: False)')
|
||||||
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
tmp_path = 'tmp-{date:%Y-%m-%d_%H-%M-%S-%f}'.format(date=datetime.datetime.now())
|
||||||
|
if not os.path.exists(tmp_path):
|
||||||
|
os.mkdir(tmp_path)
|
||||||
|
|
||||||
|
netconvert_carla(args, tmp_path)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if os.path.exists(tmp_path):
|
||||||
|
shutil.rmtree(tmp_path)
|
Loading…
Reference in New Issue