-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SUMO Groundtruth Reader #827
Open
hpritchett-dstl
wants to merge
12
commits into
main
Choose a base branch
from
sumo_gt_reader
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 11 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c723856
Add SUMO groundtruth reader class
hpritchett-dstl 293428f
Improve documentation for the Enum's used in the reader
hpritchett-dstl 162b4c8
Add SUMO reader to stonesoup.reader.rst
hpritchett-dstl 685c662
Improve documentation fro SUMOGroundTruthReader
hpritchett-dstl cdfb72a
Fix documentation by adding spaces at the end of lines when relevant.
hpritchett-dstl f5188e7
Fix typos in documentation for GroundTruthState and GroundTruthPath
hpritchett-dstl ceb32f6
Update link to documentation on how to install SUMO
hpritchett-dstl 82d48b4
Change formatting to comply with flake8
hpritchett-dstl 60d1cb4
Add lat/long of origin to metadata of each GT state if geographic_coo…
hpritchett-dstl d097d17
Fix typos in documentation for geographic_coordinates attribute.
hpritchett-dstl 11d0551
Change structure of code to comply with Flake8 line length limits.
hpritchett-dstl ccf8003
Raise a RunTimeError if environment variable SUMO_HOME is not found
hpritchett-dstl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,344 @@ | ||
import numpy as np | ||
import datetime | ||
import os | ||
import sys | ||
from typing import Collection, Sequence | ||
from enum import Enum | ||
|
||
from .base import GroundTruthReader | ||
from ..base import Property | ||
from ..types.array import StateVector | ||
from ..types.groundtruth import GroundTruthState, GroundTruthPath | ||
from ..buffered_generator import BufferedGenerator | ||
|
||
|
||
class SUMOGroundTruthReader(GroundTruthReader): | ||
r"""A Groundtruth reader for a SUMO simulation. | ||
|
||
At each time step, kinematic information from the objects in the SUMO simulation will be | ||
extracted and placed into a :class:`~.GroundTruthState`. States with the same ID will be placed | ||
into a :class:`~.GroundTruthPath` in sequence. | ||
|
||
The state vector for each truth object is, by default, of the form: | ||
|
||
.. math:: | ||
|
||
[\mathbf{x}, \mathbf{v}_x, \mathbf{y}, \mathbf{v}_y, \mathbf{z}] | ||
|
||
.. note:: | ||
|
||
By default, the Carteisan co-ordinates use the UTM-projection with the origin shifted such | ||
that the bottom left corner of the network is the origin (0,0). | ||
See: https://sumo.dlr.de/docs/Geo-Coordinates.html | ||
|
||
To extract lat/lon co-ordinates, it is required that the network is geo-referenced. | ||
|
||
This reader requires the installation of SUMO, see: | ||
https://sumo.dlr.de/docs/Installing/index.html | ||
|
||
This reader requires a SUMO configuration file. | ||
|
||
Parameters | ||
---------- | ||
""" | ||
sumo_cfg_file_path: str = Property( | ||
doc='Path to SUMO config file') | ||
|
||
sumo_server_path: str = Property( | ||
doc='Path to SUMO server, relative from SUMO_HOME environment variable. ' | ||
'"/bin/sumo" to run on command line "/bin/sumo-gui" will run using the SUMO-GUI, ' | ||
'this will require pressing play within the GUI.') | ||
|
||
sim_start: datetime.datetime = Property( | ||
default=None, | ||
doc='Start time for the simulation. Will default to datetime.datetime.now()') | ||
|
||
sim_steps: int = Property( | ||
default=200, | ||
doc='Number of steps you want your SUMO simulation to run for. Use numpy.inf to have no ' | ||
'limit') | ||
|
||
position_mapping: Sequence[int] = Property( | ||
default=[0, 2, 4], | ||
doc='Mapping for x, y, z position in state vector') | ||
|
||
velocity_mapping: Sequence[int] = Property( | ||
default=[1, 3], | ||
doc='Mapping for x and y velocities in state vector') | ||
|
||
person_metadata_fields: Collection[str] = Property( | ||
default=None, | ||
doc='Collection of metadata fields for people that will be added to the metadata of ' | ||
'each GroundTruthState. Possible fields are documented in ' | ||
'https://sumo.dlr.de/docs/TraCI/Person_Value_Retrieval.html. See also ' | ||
'PersonMetadataEnum. Underscores are required in place of spaces. ' | ||
'An example would be: ["speed", "color", "slope", "road_id]') | ||
|
||
vehicle_metadata_fields: Collection[str] = Property( | ||
default=None, | ||
doc='Collection of metadata fields for vehicles that will be added to the metadata' | ||
' of each GroundTruthState. Possible fields are documented in ' | ||
'https://sumo.dlr.de/docs/TraCI/Vehicle_Value_Retrieval.html. See also ' | ||
'VehicleMetadataEnum. Underscores are required in place of spaces. ' | ||
'An example would be: ["speed", "acceleration", "lane_position"]') | ||
|
||
geographic_coordinates: bool = Property( | ||
default=False, | ||
doc='If True, geographic co-ordinates (longitude, latitude) will be added to the metadata ' | ||
'of each state, as well as the lat/lon of the origin of the local co-ordinate frame') | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
# If network is geo-referenced, and geographic_coordinates == True, | ||
# then self.origin will be lat/lon position of the origin of the local Cartesian frame. | ||
self.origin = None | ||
self.step = 0 | ||
# Resort to default for sim_start | ||
if self.sim_start is None: | ||
self.sim_start = datetime.datetime.now() | ||
|
||
self.sumoCmd = [self.sumo_server_path, "-c", self.sumo_cfg_file_path] | ||
|
||
# Raise errors if metadata fields are incorrect | ||
if set(self.person_metadata_fields) & {data.name for data in PersonMetadataEnum} != set( | ||
self.person_metadata_fields): | ||
raise ValueError(f"""Invalid person metadata field(s): {', '.join(str(field) for | ||
field in self.person_metadata_fields if field not in | ||
[data.name for data in PersonMetadataEnum])}""") | ||
|
||
if set(self.vehicle_metadata_fields) & {data.name for data in VehicleMetadataEnum} != set( | ||
self.vehicle_metadata_fields): | ||
raise ValueError(f"""Invalid vehicle metadata field(s): {', '.join(str(field) for | ||
field in self.vehicle_metadata_fields if field not in | ||
[data.name for data in VehicleMetadataEnum])}""") | ||
|
||
@staticmethod | ||
def calculate_velocity(speed, angle, radians=False): | ||
if not radians: | ||
v_x, v_y = speed * np.sin(np.radians(angle)), speed * np.cos(np.radians(angle)) | ||
elif radians: | ||
v_x, v_y = speed * np.sin(angle), speed * np.cos(angle) | ||
return v_x, v_y | ||
|
||
@BufferedGenerator.generator_method | ||
def groundtruth_paths_gen(self): | ||
|
||
# Add SUMO_HOME env variable. Use this for server/config-file paths | ||
if 'SUMO_HOME' in os.environ: | ||
tools = os.path.join(os.environ['SUMO_HOME'], 'tools') | ||
sys.path.append(tools) | ||
import traci | ||
else: | ||
sys.exit("Declare environment variable 'SUMO_HOME'") | ||
|
||
traci.start(self.sumoCmd) | ||
if self.geographic_coordinates: | ||
self.origin = traci.simulation.convertGeo(0, 0) # lon, lat | ||
|
||
groundtruth_dict = dict() | ||
while self.step < self.sim_steps: | ||
# Need to get id list at each timestamp since not all ids may be present through the | ||
# whole of the simulation (spawning, exiting etc). | ||
vehicle_ids = traci.vehicle.getIDList() | ||
person_ids = traci.person.getIDList() | ||
|
||
# Update simulation time | ||
time = self.sim_start + datetime.timedelta(seconds=traci.simulation.getTime()) | ||
updated_paths = set() | ||
|
||
# Loop through people | ||
for id_ in person_ids: | ||
if id_ not in groundtruth_dict.keys(): | ||
groundtruth_dict[id_] = GroundTruthPath(id=id_) | ||
# Subscribe to all specified metadata fields | ||
traci.person.subscribe(id_, tuple(data.value for data in PersonMetadataEnum if | ||
data.name in self.person_metadata_fields)) | ||
|
||
# Initialise and insert StateVector information | ||
state_vector = StateVector([0.]*5) | ||
np.put(state_vector, self.position_mapping, traci.person.getPosition3D(id_)) | ||
np.put(state_vector, self.velocity_mapping, | ||
self.calculate_velocity(traci.person.getSpeed(id_), | ||
traci.person.getAngle(id_), | ||
radians=False)) | ||
|
||
# Get information that is subscribed to for metadata | ||
subscription = traci.person.getSubscriptionResults(id_) | ||
metadata = {PersonMetadataEnum(key).name: subscription[key] | ||
for key in subscription.keys()} | ||
# Add latitude / longitude to metadata | ||
if self.geographic_coordinates: | ||
lon, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) | ||
metadata['longitude'] = lon | ||
metadata['latitude'] = lat | ||
|
||
# Add co-ordinates of origin to metadata | ||
if self.origin: | ||
metadata['origin'] = self.origin | ||
|
||
state = GroundTruthState( | ||
state_vector=state_vector, | ||
timestamp=time, | ||
metadata=metadata) | ||
|
||
groundtruth_path = groundtruth_dict[id_] | ||
groundtruth_path.append(state) | ||
updated_paths.add(groundtruth_path) | ||
|
||
# Loop through vehicles | ||
for id_ in vehicle_ids: | ||
if id_ not in groundtruth_dict.keys(): | ||
groundtruth_dict[id_] = GroundTruthPath(id=id_) | ||
# Subscribe to all specified metadata fields | ||
traci.vehicle.subscribe(id_, | ||
tuple(data.value for data in VehicleMetadataEnum if | ||
data.name in self.vehicle_metadata_fields)) | ||
|
||
# Initialise and insert StateVector information | ||
state_vector = StateVector([0.]*5) | ||
np.put(state_vector, self.position_mapping, traci.vehicle.getPosition3D(id_)) | ||
np.put(state_vector, self.velocity_mapping, | ||
self.calculate_velocity(traci.vehicle.getSpeed(id_), | ||
traci.vehicle.getAngle(id_), | ||
radians=False)) | ||
|
||
# Get information that is subscribed to for metadata | ||
subscription = traci.vehicle.getSubscriptionResults(id_) | ||
metadata = {VehicleMetadataEnum(key).name: subscription[key] | ||
for key in subscription.keys()} | ||
|
||
# Add latitude / longitude to metadata | ||
if self.geographic_coordinates: | ||
lon, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) | ||
metadata['longitude'] = lon | ||
metadata['latitude'] = lat | ||
|
||
# Add co-ordinates of origin to metadata | ||
if self.origin: | ||
metadata['origin'] = self.origin | ||
|
||
state = GroundTruthState( | ||
state_vector=state_vector, | ||
timestamp=time, | ||
metadata=metadata) | ||
|
||
groundtruth_path = groundtruth_dict[id_] | ||
groundtruth_path.append(state) | ||
updated_paths.add(groundtruth_path) | ||
|
||
# Progress simulation | ||
traci.simulationStep() | ||
|
||
self.step += 1 | ||
yield time, updated_paths | ||
|
||
traci.close() | ||
|
||
|
||
class PersonMetadataEnum(Enum): | ||
""" | ||
A metadata Enum used to map the named variable of the person to the relevant id. Subscribing | ||
to this id will retrieve the value and add it to the metadata of the GroundTruthState. | ||
See https://sumo.dlr.de/docs/TraCI/Person_Value_Retrieval.html for a full list. | ||
|
||
""" | ||
id_list = 0x0 | ||
count = 0x1 | ||
speed = 0x40 | ||
position = 0x42 | ||
position_3D = 0x39 | ||
angle = 0x43 | ||
slope = 0x36 | ||
road_id = 0x50 | ||
type_id = 0x4f | ||
color = 0x45 | ||
edge_position = 0x56 | ||
length = 0x44 | ||
minGap = 0x4c | ||
width = 0x4d | ||
waiting_time = 0x7a | ||
next_edge = 0xc1 | ||
remaining_stages = 0xc2 | ||
vehicle = 0xc3 | ||
taxi_reservations = 0xc6 | ||
|
||
|
||
class VehicleMetadataEnum(Enum): | ||
""" | ||
A metadata Enum used to map the named variable of the vehicle to the relevant id. Subscribing | ||
to this id will retrieve the value and add it to the metadata of the GroundTruthState. | ||
See https://sumo.dlr.de/docs/TraCI/Vehicle_Value_Retrieval.html for a full list. | ||
|
||
""" | ||
id_list = 0x0 | ||
count = 0x1 | ||
speed = 0x40 | ||
lateral_speed = 0x32 | ||
acceleration = 0x72 | ||
position = 0x42 | ||
position_3D = 0x39 | ||
angle = 0x43 | ||
road_id = 0x50 | ||
lane_id = 0x51 | ||
lane_index = 0x52 | ||
type_id = 0x4f | ||
route_id = 0x53 | ||
route_index = 0x69 | ||
edges = 0x54 | ||
color = 0x45 | ||
lane_position = 0x56 | ||
distance = 0x84 | ||
signal_states = 0x5b | ||
routing_mode = 0x89 | ||
TaxiFleet = 0x20 | ||
CO2_emissions = 0x60 | ||
CO_emissions = 0x61 | ||
HC_emissions = 0x62 | ||
PMx_emissions = 0x63 | ||
NOx_emissions = 0x64 | ||
fuel_consumption = 0x65 | ||
noise_emission = 0x66 | ||
electricity_consumption = 0x71 | ||
best_lanes = 0xb2 | ||
stop_state = 0xb5 | ||
length = 0x44 | ||
vmax = 0x41 | ||
accel = 0x46 | ||
decel = 0x47 | ||
tau = 0x48 | ||
sigma = 0x5d | ||
speedFactor = 0x5e | ||
speedDev = 0x5f | ||
vClass = 0x49 | ||
emission_class = 0x4a | ||
shape = 0x4b | ||
minGap = 0x4c | ||
width = 0x4d | ||
height = 0xbc | ||
person_capacity = 0x38 | ||
waiting_time = 0x7a | ||
accumulated_waiting_time = 0x87 | ||
next_TLS = 0x70 | ||
next_stops = 0x73 | ||
person_id_list = 0x1a | ||
speed_mode = 0xb3 | ||
lane_change_mode = 0xb6 | ||
slope = 0x36 | ||
allowed_speed = 0xb7 | ||
line = 0xbd | ||
Person_Number = 0x67 | ||
via_edges = 0xbe | ||
speed_without_TraCI = 0xb1 | ||
valid_route = 0x92 | ||
lateral_lane_position = 0xb8 | ||
max_lateral_speed = 0xba | ||
lateral_gap = 0xbb | ||
lateral_alignment = 0xb9 | ||
parameter = 0x7e | ||
action_step_length = 0x7d | ||
last_action_time = 0x7f | ||
stops = 0x74 | ||
timeLoss = 0x8c | ||
loaded_list = 0x24 | ||
teleporting_list = 0x25 | ||
next_links = 0x33 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should just raise an error, rather than
sys.exit()