diff --git a/app/config.py b/app/config.py index 2444c39..87d03ee 100644 --- a/app/config.py +++ b/app/config.py @@ -2,6 +2,21 @@ class Config: + """ + Config class containing the configuration variables for the application. + + Attributes: + APP_HOST (str): The host of the application. Default is "0.0.0.0". + APP_PORT (int): The port of the application. Default is 5000. + DEBUG (bool): The debug mode of the application. Default is True. + API_KEY (str): The API key for the Google Maps API. Retrieved from the environment variable "MAPS_API_KEY". + GEOCODE_API_URL (str): The URL for the geocode API of Google Maps. + DISTANCE_MATRIX_API_URL (str): The URL for the distance matrix API of Google Maps. + BABEL_DEFAULT_TIMEZONE (str): The default timezone for babel translations. Default is "en". + BABEL_TRANSLATION_DIRECTORIES (str): The translation directories for babel translations. Default is "app/translations". + LANGUAGES (dict): The supported languages for translations and their corresponding names. + """ + APP_HOST = "0.0.0.0" APP_PORT = 5000 DEBUG = True diff --git a/app/routes/addresses.py b/app/routes/addresses.py index b4f581a..5ccf163 100644 --- a/app/routes/addresses.py +++ b/app/routes/addresses.py @@ -6,12 +6,18 @@ addresses_blueprint = Blueprint("addresses_blueprint", __name__) -# TODO: Add node +# TODO: CHECK THIS FUNCTION @addresses_blueprint.route("/handle-address", methods=["POST", "GET"]) def register_address(): """ Register an address based on the provided form data. The address can be either a depot or a regular address. It converts the all the addresses to coordinates (if necessary). + + Args: + None + + Returns: + dict: A dictionary containing the success status, a message, the coordinates, and the index of the registered address. """ if request.method == "POST": address = request.form["address"] @@ -53,6 +59,17 @@ def register_address(): def update_problem_when_deleted(index): + """ + Updates the problem data when a node is deleted. + + This function removes the given index from the 'start_nodes' and 'end_nodes' lists, + and removes the corresponding address from the 'addresses' dictionary in the current_app.problem_data. + + Args: + index (int): The index of the node to be deleted. + + Returns: + None""" if index in current_app.problem_data["start_nodes"]: current_app.problem_data["start_nodes"].remove(index) if index in current_app.problem_data["end_nodes"]: @@ -64,7 +81,15 @@ def update_problem_when_deleted(index): def delete_address(): """ Delete an address based on the provided form data. The address is identified by its index. - """ + + Args: + None + + Returns: + dict: A dictionary with the following keys: + - success (bool): Indicates whether the address was deleted successfully. + - message (str): A success message. + - problem_data (obj): The updated problem data.""" if request.method == "POST": index = int(request.form["index"]) update_problem_when_deleted(index) diff --git a/app/routes/loaders.py b/app/routes/loaders.py index 9642bf2..6533098 100644 --- a/app/routes/loaders.py +++ b/app/routes/loaders.py @@ -11,7 +11,12 @@ def load_yaml_problem(): """ Load problem definition data from a YAML file, convert the addresses to coordinates, and store them in the application context. - """ + + Args: + None + + Returns: + None""" coord_inds = [] if request.method == "POST": current_app.problem_data = load_problem_definiton() @@ -49,7 +54,12 @@ def load_yaml_problem(): def load_yaml_solver(): """ Load solver configuration data from a YAML file and store them in the application context. - """ + + Args: + None + + Returns: + None""" if request.method == "POST": file = request.form["file"] current_app.solver_data = load_solver_configuration(file) diff --git a/app/routes/routes.py b/app/routes/routes.py index bb2ea92..2ddcf27 100644 --- a/app/routes/routes.py +++ b/app/routes/routes.py @@ -18,6 +18,9 @@ def generate_route(): """ Generate a route based on the provided form data and the problem and solver data stored in the application context. This includes generating a distance matrix if it doesn't exist, finding routes using OR-Tools, and generating a solution. + + Returns: + JSON object: A JSON object containing the success status, a message, the generated routes, and the problem data. """ if request.method == "POST": if not current_app.solver_data: diff --git a/app/routes/save.py b/app/routes/save.py index b3491aa..4527d08 100644 --- a/app/routes/save.py +++ b/app/routes/save.py @@ -12,8 +12,10 @@ def save_nodes(file="output/nodes2visit.yaml"): Save the list of addresses (nodes) to visit to a YAML file. Parameters: - file (str, optional): The path of the file to save to. Default is "output/nodes2visit.yaml". - """ + file (str, optional): The path of the file to save to. Default is "output/nodes2visit.yaml". + + Returns: + type: Description of the return param.""" if request.method == "POST": ensure_folder_exist("/".join(file.split("/")[:-1])) data = {"addresses": current_app.problem_data["addresses"]} @@ -27,8 +29,10 @@ def save_routes(file="output/routes.yaml"): Save the generated routes to a YAML file. Parameters: - file (str, optional): The path of the file to save to. Default is "output/routes.yaml". - """ + file (str, optional): The path of the file to save to. Default is "output/routes.yaml". + + Returns: + None""" if request.method == "POST": ensure_folder_exist("/".join(file.split("/")[:-1])) data = {"routes": current_app.routes} diff --git a/app/routes/simulation.py b/app/routes/simulation.py index cec6918..8c7a962 100644 --- a/app/routes/simulation.py +++ b/app/routes/simulation.py @@ -11,7 +11,9 @@ def start_simulation(): """ Start a simulation of the vehicle routing problem. - """ + + Returns: + Response: A response object containing the simulated data.""" def stream(simulation, simulation_path): while simulation: @@ -39,8 +41,7 @@ def stream(simulation, simulation_path): @simulation_blueprint.route("/stop-simulation", methods=["POST"]) def stop_simulation(): """ - Activate the flag to end the ongoing simulation of the vehicles. - """ + Activate the flag to end the ongoing simulation of the vehicles.""" if request.method == "POST": current_app.simulation = False return jsonify(success=True, message=gettext("Ending simulation...")) diff --git a/main.py b/main.py index b92edac..60cfd79 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,13 @@ # To get the best matching language def get_locale(): + """ + Get the locale that best matches the user's preferred language. + + This function uses the `accept_languages` attribute from the `request` object to determine the best matching locale based on the user's preferred language. It compares the user's preferred language against the available languages defined in the `LANGUAGES` configuration dictionary. + + Returns: + str: The locale that best matches the user's preferred language.""" return request.accept_languages.best_match(app.config["LANGUAGES"].keys()) @@ -44,11 +51,31 @@ def get_locale(): @app.route("/images/") def custom_image(filename): + """ + This function retrieves and returns a custom image file from the "app/static/images" directory. + + Args: + filename (str): The name of the image file to be retrieved. + + Returns: + str: The retrieved custom image file. + + Example: + >>> custom_image("image.jpg")""" return send_from_directory("app/static/images", filename) @app.route("/reset", methods=["POST"]) def reset(): + """ + Resets the problem definition, solver definiton, and routes. + + This function resets the problem definition, solver definition, and routes by clearing the respective data structures. + It sets the 'addresses', 'start_nodes', 'end_nodes' to empty lists, and 'n_vehicles' to 1. + It also sets the 'simulation' flag to False. + + Returns: + jsonify: A JSON response indicating the success of the reset operation.""" current_app.problem_data = {} # Reset problem definition current_app.solver_data = {} # Reset solver definiton current_app.routes = {} # Reset the routes @@ -62,7 +89,19 @@ def reset(): @app.route("/", methods=["POST", "GET"]) def main(): - # Initialize variables global to the app context + """ + Initialize the main function of the app. + + This function initializes variables global to the app context, sets the map center coordinates, + sets up an AddressFormatConversion object, and calls the reset function to initialize variables. + When a POST request is made, the function redirects to the root URL. If an error occurs, it returns an error message. + Otherwise, it renders the index.html template with the map center coordinates and the API key. + + Args: + None + + Returns: + None""" current_app.map_center = [ [37.38602323347123, -5.99311606343879] ] # Center of the map diff --git a/utils/auxiliary.py b/utils/auxiliary.py index 54c3e49..6de6b0c 100644 --- a/utils/auxiliary.py +++ b/utils/auxiliary.py @@ -10,6 +10,15 @@ # Decorators def measure_execution_time(func): + """ + Decorator function that measures the execution time of a given function. + + Args: + func (callable): The function to be wrapped and measured. + + Returns: + callable: The wrapper function that measures the execution time.""" + def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) @@ -22,6 +31,17 @@ def wrapper(*args, **kwargs): def string2value(input_str): + """ + Converts a string to a value. If the string contains only one value, the function returns that value as an integer. + If the string contains multiple values separated by commas, the function returns a list of those values as integers. + + Args: + input_str (str): The input string to be converted. + + Returns: + int or list(int): The converted value(s) as integers. If the string contains only one value, it is returned as an integer. + If the string contains multiple values, they are returned as a list of integers. + """ parts = input_str.split(",") parts = [p.strip() for p in parts if p.strip() != ""] if len(parts) == 1: @@ -34,28 +54,52 @@ def string2value(input_str): def compress_frame(frame, codec="jpg"): """ - Frame compression usin cv2.imencode - """ + Frame compression using cv2.imencode + + Args: + frame (numpy array): The input frame to be compressed. + codec (str, optional): The compression codec to be used (default is "jpg"). + + Returns: + numpy array: The compressed frame.""" _, compressed_frame = cv2.imencode("." + codec, frame) return compressed_frame def decompress_frame(frame): """ - Decompress frame using a chosen codec - """ + Decompress frame using a chosen codec. + + Args: + frame (np.ndarray): The frame to be decompressed. + + Returns: + np.ndarray: The decompressed frame.""" decompressed_frame = cv2.imdecode(frame, cv2.IMREAD_UNCHANGED) return decompressed_frame def buffer2base64(buffer): + """ + Encodes a binary buffer as a base64 string. + + Args: + buffer (bytes): The binary buffer to be encoded. + + Returns: + str: The base64 encoded string.""" return base64.b64encode(buffer).decode("utf-8") def base642image(base64_string): """ The base64_to_image function accepts a base64 encoded string and returns an image - """ + + Args: + base64_string (str): The base64 encoded string representing an image. + + Returns: + numpy.ndarray: The decoded image in the form of a NumPy array.""" image_bytes = base64.b64decode(base64_string) image_array = np.frombuffer(image_bytes, dtype=np.uint8) image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) @@ -70,8 +114,7 @@ def load_yaml(file: str = "data.yaml") -> Dict: file (str): Path to the YAML file (default is "data.yaml"). Returns: - Dict: Dictionary containing the contents of the YAML file. - """ + Dict: Dictionary containing the contents of the YAML file.""" assert os.path.exists(file), "File not found in path {}".format(file) assert isinstance(file, str) and file.endswith( ".yaml" @@ -92,12 +135,30 @@ def load_yaml(file: str = "data.yaml") -> Dict: def save_yaml(file, data): """ This function save data in a yaml file - """ + + Args: + file (str): The file name or path where the data will be saved. + data (dict): The data to be saved in the yaml file. + + Returns: + None""" with open(file, "w") as file: yaml.dump(data, file) def ensure_folder_exist(path): + """ + Ensure that a folder exists at the specified path. + + This function takes a path as input and creates any necessary folders in the path so that a folder exists at the final path. + If the folder(s) already exist, the function does not perform any action. + + Args: + path (str): The path for which the folders need to be created. + + Returns: + bool: True if the folder(s) already exist(s), False if the folder(s) needed to be created. + """ path = str(path) separated = path.split(os.path.sep) # To consider absolute paths @@ -118,6 +179,17 @@ def ensure_folder_exist(path): def generate_n_colors(n): + """ + Generates a list of n colors using the HSV color space. + + This function takes an integer n as input and generates a list of n colors in the form of hexadecimal RGB values. + The colors are evenly distributed in hue with maximum saturation and value. + + Args: + n (int): The number of colors to generate. + + Returns: + List[str]: A list of n colors represented as hexadecimal RGB values.""" from colorsys import hsv_to_rgb colors = [] @@ -133,8 +205,16 @@ def generate_n_colors(n): def adapt_solution(data, manager, routing, solution, coordinates_list): """ Generates a dictionary with the routing data - """ - # TODO: SOLVE LAST PATH COMPUTATION! + + Args: + data (dict): A dictionary with data related to the problem. + manager (ortools.routing.RoutingIndexManager): A manager object to retrieve nodes. + routing (ortools.routing.RoutingModel): A routing model. + solution (ortools.routing.RoutingSolution): A solution object. + coordinates_list (list): A list of coordinates. + + Returns: + dict: A dictionary containing the routing data.""" print(f"Objective: {solution.ObjectiveValue()}") max_route_time = 0 routes_dict = {} diff --git a/utils/load_parameters.py b/utils/load_parameters.py index 8f73516..3b503a2 100644 --- a/utils/load_parameters.py +++ b/utils/load_parameters.py @@ -11,7 +11,12 @@ def load_problem_definiton( ): """ Loads the definition for the problem - """ + + Args: + file (str): The path to the YAML file containing the problem definition (default is the default YAML file path). + + Returns: + dict: The loaded problem definition as a dictionary.""" return load_yaml(file) @@ -23,5 +28,10 @@ def load_solver_configuration( ): """ Loads the configuration for the solver - """ + + Args: + file (str, optional): The file path of the configuration (default is the path to the solver_configuration.yaml file) + + Returns: + dict: The loaded configuration as a dictionary""" return load_yaml(file) diff --git a/utils/or_tools_method.py b/utils/or_tools_method.py index 3ffeadd..46b03e5 100644 --- a/utils/or_tools_method.py +++ b/utils/or_tools_method.py @@ -5,7 +5,18 @@ @measure_execution_time def find_routes(problem_data, routing_data): - # Adapt the distance matrix to consider the time [s] instead of the distance + """ + Find routes using a given problem data and routing data. + + This function takes in problem data and routing data and uses them to find routes using the Google OR-Tools library. + + Args: + problem_data (dict): A dictionary containing the problem data, including the distance matrix, number of vehicles, start nodes, end nodes, max flight time, and velocity. + routing_data (dict): A dictionary containing the routing data, including the first solution strategy, local search strategy, solution limit, time limit, log search flag, and LNS time limit. + + Returns: + tuple: A tuple containing the problem data, routing index manager, routing model, and the solution to the routing problem. + """ for i in range(len(problem_data["distance_matrix"])): for j in range(len(problem_data["distance_matrix"][i])): problem_data["distance_matrix"][i][j] = int( diff --git a/utils/problem_definiton.py b/utils/problem_definiton.py index c2da6a4..490d63b 100644 --- a/utils/problem_definiton.py +++ b/utils/problem_definiton.py @@ -8,7 +8,13 @@ def calculate_haversine_distance(c1, c2): """ Calculate distance between 2 coordinates using haversine formula. - """ + + Args: + c1 (tuple): A tuple containing the latitude and longitude of the first coordinate. + c2 (tuple): A tuple containing the latitude and longitude of the second coordinate. + + Returns: + float: The distance between the two coordinates in meters.""" lat1, lon1 = c1 lat2, lon2 = c2 lat1_rad = math.radians(lat1) @@ -31,6 +37,12 @@ def calculate_haversine_distance(c1, c2): def generate_flight_distance_matrix(coordinates): """ Create distance matrix using haversine formula + + Args: + coordinates (list): List of coordinate tuples (latitude, longitude). + + Returns: + list: Distance matrix where each element represents the distance between two coordinates. """ num_coordinates = len(coordinates) distance_matrix = [[0] * num_coordinates for _ in range(num_coordinates)] @@ -54,7 +66,17 @@ def generate_distance_matrix( ): """ Generate a distance matrix of the specified addresses. - """ + + Args: + addresses (list): List of addresses to generate the distance matrix. + API_KEY (str): API key for accessing the Distance Matrix API. + GEOCODE_API_URL (str): URL for the Geocode API. + DISTANCE_MATRIX_API_URL (str): URL for the Distance Matrix API. + mode (str, optional): Mode of transport for the distance request (default is "walking"). + max_elements (int, optional): Maximum number of elements in each request (default is 10). + + Returns: + list: The distance matrix as a list of lists.""" available_modes = ["driving", "walking", "bicycling", "transit", "flight"] assert mode in available_modes, "Distance request mode not available!" @@ -111,6 +133,17 @@ def fill_matrix(i, j, fullmatrix, submatrix): def detect_address_format(address: str): + """ + Detects the format of an address based on its structure. + + This function takes an address as input and determines its format based on the structure of the address. It can detect addresses in three different formats: float-coordinates, str-coordinates, and name. + + Args: + address (str): The address to be analyzed. + + Returns: + str: The format of the address. It can be "float-coordinates", "str-coordinates" or "name". + """ if ( isinstance(address, list) and isinstance(address[0], (float, int)) @@ -129,7 +162,35 @@ def detect_address_format(address: str): class AddressFormatConversion: + """ + Converts the given address from the input format to the corresponding coordinates. + + Args: + API_KEY (str): The API key required to access the geocoding and distance matrix services. + GEOCODE_API_URL (str): The URL of the geocoding API. + DISTANCE_MATRIX_API_URL (str): The URL of the distance matrix API. + + Attributes: + API_KEY (str): The API key required to access the geocoding and distance matrix services. + GEOCODE_API_URL (str): The URL of the geocoding API. + DISTANCE_MATRIX_API_URL (str): The URL of the distance matrix API. + + Methods: + __init__(API_KEY, GEOCODE_API_URL, DISTANCE_MATRIX_API_URL): Initializes the AddressFormatConversion object with the provided API key and API URLs. + address2coords(address): Given an address in str format, return its coordinates. + """ + def __init__(self, API_KEY, GEOCODE_API_URL, DISTANCE_MATRIX_API_URL): + """ + Initializes the API key and API URLs for the class. + + Args: + API_KEY (str): The API key for accessing the APIs. + GEOCODE_API_URL (str): The URL for the geocode API. + DISTANCE_MATRIX_API_URL (str): The URL for the distance matrix API. + + Returns: + None""" self.API_KEY, self.GEOCODE_API_URL, self.DISTANCE_MATRIX_API_URL = ( API_KEY, GEOCODE_API_URL, @@ -139,7 +200,12 @@ def __init__(self, API_KEY, GEOCODE_API_URL, DISTANCE_MATRIX_API_URL): def address2coords(self, address): """ Given an address in str format, return its coordinates - """ + + Args: + address (str): The address to geocode. + + Returns: + list: The coordinates of the address in the format [latitude, longitude].""" address = unidecode(address) jsonResult = urllib.request.urlopen( self.GEOCODE_API_URL @@ -160,13 +226,49 @@ def address2coords(self, address): class DistanceMatrixRequest: """ - Build and send request for the given origin and destination addresses. - First convert the addresses/coordinates into place ids and then perform the distance request + This class represents a request to the Distance Matrix API and provides functionality for building the request URL, + handling addresses and coordinates, and checking the status of the response. + + Args: + API_KEY (str): The API key for the distance calculator service. + GEOCODE_API_URL (str): The URL for the geocode API. + DISTANCE_MATRIX_API_URL (str): The URL for the distance matrix API. + mode (str, optional): The transportation mode for distance requests. Must be one of "driving", "walking", "bicycling", or "transit". Defaults to "DRIVING". + + Attributes: + API_KEY (str): The API key for the distance calculator service. + GEOCODE_API_URL (str): The URL for the geocode API. + DISTANCE_MATRIX_API_URL (str): The URL for the distance matrix API. + mode (str): The transportation mode for distance requests. + available_modes (list): The list of available transportation modes. + + Methods: + __init__(API_KEY, GEOCODE_API_URL, DISTANCE_MATRIX_API_URL, mode): Constructor for the DistanceMatrixRequest class. + __call__(origin_dirs, dest_dirs): Performs the distance matrix request. + + Private Methods: + __build_address_str(address): Builds a string representation of the Google Maps geocoding API request URL for a given address. + __build_coords_str(coords): Builds a string representation of coordinates. + __add_to_str(data, global_str): Adds data to a global string. + __adapt_addresses(addresses): Adapts addresses by replacing any accented characters with their unaccented counterparts. + __check_status(response, origin, dest): Checks the status of a geocoding response and prints any errors encountered. """ def __init__( self, API_KEY, GEOCODE_API_URL, DISTANCE_MATRIX_API_URL, mode="DRIVING" ): + """ + This function initializes a DistanceCalculator object with the provided API key, geocode API URL, distance matrix API URL, + and mode of transportation (default is "DRIVING"). + + Args: + API_KEY (str): The API key for the distance calculator service. + GEOCODE_API_URL (str): The URL for the geocode API. + DISTANCE_MATRIX_API_URL (str): The URL for the distance matrix API. + mode (str, optional): The transportation mode for distance requests. Must be one of "driving", "walking", "bicycling", or "transit". Defaults to "DRIVING". + + Returns: + None""" self.API_KEY, self.GEOCODE_API_URL, self.DISTANCE_MATRIX_API_URL = ( API_KEY, GEOCODE_API_URL, @@ -177,6 +279,16 @@ def __init__( assert mode in self.available_modes, "Distance request mode not available!" def __build_address_str(self, address): + """ + Builds a string representation of the Google Maps geocoding API request URL for a given address. + + Args: + self (object): The instance of the class that the method is called on. + address (str): The address to geocode. + + Returns: + str: A string containing the place_id of the first result from the geocoding API response, or None if no results were found. + """ jsonResult = urllib.request.urlopen( self.GEOCODE_API_URL + "address=" @@ -191,9 +303,34 @@ def __build_address_str(self, address): return "place_id:" + response["results"][0]["place_id"] def __build_coords_str(self, coords): + """ + Builds a string representation of coordinates. + + This function takes a list of coordinates and builds a string representation of them by joining each coordinate with a comma. + + Args: + coords (list): The list of coordinates to be converted into a string representation. + + Returns: + str: The string representation of the coordinates.""" return ",".join([str(coord) for coord in coords]) def __add_to_str(self, data, global_str): + """ + Adds data to a global string. + + This function checks the type of the data and adds it to the global string accordingly. + If the data is a list of floats or integers, the coordinates string is built and added to the global string. + If the data is a string, the address string is built and added to the global string. + If the data is neither a list of floats or integers nor a string, an error message is printed and None is returned. + + Args: + data (list or str): The data to be added to the global string. + global_str (str): The global string to which the data will be added. + + Returns: + str or None: The updated global string if data was added successfully, or None if an error occurred. + """ if ( isinstance(data, list) and isinstance(data[0], (float, int)) @@ -211,11 +348,32 @@ def __add_to_str(self, data, global_str): return global_str def __adapt_addresses(self, addresses): + """ + This function adapts addresses by replacing any accented characters with their unaccented counterparts. + + Args: + addresses (list): A list of addresses to adapt. + + Returns: + None""" for i in range(len(addresses)): if isinstance(addresses[i], str): addresses[i] = unidecode(addresses[i]) def __check_status(self, response, origin, dest): + """ + Checks the status of a geocoding response and prints any errors encountered. + + This function takes a geocoding response, origin addresses, and destination addresses. It checks the status of each element in the response and prints an error message for any element with a status other than "OK". The function returns a boolean indicating whether all elements in the response have a status of "OK". + + Args: + response (dict): The geocoding response containing "rows" and "elements" data. + origin (list): The list of origin addresses. + dest (list): The list of destination addresses. + + Returns: + bool: True if all elements in the response have a status of "OK", False otherwise. + """ possible_status = { "OK": "Valid result", "NOT_FOUND": "The origin and/or destination can not be geocoded.", @@ -236,6 +394,16 @@ def __check_status(self, response, origin, dest): return global_check def __call__(self, origin_dirs, dest_dirs): + """ + Calls the Google Distance Matrix API to retrieve the distance and duration between origin and destination addresses. + + Args: + origin_dirs (list): A list of origin addresses. + dest_dirs (list): A list of destination addresses. + + Returns: + dict: A dictionary containing the response from the API, including distance and duration information. + """ self.org_str = "" self.dst_str = "" self.__adapt_addresses(origin_dirs) diff --git a/utils/simulation.py b/utils/simulation.py index 69d31b9..7a37bd7 100644 --- a/utils/simulation.py +++ b/utils/simulation.py @@ -6,10 +6,15 @@ def calculate_intermediate_coordinate(c1, c2, velocity, timestamp): """ Calculate intermediate coordinate between two locations using the haversine formula, based on the velocity of the vehicle and the timestamp. - c1 and c2 are tuples in the format (latitude, longitude). - velocity is the velocity of the vehicle in meters per second. - timestamp is the time elapsed in seconds since the start of the journey. - Returns the intermediate coordinate as a tuple (latitude, longitude). + + Args: + c1 (tuple): A tuple representing the coordinates of the first location in the format (latitude, longitude). + c2 (tuple): A tuple representing the coordinates of the second location in the format (latitude, longitude). + velocity (float): The velocity of the vehicle in meters per second. + timestamp (float): The time elapsed in seconds since the start of the journey. + + Returns: + dict: A dictionary containing the intermediate coordinate as a tuple (latitude, longitude). """ coords = {} lat1, lon1 = c1 @@ -64,7 +69,50 @@ def calculate_intermediate_coordinate(c1, c2, velocity, timestamp): class SimulationPath: + """ + Simulates the UAVs' positions in their corresponding route at the corresponding timestep. When iterated, returns the corresponding + position of each UAV in in the current simulated time. + + Args: + routes (dict): A dictionary containing the routes for each vehicle, with their corresponding nodes and coordinates. + distance_matrix (list of lists): A matrix containing the distances between nodes. + timestep (int, optional): The initial timestep for the simulation. Default is 100. + decrease_factor (float, optional): The factor by which the timestep decreases in each iteration. Default is 0.01. + + Attributes: + initial_timestep (int): The initial timestep for the simulation. + timestep (int): The current timestep for the simulation. + decrease_factor (float): The factor by which the simulated timestep decreases for the real time to wait. + routes (dict): A dictionary containing the routes for each vehicle, with their corresponding nodes and coordinates. + number_of_vehicles (int): The number of vehicles in the simulation. + distance_matrix (list of lists): A matrix containing the distances between nodes. + cummulative_times (list): A list of lists containing the cumulative times for each route. + current_node_index (list): A list containing the current node index for each vehicle. + finish_route (list): A list containing the stop flag for each vehicle's route. + current_path (list): A list containing the current path coordinates for each vehicle. + current_coords (list): A list containing the current coordinates of each vehicle. + time (int): The current simulation time. + + Methods: + __init__(routes, distance_matrix, timestep=100, decrease_factor=0.01): Initializes an instance of the class. + __iter__(): Returns an iterator object that iterates over the object. + __next__(): Performs a sleep operation for a specified duration, updates the current time, and calculates the current + coordinates based on the given routes and time. + """ + def __init__(self, routes, distance_matrix, timestep=100, decrease_factor=0.01): + """ + It sets the initial timestep, timestep decrease factor, routes, number of vehicles, distance matrix, + cummulative times, current node index, finish route flags, current path, current coordinates, and simulation time. + + Args: + routes (dict): A dictionary containing the routes for each vehicle, with their corresponding nodes and coordinates. + distance_matrix (list of lists): A matrix containing the distances between nodes. + timestep (int, optional): The initial timestep for the simulation. Default is 100. + decrease_factor (float, optional): The factor by which the timestep decreases in each iteration. Default is 0.01. + + Returns: + None""" self.initial_timestep = timestep self.timestep = timestep self.decrease_factor = decrease_factor @@ -106,6 +154,17 @@ def __iter__(self): return self def __next__(self): + """ + This function performs a sleep operation for a specified duration, updates the current time, + and calculates the current coordinates based on the given routes and time. It iterates through each route, + updating the current node index and path if necessary, and calculates the intermediate coordinate based on + the current time and velocity. + + Args: + None + + Returns: + dict: The current coordinates of each route.""" time.sleep(self.decrease_factor * self.timestep) if all(self.finish_route): raise StopIteration diff --git a/utils/streaming.py b/utils/streaming.py index b8ed7af..1452867 100644 --- a/utils/streaming.py +++ b/utils/streaming.py @@ -2,6 +2,18 @@ def capture_frames(): + """ + Capture frames from a camera. + + This function uses OpenCV to capture frames from a camera specified by its index. + It continuously reads frames from the camera and yields them as byte strings in the format required for streaming. + + Args: + None. + + Returns: + Iterator[str]: Yields each frame as a byte string in the format required for streaming. + """ camera = cv2.VideoCapture(0) # Adjust the camera index as needed while True: @@ -17,20 +29,3 @@ def capture_frames(): yield (b"--frame\r\n" b"Content-Type: image/jpeg\r\n\r\n" + frame + b"\r\n") camera.release() - - -class Streamer: - def __init__(self, source): - # Initialize webcam - self.camera = cv2.VideoCapture(source) - - -def process_frame(): - # Read a frame from the webcam - _, frame = camera.read() - - # Convert the frame to JPEG format - _, buffer = cv2.imencode(".jpg", frame) - frame_encoded = base64.b64encode(buffer).decode("utf-8") - - return frame_encoded