better pep8
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m30s
				
			
		
			
				
	
				Run linting on the backend code / Build (pull_request) Failing after 28s
				
			
		
			
				
	
				Run testing on the backend code / Build (pull_request) Failing after 1m37s
				
			
		
			
				
	
				Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 16s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m30s
				
			Run linting on the backend code / Build (pull_request) Failing after 28s
				
			Run testing on the backend code / Build (pull_request) Failing after 1m37s
				
			Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 16s
				
			This commit is contained in:
		@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user