backend/feature/add-description #63
| @@ -8,8 +8,8 @@ from pydantic import BaseModel | |||||||
| 
 | 
 | ||||||
| from ..overpass.overpass import Overpass, get_base_info | from ..overpass.overpass import Overpass, get_base_info | ||||||
| from ..structs.landmark import Landmark | from ..structs.landmark import Landmark | ||||||
| from .get_time_distance import get_distance | from ..utils.get_time_distance import get_distance | ||||||
| from .utils import create_bbox | from ..utils.bbox import create_bbox | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -4,10 +4,10 @@ import yaml | |||||||
| 
 | 
 | ||||||
| from ..structs.preferences import Preferences | from ..structs.preferences import Preferences | ||||||
| from ..structs.landmark import Landmark | from ..structs.landmark import Landmark | ||||||
| from .take_most_important import take_most_important | from ..utils.take_most_important import take_most_important | ||||||
| from .cluster_manager import ClusterManager | from .cluster_manager import ClusterManager | ||||||
| from ..overpass.overpass import Overpass, get_base_info | from ..overpass.overpass import Overpass, get_base_info | ||||||
| from .utils import create_bbox | from ..utils.bbox import create_bbox | ||||||
| 
 | 
 | ||||||
| from ..constants import AMENITY_SELECTORS_PATH, LANDMARK_PARAMETERS_PATH, OPTIMIZER_PARAMETERS_PATH | from ..constants import AMENITY_SELECTORS_PATH, LANDMARK_PARAMETERS_PATH, OPTIMIZER_PARAMETERS_PATH | ||||||
| 
 | 
 | ||||||
| @@ -7,12 +7,12 @@ from fastapi import FastAPI, HTTPException, BackgroundTasks, Query | |||||||
| from fastapi.encoders import jsonable_encoder | from fastapi.encoders import jsonable_encoder | ||||||
|  |  | ||||||
| from .logging_config import configure_logging | from .logging_config import configure_logging | ||||||
| from .structs.landmark import Landmark, Toilets | from .structs.landmark import Landmark | ||||||
| from .structs.preferences import Preferences | from .structs.preferences import Preferences | ||||||
| from .structs.linked_landmarks import LinkedLandmarks | from .structs.linked_landmarks import LinkedLandmarks | ||||||
| from .structs.trip import Trip | from .structs.trip import Trip | ||||||
| from .utils.landmarks_manager import LandmarkManager | from .landmarks.landmarks_manager import LandmarkManager | ||||||
| from .utils.toilets_manager import ToiletsManager | from .toilets.toilet_routes import router as toilets_router | ||||||
| from .optimization.optimizer import Optimizer | from .optimization.optimizer import Optimizer | ||||||
| from .optimization.refiner import Refiner | from .optimization.refiner import Refiner | ||||||
| from .overpass.overpass import fill_cache | from .overpass.overpass import fill_cache | ||||||
| @@ -38,6 +38,10 @@ async def lifespan(app: FastAPI): | |||||||
| app = FastAPI(lifespan=lifespan) | app = FastAPI(lifespan=lifespan) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | app.include_router(toilets_router) | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.post("/trip/new") | @app.post("/trip/new") | ||||||
| def new_trip(preferences: Preferences, | def new_trip(preferences: Preferences, | ||||||
|              start: tuple[float, float], |              start: tuple[float, float], | ||||||
| @@ -67,6 +71,8 @@ def new_trip(preferences: Preferences, | |||||||
|         end = start |         end = start | ||||||
|         logger.info("No end coordinates provided. Using start=end.") |         logger.info("No end coordinates provided. Using start=end.") | ||||||
|  |  | ||||||
|  |     logger.info(f"Requested new trip generation. Details:\n\tCoordinates: {start}\n\tTime: {preferences.max_time_minute}\n\tSightseeing: {preferences.sightseeing.score}\n\tNature: {preferences.nature.score}\n\tShopping: {preferences.shopping.score}") | ||||||
|  |  | ||||||
|     start_landmark = Landmark(name='start', |     start_landmark = Landmark(name='start', | ||||||
|                               type='start', |                               type='start', | ||||||
|                               location=(start[0], start[1]), |                               location=(start[0], start[1]), | ||||||
| @@ -225,32 +231,3 @@ def update_trip_time(trip_uuid: str, removed_landmark_uuid: str) -> Trip: | |||||||
|     trip = Trip.from_linked_landmarks(linked_tour, cache_client) |     trip = Trip.from_linked_landmarks(linked_tour, cache_client) | ||||||
|  |  | ||||||
|     return trip |     return trip | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.post("/toilets/new") |  | ||||||
| def get_toilets(location: tuple[float, float] = Query(...), radius: int = 500) -> list[Toilets] : |  | ||||||
|     """ |  | ||||||
|     Endpoint to find toilets within a specified radius from a given location. |  | ||||||
|  |  | ||||||
|     This endpoint expects the `location` and `radius` as **query parameters**, not in the request body. |  | ||||||
|  |  | ||||||
|     Args: |  | ||||||
|         location (tuple[float, float]): The latitude and longitude of the location to search from. |  | ||||||
|         radius (int, optional): The radius (in meters) within which to search for toilets. Defaults to 500 meters. |  | ||||||
|  |  | ||||||
|     Returns: |  | ||||||
|         list[Toilets]: A list of Toilets objects that meet the criteria. |  | ||||||
|     """ |  | ||||||
|     if location is None: |  | ||||||
|         raise HTTPException(status_code=406, detail="Coordinates not provided or invalid") |  | ||||||
|     if not (-90 <= location[0] <= 90 or -180 <= location[1] <= 180): |  | ||||||
|         raise HTTPException(status_code=422, detail="Start coordinates not in range") |  | ||||||
|  |  | ||||||
|     toilets_manager = ToiletsManager(location, radius) |  | ||||||
|  |  | ||||||
|     try : |  | ||||||
|         toilets_list = toilets_manager.generate_toilet_list() |  | ||||||
|         return toilets_list |  | ||||||
|     except KeyError as exc: |  | ||||||
|         raise HTTPException(status_code=404, detail="No toilets found") from exc |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| """Module allowing connexion to overpass api and fectch data from OSM.""" | """Module allowing connexion to overpass api and fectch data from OSM.""" | ||||||
| import os | import os | ||||||
|  | import time | ||||||
| import urllib | import urllib | ||||||
| import math | import math | ||||||
| import logging | import logging | ||||||
| @@ -153,7 +154,7 @@ class Overpass : | |||||||
|             - If no conditions are provided, the query will just use the `selector` to filter the OSM  |             - If no conditions are provided, the query will just use the `selector` to filter the OSM  | ||||||
|             elements without additional constraints. |             elements without additional constraints. | ||||||
|         """ |         """ | ||||||
|         query = '[out:json];(' |         query = '[out:json][timeout:20];(' | ||||||
|  |  | ||||||
|         # convert the bbox to string. |         # convert the bbox to string. | ||||||
|         bbox_str = f"({','.join(map(str, bbox))})" |         bbox_str = f"({','.join(map(str, bbox))})" | ||||||
| @@ -399,18 +400,25 @@ def fill_cache(): | |||||||
|     """ |     """ | ||||||
|     overpass = Overpass() |     overpass = Overpass() | ||||||
|  |  | ||||||
|  |     n_files = 0 | ||||||
|  |     total = 0 | ||||||
|  |  | ||||||
|     with os.scandir(OSM_CACHE_DIR) as it: |     with os.scandir(OSM_CACHE_DIR) as it: | ||||||
|         for entry in it: |         for entry in it: | ||||||
|             if entry.is_file() and entry.name.startswith('hollow_'): |             if entry.is_file() and entry.name.startswith('hollow_'): | ||||||
|  |                 total += 1 | ||||||
|                 try : |                 try : | ||||||
|                     # Read the whole file content as a string |                     # Read the whole file content as a string | ||||||
|                     with open(entry.path, 'r', encoding='utf-8') as f: |                     with open(entry.path, 'r', encoding='utf-8') as f: | ||||||
|                         # load data and fill the cache with the query and key |                         # load data and fill the cache with the query and key | ||||||
|                         json_data = json.load(f) |                         json_data = json.load(f) | ||||||
|                         overpass.fill_cache(json_data) |                         overpass.fill_cache(json_data) | ||||||
|  |                         n_files += 1 | ||||||
|  |                         time.sleep(1) | ||||||
|                     # Now delete the file as the cache is filled |                     # Now delete the file as the cache is filled | ||||||
|                     os.remove(entry.path) |                     os.remove(entry.path) | ||||||
|  |  | ||||||
|                 except Exception as exc : |                 except Exception as exc : | ||||||
|                     overpass.logger.error(f'An error occured while parsing file {entry.path} as .json file: {str(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.") | ||||||
| @@ -1,5 +1,4 @@ | |||||||
| """Definition of the Landmark class to handle visitable objects across the world.""" | """Definition of the Landmark class to handle visitable objects across the world.""" | ||||||
|  |  | ||||||
| from typing import Optional, Literal, List | from typing import Optional, Literal, List | ||||||
| from uuid import uuid4, UUID | from uuid import uuid4, UUID | ||||||
| from pydantic import BaseModel, ConfigDict, Field | from pydantic import BaseModel, ConfigDict, Field | ||||||
| @@ -129,26 +128,3 @@ class Landmark(BaseModel) : | |||||||
|         return (self.uuid == value.uuid or |         return (self.uuid == value.uuid or | ||||||
|                 self.osm_id == value.osm_id or |                 self.osm_id == value.osm_id or | ||||||
|                 (self.name == value.name and self.distance(value) < 0.001)) |                 (self.name == value.name and self.distance(value) < 0.001)) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Toilets(BaseModel) : |  | ||||||
|     """ |  | ||||||
|     Model for toilets. When false/empty the information is either false either not known. |  | ||||||
|     """ |  | ||||||
|     location : tuple |  | ||||||
|     wheelchair : Optional[bool] = False |  | ||||||
|     changing_table : Optional[bool] = False |  | ||||||
|     fee : Optional[bool] = False |  | ||||||
|     opening_hours : Optional[str] = "" |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __str__(self) -> str: |  | ||||||
|         """ |  | ||||||
|         String representation of the Toilets object. |  | ||||||
|  |  | ||||||
|         Returns: |  | ||||||
|             str: A formatted string with the toilets location. |  | ||||||
|         """ |  | ||||||
|         return f'Toilets @{self.location}' |  | ||||||
|  |  | ||||||
|     model_config = ConfigDict(from_attributes=True) |  | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								backend/src/structs/toilets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								backend/src/structs/toilets.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | """Definition of the Toilets class.""" | ||||||
|  | from typing import Optional | ||||||
|  | from pydantic import BaseModel, ConfigDict | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Toilets(BaseModel) : | ||||||
|  |     """ | ||||||
|  |     Model for toilets. When false/empty the information is either false either not known. | ||||||
|  |     """ | ||||||
|  |     location : tuple | ||||||
|  |     wheelchair : Optional[bool] = False | ||||||
|  |     changing_table : Optional[bool] = False | ||||||
|  |     fee : Optional[bool] = False | ||||||
|  |     opening_hours : Optional[str] = "" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def __str__(self) -> str: | ||||||
|  |         """ | ||||||
|  |         String representation of the Toilets object. | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             str: A formatted string with the toilets location. | ||||||
|  |         """ | ||||||
|  |         return f'Toilets @{self.location}' | ||||||
|  |  | ||||||
|  |     model_config = ConfigDict(from_attributes=True) | ||||||
							
								
								
									
										37
									
								
								backend/src/toilets/toilet_routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								backend/src/toilets/toilet_routes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | from fastapi import HTTPException, APIRouter, Query | ||||||
|  |  | ||||||
|  | from ..structs.toilets import Toilets | ||||||
|  | from .toilets_manager import ToiletsManager | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Define the API router | ||||||
|  | router = APIRouter() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @router.post("/toilets/new") | ||||||
|  | def get_toilets(location: tuple[float, float] = Query(...), radius: int = 500) -> list[Toilets] : | ||||||
|  |     """ | ||||||
|  |     Endpoint to find toilets within a specified radius from a given location. | ||||||
|  |  | ||||||
|  |     This endpoint expects the `location` and `radius` as **query parameters**, not in the request body. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         location (tuple[float, float]): The latitude and longitude of the location to search from. | ||||||
|  |         radius (int, optional): The radius (in meters) within which to search for toilets. Defaults to 500 meters. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         list[Toilets]: A list of Toilets objects that meet the criteria. | ||||||
|  |     """ | ||||||
|  |     if location is None: | ||||||
|  |         raise HTTPException(status_code=406, detail="Coordinates not provided or invalid") | ||||||
|  |     if not (-90 <= location[0] <= 90 or -180 <= location[1] <= 180): | ||||||
|  |         raise HTTPException(status_code=422, detail="Start coordinates not in range") | ||||||
|  |  | ||||||
|  |     toilets_manager = ToiletsManager(location, radius) | ||||||
|  |  | ||||||
|  |     try : | ||||||
|  |         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 | ||||||
| @@ -2,8 +2,8 @@ | |||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
| from ..overpass.overpass import Overpass, get_base_info | from ..overpass.overpass import Overpass, get_base_info | ||||||
| from ..structs.landmark import Toilets | from ..structs.toilets import Toilets | ||||||
| from .utils import create_bbox | from ..utils.bbox import create_bbox | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # silence the overpass logger | # silence the overpass logger | ||||||
		Reference in New Issue
	
	Block a user