From c7238568103520dfd08aa1fd7af12ab640f150bf Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 09:52:46 +0100 Subject: [PATCH 01/12] Add SUMO groundtruth reader class --- stonesoup/reader/sumo.py | 289 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 stonesoup/reader/sumo.py diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py new file mode 100644 index 000000000..588c0ea48 --- /dev/null +++ b/stonesoup/reader/sumo.py @@ -0,0 +1,289 @@ +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): + """A groundtruth reader for SUMO simulations. + + Requires the installation of SUMO https://www.eclipse.org/sumo/. + 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], + doc='Mapping for x, y 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') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + 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) + + 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.]*4) + np.put(state_vector, self.position_mapping, traci.person.getPosition(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: + longitude, latitude = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) + metadata['longitude'] = longitude + metadata['latitude'] = latitude + + 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.]*4) + np.put(state_vector, self.position_mapping, traci.vehicle.getPosition(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: + longitude, latitude = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) + metadata['longitude'] = longitude + metadata['latitude'] = latitude + + 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): + 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): + 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 From 293428f9d77c042e510d1e249fb031edaf44fd87 Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 11:10:57 +0100 Subject: [PATCH 02/12] Improve documentation for the Enum's used in the reader --- stonesoup/reader/sumo.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index 588c0ea48..66d960055 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -13,10 +13,15 @@ class SUMOGroundTruthReader(GroundTruthReader): - """A groundtruth reader for SUMO simulations. + """A Groundtruth reader for a SUMO simulation. + + This reader requires the installation of SUMO, see: https://www.eclipse.org/sumo/ + This reader also requires a SUMO configuration file. + + 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. - Requires the installation of SUMO https://www.eclipse.org/sumo/. - Requires a SUMO configuration file Parameters ---------- """ @@ -159,6 +164,10 @@ def groundtruth_paths_gen(self): # Initialise and insert StateVector information state_vector = StateVector([0.]*4) + print(id_) + print(f"3d position: {traci.vehicle.getPosition3D(id_)}") + print(f"2d position: {traci.vehicle.getPosition(id_)}") + np.put(state_vector, self.position_mapping, traci.vehicle.getPosition(id_)) np.put(state_vector, self.velocity_mapping, self.calculate_velocity(traci.vehicle.getSpeed(id_), traci.vehicle.getAngle(id_), @@ -193,6 +202,12 @@ def groundtruth_paths_gen(self): 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 @@ -215,6 +230,12 @@ class PersonMetadataEnum(Enum): 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 From 162b4c8a25a2045da4c3544f8b0a058214c99c4a Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 12:17:19 +0100 Subject: [PATCH 03/12] Add SUMO reader to stonesoup.reader.rst --- docs/source/stonesoup.reader.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/stonesoup.reader.rst b/docs/source/stonesoup.reader.rst index c5d8c91ab..da378eef4 100644 --- a/docs/source/stonesoup.reader.rst +++ b/docs/source/stonesoup.reader.rst @@ -50,3 +50,7 @@ Astronomical .. automodule:: stonesoup.reader.astronomical :show-inheritance: +SUMO +---- +.. automodule:: stonesoup.reader.sumo + :show-inheritance: \ No newline at end of file From 685c6628282a6ab7cd64f308b5409e36931437d8 Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 12:17:45 +0100 Subject: [PATCH 04/12] Improve documentation fro SUMOGroundTruthReader --- stonesoup/reader/sumo.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index 66d960055..d78cc9f41 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -13,15 +13,23 @@ class SUMOGroundTruthReader(GroundTruthReader): - """A Groundtruth reader for a SUMO simulation. - - This reader requires the installation of SUMO, see: https://www.eclipse.org/sumo/ - This reader also requires a SUMO configuration file. + 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:: + + This reader requires the installation of SUMO, see: https://www.eclipse.org/sumo/ + This reader also requires a SUMO configuration file. + Parameters ---------- """ @@ -41,8 +49,8 @@ class SUMOGroundTruthReader(GroundTruthReader): 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], - doc='Mapping for x, y position in state vector') + default=[0, 2, 4], + doc='Mapping for x, y, z position in state vector') velocity_mapping: Sequence[int] = Property( default=[1, 3], @@ -129,8 +137,8 @@ def groundtruth_paths_gen(self): data.name in self.person_metadata_fields)) # Initialise and insert StateVector information - state_vector = StateVector([0.]*4) - np.put(state_vector, self.position_mapping, traci.person.getPosition(id_)) + 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)) @@ -163,12 +171,8 @@ def groundtruth_paths_gen(self): data.name in self.vehicle_metadata_fields)) # Initialise and insert StateVector information - state_vector = StateVector([0.]*4) - print(id_) - print(f"3d position: {traci.vehicle.getPosition3D(id_)}") - print(f"2d position: {traci.vehicle.getPosition(id_)}") - - np.put(state_vector, self.position_mapping, traci.vehicle.getPosition(id_)) + 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)) From cdfb72a83b64167c38894f212f3869c9d515d8b6 Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 12:22:35 +0100 Subject: [PATCH 05/12] Fix documentation by adding spaces at the end of lines when relevant. --- stonesoup/reader/sumo.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index d78cc9f41..2edc58ec5 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -37,7 +37,7 @@ class SUMOGroundTruthReader(GroundTruthReader): 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' + 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( @@ -58,16 +58,16 @@ class SUMOGroundTruthReader(GroundTruthReader): 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.' + 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.' + 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( From f5188e73f32f08a46e7d9acab7c295a0ef147eea Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 12:36:01 +0100 Subject: [PATCH 06/12] Fix typos in documentation for GroundTruthState and GroundTruthPath --- stonesoup/reader/sumo.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index 2edc58ec5..d9e22a7e3 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -16,7 +16,7 @@ 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 + :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: @@ -28,7 +28,8 @@ class SUMOGroundTruthReader(GroundTruthReader): .. note:: This reader requires the installation of SUMO, see: https://www.eclipse.org/sumo/ - This reader also requires a SUMO configuration file. + + This reader requires a SUMO configuration file. Parameters ---------- From ceb32f69e31fb7c0c217e51474194db59e37a191 Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 13:34:51 +0100 Subject: [PATCH 07/12] Update link to documentation on how to install SUMO --- docs/source/stonesoup.reader.rst | 2 +- stonesoup/reader/sumo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/stonesoup.reader.rst b/docs/source/stonesoup.reader.rst index da378eef4..27a76250f 100644 --- a/docs/source/stonesoup.reader.rst +++ b/docs/source/stonesoup.reader.rst @@ -53,4 +53,4 @@ Astronomical SUMO ---- .. automodule:: stonesoup.reader.sumo - :show-inheritance: \ No newline at end of file + :show-inheritance: diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index d9e22a7e3..0d52b350f 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -27,7 +27,7 @@ class SUMOGroundTruthReader(GroundTruthReader): .. note:: - This reader requires the installation of SUMO, see: https://www.eclipse.org/sumo/ + This reader requires the installation of SUMO, see: https://sumo.dlr.de/docs/Installing/index.html This reader requires a SUMO configuration file. From 82d48b41fec47d676e66c856d2fbdc9a143e0430 Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Tue, 11 Jul 2023 16:52:29 +0100 Subject: [PATCH 08/12] Change formatting to comply with flake8 --- stonesoup/reader/sumo.py | 78 ++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index 0d52b350f..5f3e41ade 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -15,9 +15,9 @@ 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. + 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: @@ -27,7 +27,8 @@ class SUMOGroundTruthReader(GroundTruthReader): .. note:: - This reader requires the installation of SUMO, see: https://sumo.dlr.de/docs/Installing/index.html + This reader requires the installation of SUMO, see: + https://sumo.dlr.de/docs/Installing/index.html This reader requires a SUMO configuration file. @@ -38,8 +39,9 @@ class SUMOGroundTruthReader(GroundTruthReader): 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.') + 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, @@ -47,7 +49,8 @@ class SUMOGroundTruthReader(GroundTruthReader): 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') + 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], @@ -59,21 +62,24 @@ class SUMOGroundTruthReader(GroundTruthReader): 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. ' + 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. ' + 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') + doc='If True, geographic co-ordinates (longitude, latitude) will be added to the metadata ' + ' of each state') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -120,8 +126,8 @@ def groundtruth_paths_gen(self): 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). + # 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() @@ -140,19 +146,21 @@ def groundtruth_paths_gen(self): # 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)) + 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()} + metadata = {PersonMetadataEnum(key).name: subscription[key] + for key in subscription.keys()} # Add latitude / longitude to metadata if self.geographic_coordinates: - longitude, latitude = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) - metadata['longitude'] = longitude - metadata['latitude'] = latitude + long, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) + metadata['longitude'] = long + metadata['latitude'] = lat state = GroundTruthState( state_vector=state_vector, @@ -174,19 +182,21 @@ def groundtruth_paths_gen(self): # 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)) + 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()} + metadata = {VehicleMetadataEnum(key).name: subscription[key] + for key in subscription.keys()} # Add latitude / longitude to metadata if self.geographic_coordinates: - longitude, latitude = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) - metadata['longitude'] = longitude - metadata['latitude'] = latitude + long, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) + metadata['longitude'] = long + metadata['latitude'] = lat state = GroundTruthState( state_vector=state_vector, @@ -208,8 +218,8 @@ def groundtruth_paths_gen(self): 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. + 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. """ @@ -236,8 +246,8 @@ class PersonMetadataEnum(Enum): 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. + 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. """ From 60d1cb47989a73d2a2aca3d16326c19a4d3420e3 Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Mon, 31 Jul 2023 15:24:17 +0100 Subject: [PATCH 09/12] Add lat/long of origin to metadata of each GT state if geographic_coordinates==True. Requires network to be geo-referenced. --- stonesoup/reader/sumo.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index 5f3e41ade..f348b4cc0 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -27,6 +27,11 @@ class SUMOGroundTruthReader(GroundTruthReader): .. 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/long 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 @@ -79,10 +84,13 @@ class SUMOGroundTruthReader(GroundTruthReader): geographic_coordinates: bool = Property( default=False, doc='If True, geographic co-ordinates (longitude, latitude) will be added to the metadata ' - ' of each state') + ' of each state, as well as the lat/long of the origin of hte 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/long + # 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: @@ -123,6 +131,8 @@ def groundtruth_paths_gen(self): sys.exit("Declare environment variable 'SUMO_HOME'") traci.start(self.sumoCmd) + if self.geographic_coordinates: + self.origin = traci.simulation.convertGeo(0, 0) # long, lat groundtruth_dict = dict() while self.step < self.sim_steps: @@ -155,12 +165,15 @@ def groundtruth_paths_gen(self): 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: long, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) metadata['longitude'] = long metadata['latitude'] = lat + + # Add co-ordinates of origin to metadata + if self.origin: + metadata['origin'] = self.origin state = GroundTruthState( state_vector=state_vector, @@ -198,6 +211,10 @@ def groundtruth_paths_gen(self): metadata['longitude'] = long 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, From d097d17536a6a1fca87a3b756aebfaf1013f489c Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Wed, 9 Aug 2023 16:13:56 +0100 Subject: [PATCH 10/12] Fix typos in documentation for geographic_coordinates attribute. --- stonesoup/reader/sumo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index f348b4cc0..bdc2c8ab8 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -84,7 +84,7 @@ class SUMOGroundTruthReader(GroundTruthReader): 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/long of the origin of hte local co-ordinate frame') + ' of each state, as well as the lat/long of the origin of the local co-ordinate frame') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From 11d05512fe07eaac05b955778dbfbeff4652cc8e Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Wed, 9 Aug 2023 16:35:03 +0100 Subject: [PATCH 11/12] Change structure of code to comply with Flake8 line length limits. --- stonesoup/reader/sumo.py | 50 +++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index bdc2c8ab8..67e40ce42 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -27,10 +27,11 @@ class SUMOGroundTruthReader(GroundTruthReader): .. 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 + 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/long co-ordinates, it is required that the network is geo-referenced. + 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 @@ -44,9 +45,9 @@ class SUMOGroundTruthReader(GroundTruthReader): 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.') + 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, @@ -54,8 +55,8 @@ class SUMOGroundTruthReader(GroundTruthReader): 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') + 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], @@ -75,8 +76,8 @@ class SUMOGroundTruthReader(GroundTruthReader): 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 ' + 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"]') @@ -84,12 +85,12 @@ class SUMOGroundTruthReader(GroundTruthReader): 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/long of the origin of the local co-ordinate frame') + '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/long - # position of the origin of the local Cartesian frame. + # 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 @@ -132,7 +133,7 @@ def groundtruth_paths_gen(self): traci.start(self.sumoCmd) if self.geographic_coordinates: - self.origin = traci.simulation.convertGeo(0, 0) # long, lat + self.origin = traci.simulation.convertGeo(0, 0) # lon, lat groundtruth_dict = dict() while self.step < self.sim_steps: @@ -167,8 +168,8 @@ def groundtruth_paths_gen(self): for key in subscription.keys()} # Add latitude / longitude to metadata if self.geographic_coordinates: - long, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) - metadata['longitude'] = long + lon, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) + metadata['longitude'] = lon metadata['latitude'] = lat # Add co-ordinates of origin to metadata @@ -189,8 +190,9 @@ def groundtruth_paths_gen(self): 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)) + 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) @@ -207,8 +209,8 @@ def groundtruth_paths_gen(self): # Add latitude / longitude to metadata if self.geographic_coordinates: - long, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) - metadata['longitude'] = long + lon, lat = traci.simulation.convertGeo(*state_vector[self.position_mapping, :]) + metadata['longitude'] = lon metadata['latitude'] = lat # Add co-ordinates of origin to metadata @@ -235,8 +237,8 @@ def groundtruth_paths_gen(self): 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. + 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. """ @@ -263,8 +265,8 @@ class PersonMetadataEnum(Enum): 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. + 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. """ From ccf8003bac66cc776910b10263a5fda90b9f4dfe Mon Sep 17 00:00:00 2001 From: Henry Pritchett Date: Mon, 6 Nov 2023 11:52:30 +0000 Subject: [PATCH 12/12] Raise a RunTimeError if environment variable SUMO_HOME is not found --- stonesoup/reader/sumo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stonesoup/reader/sumo.py b/stonesoup/reader/sumo.py index 67e40ce42..2b1e01aa4 100644 --- a/stonesoup/reader/sumo.py +++ b/stonesoup/reader/sumo.py @@ -129,7 +129,7 @@ def groundtruth_paths_gen(self): sys.path.append(tools) import traci else: - sys.exit("Declare environment variable 'SUMO_HOME'") + raise RuntimeError("Environment variable 'SUMO_HOME' is not set") traci.start(self.sumoCmd) if self.geographic_coordinates: