diff --git a/backend/src/landmarks/__init__.py b/backend/src/landmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/landmarks/cluster_manager.py b/backend/src/landmarks/cluster_manager.py index 40f1fd0..38523f6 100644 --- a/backend/src/landmarks/cluster_manager.py +++ b/backend/src/landmarks/cluster_manager.py @@ -113,7 +113,7 @@ class ClusterManager: points = [] for elem in result: osm_type = elem.get('type') - + # Get coordinates and append them to the points list _, coords = get_base_info(elem, osm_type) if coords is not None : @@ -217,7 +217,7 @@ class ClusterManager: # Define the bounding box for a given radius around the coordinates bbox = create_bbox(cluster.centroid, 300) - + # Query neighborhoods and shopping malls selectors = ['"place"~"^(suburb|neighborhood|neighbourhood|quarter|city_block)$"'] diff --git a/backend/src/landmarks/landmarks_manager.py b/backend/src/landmarks/landmarks_manager.py index 0980f43..a02a44f 100644 --- a/backend/src/landmarks/landmarks_manager.py +++ b/backend/src/landmarks/landmarks_manager.py @@ -298,6 +298,16 @@ class LandmarkManager: def description_and_keywords(self, tags: dict): """ + Generates a description and a set of keywords for a given landmark based on its tags. + + Params: + tags (dict): A dictionary containing metadata about the landmark, including its name, + importance, height, date of construction, and visitor information. + + Returns: + description (str): A string description of the landmark. + keywords (dict): A dictionary of keywords with fields such as 'importance', 'height', + 'place_type', and 'date'. """ # Extract relevant fields name = tags.get('name') @@ -314,7 +324,7 @@ class LandmarkManager: if importance is None : if len(tags.keys()) < 5 : return None, None - elif len(tags.keys()) < 10 : + if len(tags.keys()) < 10 : description = f"{name} is a well known {place_type}." elif len(tags.keys()) < 17 : importance = 'national' @@ -350,6 +360,17 @@ class LandmarkManager: def get_place_type(self, data): + """ + Determines the type of the place based on available tags such as 'amenity', 'building', + 'historic', and 'leisure'. The priority order is: 'historic' > 'building' (if not generic) > + 'amenity' > 'leisure'. + + Params: + data (dict): A dictionary containing metadata about the place. + + Returns: + place_type (str): The determined type of the place, or None if no relevant type is found. + """ amenity = data.get('amenity', None) building = data.get('building', None) historic = data.get('historic', None) @@ -369,6 +390,16 @@ class LandmarkManager: def get_date(self, data): + """ + Extracts the most relevant date from the available tags, prioritizing 'construction_date', + 'start_date', 'year_of_construction', and 'opening_date' in that order. + + Params: + data (dict): A dictionary containing metadata about the place. + + Returns: + date (str): The most relevant date found, or None if no date is available. + """ construction_date = data.get('construction_date', None) opening_date = data.get('opening_date', None) start_date = data.get('start_date', None) diff --git a/backend/src/main.py b/backend/src/main.py index 7580c1c..cce38a8 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -94,6 +94,7 @@ def new_trip(preferences: Preferences, n_tags=0) start_time = time.time() + # Generate the landmarks from the start location landmarks, landmarks_short = manager.generate_landmarks_list( center_coordinates = start, @@ -103,15 +104,6 @@ def new_trip(preferences: Preferences, if len(landmarks) == 0 : raise HTTPException(status_code=500, detail="No landmarks were found.") - - - ###################### store landmarks in json file for debug ###################### - landmarks_list = [jsonable_encoder(item) for item in landmarks] - with open('landmarks.json', 'w+') as file: - json.dump(landmarks_list, file, indent=4) - #################################################################################### - - # insert start and finish to the landmarks list landmarks_short.insert(0, start_landmark) landmarks_short.append(end_landmark) diff --git a/backend/src/optimization/refiner.py b/backend/src/optimization/refiner.py index e3f1c71..5e71033 100644 --- a/backend/src/optimization/refiner.py +++ b/backend/src/optimization/refiner.py @@ -278,7 +278,7 @@ class Refiner : better_tour_poly = concave_hull(MultiPoint(coords)) # Create concave hull with "core" of tour leaving out start and finish xs, ys = better_tour_poly.exterior.xy """ - ERROR HERE : + FIXED : ERROR HERE : Exception has occurred: AttributeError 'LineString' object has no attribute 'exterior' """ diff --git a/backend/src/overpass/overpass.py b/backend/src/overpass/overpass.py index 2cd0058..2b725ce 100644 --- a/backend/src/overpass/overpass.py +++ b/backend/src/overpass/overpass.py @@ -65,14 +65,12 @@ class Overpass : self.logger.debug(f'Query string: {query_str}') return self.fetch_data_from_api(query_str) - # Hybrid cache: some data from Overpass, some data from cache. - else : - # Resize the bbox for smaller search area and build new query string. - non_cached_bbox = Overpass._get_non_cached_bbox(non_cached_cells, bbox) - query_str = Overpass.build_query(non_cached_bbox, osm_types, selector, conditions, out) - self.logger.debug(f'Query string: {query_str}') - non_cached_responses = self.fetch_data_from_api(query_str) - return Overpass._filter_landmarks(cached_responses, bbox) + non_cached_responses + # Resize the bbox for smaller search area and build new query string. + non_cached_bbox = Overpass._get_non_cached_bbox(non_cached_cells, bbox) + query_str = Overpass.build_query(non_cached_bbox, osm_types, selector, conditions, out) + self.logger.debug(f'Query string: {query_str}') + non_cached_responses = self.fetch_data_from_api(query_str) + return Overpass._filter_landmarks(cached_responses, bbox) + non_cached_responses def fetch_data_from_api(self, query_str: str) -> List[dict]: @@ -97,8 +95,8 @@ class Overpass : return elements except urllib.error.URLError as e: - self.logger.error(f"Error connecting to Overpass API: {e}") - raise ConnectionError(f"Error connecting to Overpass API: {e}") from e + self.logger.error(f"Error connecting to Overpass API: {str(exc)}") + raise ConnectionError(f"Error connecting to Overpass API: {str(exc)}") from e except Exception as exc : raise Exception(f'An unexpected error occured: {str(exc)}') from exc @@ -389,8 +387,8 @@ def get_base_info(elem: dict, osm_type: OSM_TYPES, with_name=False) : if with_name : name = elem.get('tags', {}).get('name') return osm_id, coords, name - else : - return osm_id, coords + + return osm_id, coords def fill_cache(): @@ -421,4 +419,4 @@ def fill_cache(): except Exception as exc : overpass.logger.error(f'An error occured while parsing file {entry.path} as .json file: {str(exc)}') - overpass.logger.info(f"Successfully filled {n_files}/{total} cache files.") \ No newline at end of file + overpass.logger.info(f"Successfully filled {n_files}/{total} cache files.") diff --git a/backend/src/structs/landmark.py b/backend/src/structs/landmark.py index d6250a8..3b0e571 100644 --- a/backend/src/structs/landmark.py +++ b/backend/src/structs/landmark.py @@ -1,7 +1,7 @@ """Definition of the Landmark class to handle visitable objects across the world.""" -from typing import Optional, Literal, List +from typing import Optional, Literal from uuid import uuid4, UUID -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, Field # Output to frontend @@ -70,11 +70,6 @@ class Landmark(BaseModel) : is_place_of_worship : Optional[bool] = False - class Config: - json_encoders = { - UUID: lambda v: str(v) # Ensure UUID is serialized as a string - } - def __str__(self) -> str: """ String representation of the Landmark object. diff --git a/backend/src/tests/test_main.py b/backend/src/tests/test_main.py index 98ebad0..c2b163e 100644 --- a/backend/src/tests/test_main.py +++ b/backend/src/tests/test_main.py @@ -341,5 +341,3 @@ def test_shopping(client, request) : # pylint: disable=redefined-outer-name assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" - - \ No newline at end of file diff --git a/backend/src/tests/test_utils.py b/backend/src/tests/test_utils.py index 92d736f..5f5bf66 100644 --- a/backend/src/tests/test_utils.py +++ b/backend/src/tests/test_utils.py @@ -1,7 +1,6 @@ """Helper methods for testing.""" import logging from fastapi import HTTPException -from pydantic import ValidationError from ..structs.landmark import Landmark from ..cache import client as cache_client diff --git a/backend/src/toilets/__init__.py b/backend/src/toilets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/toilets/toilet_routes.py b/backend/src/toilets/toilet_routes.py index 20d0383..e80a023 100644 --- a/backend/src/toilets/toilet_routes.py +++ b/backend/src/toilets/toilet_routes.py @@ -1,3 +1,4 @@ +"""Defines the endpoint for fetching toilet locations.""" from fastapi import HTTPException, APIRouter, Query from ..structs.toilets import Toilets @@ -33,5 +34,5 @@ def get_toilets(location: tuple[float, float] = Query(...), radius: int = 500) - toilets_list = toilets_manager.generate_toilet_list() except KeyError as exc: raise HTTPException(status_code=404, detail="No toilets found") from exc - + return toilets_list diff --git a/backend/src/utils/bbox.py b/backend/src/utils/bbox.py index 23e9b52..f975113 100644 --- a/backend/src/utils/bbox.py +++ b/backend/src/utils/bbox.py @@ -24,4 +24,4 @@ def create_bbox(coords: tuple[float, float], radius: int): lon_min = lon - d_lon * 180 / m.pi lon_max = lon + d_lon * 180 / m.pi - return (lat_min, lon_min, lat_max, lon_max) \ No newline at end of file + return (lat_min, lon_min, lat_max, lon_max)