Skip to content

Commit

Permalink
Python code documented
Browse files Browse the repository at this point in the history
  • Loading branch information
javierganan99 committed Oct 15, 2023
1 parent 3fd492e commit 34ffbd5
Show file tree
Hide file tree
Showing 13 changed files with 471 additions and 51 deletions.
15 changes: 15 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 27 additions & 2 deletions app/routes/addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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"]:
Expand All @@ -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)
Expand Down
14 changes: 12 additions & 2 deletions app/routes/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions app/routes/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 8 additions & 4 deletions app/routes/save.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
Expand All @@ -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}
Expand Down
7 changes: 4 additions & 3 deletions app/routes/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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..."))
41 changes: 40 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())


Expand All @@ -44,11 +51,31 @@ def get_locale():

@app.route("/images/<filename>")
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
Expand All @@ -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
Expand Down
100 changes: 90 additions & 10 deletions utils/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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 = []
Expand All @@ -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 = {}
Expand Down
Loading

0 comments on commit 34ffbd5

Please sign in to comment.