diff --git a/backend/src/main.py b/backend/src/main.py index 11e3039..2cc558f 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -61,7 +61,7 @@ def new_trip(preferences: Preferences, attractiveness=0, must_do=True, n_tags = 0) - + end_landmark = Landmark(name='finish', type='finish', location=(end[0], end[1]), diff --git a/backend/src/structs/shopping_location.py b/backend/src/structs/shopping_location.py deleted file mode 100644 index d784ca4..0000000 --- a/backend/src/structs/shopping_location.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Literal, Optional -from pydantic import BaseModel - - -class ShoppingLocation(BaseModel): - """" - A classe representing an interesting area for shopping. - - It can represent either a general area or a specifc route with start and end point. - The importance represents the number of shops found in this cluster. - - Attributes: - type : either a 'street' or 'area' (representing a denser field of shops). - importance : size of the cluster (number of points). - centroid : center of the cluster. - start : if the type is a street it goes from here... - end : ...to here - """ - type: Literal['street', 'area'] - importance: int - centroid: tuple - start: Optional[list] = None - end: Optional[list] = None \ No newline at end of file diff --git a/backend/src/utils/cluster_processing.py b/backend/src/utils/cluster_processing.py index a6ef15b..2114bcb 100644 --- a/backend/src/utils/cluster_processing.py +++ b/backend/src/utils/cluster_processing.py @@ -11,13 +11,28 @@ from ..structs.landmark import Landmark from ..utils.get_time_separation import get_distance from ..constants import AMENITY_SELECTORS_PATH, LANDMARK_PARAMETERS_PATH, OPTIMIZER_PARAMETERS_PATH, OSM_CACHE_DIR + class ShoppingLocation(BaseModel): + """" + A classe representing an interesting area for shopping. + + It can represent either a general area or a specifc route with start and end point. + The importance represents the number of shops found in this cluster. + + Attributes: + type : either a 'street' or 'area' (representing a denser field of shops). + importance : size of the cluster (number of points). + centroid : center of the cluster. + start : if the type is a street it goes from here... + end : ...to here + """ type: Literal['street', 'area'] importance: int centroid: tuple # start: Optional[list] = None # for later use if we want to have streets as well # end: Optional[list] = None + class ShoppingManager: logger = logging.getLogger(__name__) @@ -31,7 +46,11 @@ class ShoppingManager: def __init__(self, bbox: tuple) -> None: """ - Upon intialization, generate the list of shops used for cluster points. + Upon intialization, generate the point cloud used for cluster detection. + The points represent bag/clothes shops and general boutiques. + + Args: + bbox: The bounding box coordinates (around:radius, center_lat, center_lon). """ # Initialize overpass and cache @@ -52,27 +71,34 @@ class ShoppingManager: except Exception as e: self.logger.error(f"Error fetching landmarks: {e}") - if len(result.elements()) > 0 : - + if len(result.elements()) == 0 : + self.valid = False + + else : points = [] for elem in result.elements() : points.append(tuple((elem.lat(), elem.lon()))) self.all_points = np.array(points) - self.valid = True - - else : - self.valid = False + self.valid = True def generate_shopping_landmarks(self) -> list[Landmark]: + """ + Generate shopping landmarks based on clustered locations. + + This method first generates clusters of locations and then extracts shopping-related + locations from these clusters. It transforms each shopping location into a `Landmark` object. + + Returns: + list[Landmark]: A list of `Landmark` objects representing shopping locations. + Returns an empty list if no clusters are found. + """ - # First generate the clusters self.generate_clusters() - # Return empty list if no clusters were found if len(set(self.cluster_labels)) == 0 : - return [] + return [] # Return empty list if no clusters were found # Then generate the shopping locations self.generate_shopping_locations() @@ -87,6 +113,19 @@ class ShoppingManager: def generate_clusters(self) : + """ + Generate clusters of points using DBSCAN. + + This method applies the DBSCAN clustering algorithm with different + parameters depending on the size of the city (number of points). + It filters out noise points and keeps only the largest clusters. + + The method updates: + - `self.cluster_points`: The points belonging to clusters. + - `self.cluster_labels`: The labels for the points in clusters. + + The method also calls `filter_clusters()` to retain only the largest clusters. + """ # Apply DBSCAN to find clusters. Choose different settings for different cities. if len(self.all_points) > 200 : @@ -105,6 +144,19 @@ class ShoppingManager: def generate_shopping_locations(self) : + """ + Generate shopping locations based on clustered points. + + This method iterates over the different clusters, calculates the centroid + (as the mean of the points within each cluster), and assigns an importance + based on the size of the cluster. + + The generated shopping locations are stored in `self.shopping_locations` + as a list of `ShoppingLocation` objects, each with: + - `type`: Set to 'area'. + - `centroid`: The calculated centroid of the cluster. + - `importance`: The number of points in the cluster. + """ locations = [] @@ -127,6 +179,21 @@ class ShoppingManager: def create_landmark(self, shopping_location: ShoppingLocation) -> Landmark: + """ + Create a Landmark object based on the given shopping location. + + This method queries the Overpass API for nearby neighborhoods and shopping malls + within a 1000m radius around the shopping location centroid. It selects the closest + result and creates a landmark with the associated details such as name, type, and OSM ID. + + Parameters: + shopping_location (ShoppingLocation): A ShoppingLocation object containing + the centroid and importance of the area. + + Returns: + Landmark: A Landmark object containing details such as the name, type, + location, attractiveness, and OSM details. + """ # Define the bounding box for a given radius around the coordinates lat, lon = shopping_location.centroid @@ -153,10 +220,10 @@ class ShoppingManager: try: result = self.overpass.query(query) except Exception as e: - raise Exception("query unsuccessful") + self.logger.error(f"Error fetching landmarks: {e}") + continue for elem in result.elements(): - location = (elem.centerLat(), elem.centerLon()) if location[0] is None : @@ -168,10 +235,10 @@ class ShoppingManager: if d < min_dist : min_dist = d new_name = elem.tag('name') - osm_type = elem.type() # Add type: 'way' or 'relation' - osm_id = elem.id() # Add OSM id + osm_type = elem.type() # Add type: 'way' or 'relation' + osm_id = elem.id() # Add OSM id - # add english name if it exists + # Add english name if it exists try : new_name_en = elem.tag('name:en') except: @@ -191,7 +258,11 @@ class ShoppingManager: def filter_clusters(self): """ - Remove clusters of lesser importance. + Filter clusters to retain only the 5 largest clusters by point count. + + This method calculates the size of each cluster and filters out all but the + 5 largest clusters. It then updates the cluster points and labels to reflect + only those from the top 5 clusters. """ label_counts = np.bincount(self.cluster_labels) diff --git a/backend/src/utils/landmarks_manager.py b/backend/src/utils/landmarks_manager.py index 310692f..883fca9 100644 --- a/backend/src/utils/landmarks_manager.py +++ b/backend/src/utils/landmarks_manager.py @@ -184,7 +184,7 @@ class LandmarkManager: Fetches landmarks of a specified type from OpenStreetMap (OSM) within a bounding box centered on given coordinates. Args: - bbox (tuple[float, float, float, float]): The bounding box coordinates (min_lat, min_lon, max_lat, max_lon). + bbox (tuple[float, float, float, float]): The bounding box coordinates (around:radius, center_lat, center_lon). amenity_selector (dict): The Overpass API query selector for the desired landmark type. landmarktype (str): The type of the landmark (e.g., 'sightseeing', 'nature', 'shopping'). score_function (callable): The function to compute the score of the landmark based on its attributes.