new endpoint for toilets
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build and deploy the backend to staging / Build and push image (pull_request) Failing after 2m35s
				
			
		
			
				
	
				Build and deploy the backend to staging / Deploy to staging (pull_request) Has been skipped
				
			
		
			
				
	
				Run linting on the backend code / Build (pull_request) Failing after 28s
				
			
		
			
				
	
				Run testing on the backend code / Build (pull_request) Failing after 1m24s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build and deploy the backend to staging / Build and push image (pull_request) Failing after 2m35s
				
			Build and deploy the backend to staging / Deploy to staging (pull_request) Has been skipped
				
			Run linting on the backend code / Build (pull_request) Failing after 28s
				
			Run testing on the backend code / Build (pull_request) Failing after 1m24s
				
			This commit is contained in:
		| @@ -1,13 +1,14 @@ | |||||||
| """Main app for backend api""" | """Main app for backend api""" | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
| from fastapi import FastAPI, HTTPException | from fastapi import FastAPI, HTTPException, Query | ||||||
|  |  | ||||||
| from .structs.landmark import Landmark | from .structs.landmark import Landmark, Toilets | ||||||
| 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 .utils.landmarks_manager import LandmarkManager | ||||||
|  | from .utils.toilets_manager import ToiletsManager | ||||||
| from .utils.optimizer import Optimizer | from .utils.optimizer import Optimizer | ||||||
| from .utils.refiner import Refiner | from .utils.refiner import Refiner | ||||||
| from .persistence import client as cache_client | from .persistence import client as cache_client | ||||||
| @@ -36,19 +37,15 @@ def new_trip(preferences: Preferences, | |||||||
|         (uuid) : The uuid of the first landmark in the optimized route |         (uuid) : The uuid of the first landmark in the optimized route | ||||||
|     """ |     """ | ||||||
|     if preferences is None: |     if preferences is None: | ||||||
|         raise HTTPException(status_code=406, |         raise HTTPException(status_code=406, detail="Preferences not provided or incomplete.") | ||||||
|                             detail="Preferences not provided or incomplete.") |  | ||||||
|     if (preferences.shopping.score == 0 and |     if (preferences.shopping.score == 0 and | ||||||
|         preferences.sightseeing.score == 0 and |         preferences.sightseeing.score == 0 and | ||||||
|         preferences.nature.score == 0) : |         preferences.nature.score == 0) : | ||||||
|         raise HTTPException(status_code=406, |         raise HTTPException(status_code=406, detail="All preferences are 0.") | ||||||
|                             detail="All preferences are 0.") |  | ||||||
|     if start is None: |     if start is None: | ||||||
|         raise HTTPException(status_code=406, |         raise HTTPException(status_code=406, detail="Start coordinates not provided") | ||||||
|                             detail="Start coordinates not provided") |  | ||||||
|     if not (-90 <= start[0] <= 90 or -180 <= start[1] <= 180): |     if not (-90 <= start[0] <= 90 or -180 <= start[1] <= 180): | ||||||
|         raise HTTPException(status_code=422, |         raise HTTPException(status_code=422, detail="Start coordinates not in range") | ||||||
|                             detail="Start coordinates not in range") |  | ||||||
|     if end is None: |     if end is None: | ||||||
|         end = start |         end = start | ||||||
|         logger.info("No end coordinates provided. Using start=end.") |         logger.info("No end coordinates provided. Using start=end.") | ||||||
| @@ -135,3 +132,31 @@ def get_landmark(landmark_uuid: str) -> Landmark: | |||||||
|         return landmark |         return landmark | ||||||
|     except KeyError as exc: |     except KeyError as exc: | ||||||
|         raise HTTPException(status_code=404, detail="Landmark not found") from exc |         raise HTTPException(status_code=404, detail="Landmark not found") from exc | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @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 | ||||||
|   | |||||||
| @@ -79,8 +79,8 @@ def get_distance(p1: tuple[float, float], p2: tuple[float, float]) -> int: | |||||||
|     Calculate the time in minutes to travel from one location to another. |     Calculate the time in minutes to travel from one location to another. | ||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         p1 (Tuple[float, float]): Coordinates of the starting location. |         p1 (tuple[float, float]): Coordinates of the starting location. | ||||||
|         p2 (Tuple[float, float]): Coordinates of the destination. |         p2 (tuple[float, float]): Coordinates of the destination. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|         int: Time to travel from p1 to p2 in minutes. |         int: Time to travel from p1 to p2 in minutes. | ||||||
|   | |||||||
| @@ -115,3 +115,28 @@ 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}' | ||||||
|  |      | ||||||
|  |     class Config: | ||||||
|  |         # This allows us to easily convert the model to and from dictionaries | ||||||
|  |         orm_mode = True | ||||||
							
								
								
									
										103
									
								
								backend/src/tests/test_toilets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								backend/src/tests/test_toilets.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | """Collection of tests to ensure correct implementation and track progress. """ | ||||||
|  |  | ||||||
|  | from fastapi.testclient import TestClient | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from ..structs.landmark import Toilets | ||||||
|  | from ..main import app | ||||||
|  |  | ||||||
|  | @pytest.fixture(scope="module") | ||||||
|  | def client(): | ||||||
|  |     """Client used to call the app.""" | ||||||
|  |     return TestClient(app) | ||||||
|  |  | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     "location,radius,status_code", | ||||||
|  |     [ | ||||||
|  |         ({}, None, 422),                # Invalid case: no location at all. | ||||||
|  |         ([443], None, 422),             # Invalid cases: invalid location. | ||||||
|  |         ([443, 433], None, 422),             # Invalid cases: invalid location. | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | def test_invalid_input(client, location, radius, status_code):    # pylint: disable=redefined-outer-name | ||||||
|  |     """ | ||||||
|  |     Test n°1 : Verify handling of invalid input. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         client: | ||||||
|  |         request: | ||||||
|  |     """ | ||||||
|  |     response = client.post( | ||||||
|  |         "/toilets/new", | ||||||
|  |         params={ | ||||||
|  |             "location": location, | ||||||
|  |             "radius": radius | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # checks : | ||||||
|  |     assert response.status_code == status_code | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     "location,status_code", | ||||||
|  |     [ | ||||||
|  |         ([48.2270, 7.4370], 200), # Orschwiller. | ||||||
|  |         ([10.2012, 10.123], 200), # Nigerian desert. | ||||||
|  |         ([63.989, -19.677], 200), # Hekla volcano, Iceland | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | def test_no_toilets(client, location, status_code):    # pylint: disable=redefined-outer-name | ||||||
|  |     """ | ||||||
|  |     Test n°3 : Verify the code finds some toilets in big cities. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         client: | ||||||
|  |         request: | ||||||
|  |     """ | ||||||
|  |     response = client.post( | ||||||
|  |         "/toilets/new", | ||||||
|  |         params={ | ||||||
|  |             "location": location | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     toilets_list = [Toilets.model_validate(toilet) for toilet in response.json()] | ||||||
|  |  | ||||||
|  |     # checks : | ||||||
|  |     assert response.status_code == 200  # check for successful planning | ||||||
|  |     assert isinstance(toilets_list, list)  # check that the return type is a list | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     "location,status_code", | ||||||
|  |     [ | ||||||
|  |         ([45.7576485, 4.8330241], 200), # Lyon, Bellecour. | ||||||
|  |         ([40.768502, -73.958408], 200), # New York, Upper East Side. | ||||||
|  |         ([53.482864, -2.2411116], 200), # Manchester, centre. | ||||||
|  |         ([-6.913795,  107.60278], 200), # Bandung, train station | ||||||
|  |         ([-22.970140, -43.18181], 200), # Rio de Janeiro, Copacabana | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | def test_toilets(client, location, status_code):    # pylint: disable=redefined-outer-name | ||||||
|  |     """ | ||||||
|  |     Test n°3 : Verify the code finds some toilets in big cities. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         client: | ||||||
|  |         request: | ||||||
|  |     """ | ||||||
|  |     response = client.post( | ||||||
|  |         "/toilets/new", | ||||||
|  |         params={ | ||||||
|  |             "location": location | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     toilets_list = [Toilets.model_validate(toilet) for toilet in response.json()] | ||||||
|  |  | ||||||
|  |     # checks : | ||||||
|  |     assert response.status_code == 200  # check for successful planning | ||||||
|  |     assert isinstance(toilets_list, list)  # check that the return type is a list | ||||||
|  |     assert len(toilets_list) > 0 | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| """Helper methods for testing.""" | """Helper methods for testing.""" | ||||||
| import logging | import logging | ||||||
| from typing import List |  | ||||||
| from fastapi import HTTPException | from fastapi import HTTPException | ||||||
| from pydantic import ValidationError | from pydantic import ValidationError | ||||||
|  |  | ||||||
| @@ -8,7 +7,7 @@ from ..structs.landmark import Landmark | |||||||
| from ..persistence import client as cache_client | from ..persistence import client as cache_client | ||||||
|  |  | ||||||
|  |  | ||||||
| def landmarks_to_osmid(landmarks: List[Landmark]) -> List[int] : | def landmarks_to_osmid(landmarks: list[Landmark]) -> list[int] : | ||||||
|     """ |     """ | ||||||
|     Convert the list of landmarks into a list containing their osm ids for quick landmark checking. |     Convert the list of landmarks into a list containing their osm ids for quick landmark checking. | ||||||
|      |      | ||||||
| @@ -95,7 +94,7 @@ def fetch_landmark_cache(landmark_uuid: str): | |||||||
|      |      | ||||||
|  |  | ||||||
|  |  | ||||||
| def load_trip_landmarks(client, first_uuid: str, from_cache=None) -> List[Landmark]: | def load_trip_landmarks(client, first_uuid: str, from_cache=None) -> list[Landmark]: | ||||||
|     """ |     """ | ||||||
|     Load all landmarks for a trip using the response from the API. |     Load all landmarks for a trip using the response from the API. | ||||||
|  |  | ||||||
| @@ -120,7 +119,7 @@ def load_trip_landmarks(client, first_uuid: str, from_cache=None) -> List[Landma | |||||||
|     return landmarks |     return landmarks | ||||||
|  |  | ||||||
|  |  | ||||||
| def log_trip_details(request, landmarks: List[Landmark], duration: int, target_duration: int) : | def log_trip_details(request, landmarks: list[Landmark], duration: int, target_duration: int) : | ||||||
|     """ |     """ | ||||||
|     Allows to show the detailed trip in the html test report. |     Allows to show the detailed trip in the html test report. | ||||||
|      |      | ||||||
|   | |||||||
| @@ -15,8 +15,8 @@ def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int: | |||||||
|     Calculate the time in minutes to travel from one location to another. |     Calculate the time in minutes to travel from one location to another. | ||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         p1 (Tuple[float, float]): Coordinates of the starting location. |         p1 (tuple[float, float]): Coordinates of the starting location. | ||||||
|         p2 (Tuple[float, float]): Coordinates of the destination. |         p2 (tuple[float, float]): Coordinates of the destination. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|         int: Time to travel from p1 to p2 in minutes. |         int: Time to travel from p1 to p2 in minutes. | ||||||
| @@ -55,8 +55,8 @@ def get_distance(p1: tuple[float, float], p2: tuple[float, float]) -> int: | |||||||
|     Calculate the time in minutes to travel from one location to another. |     Calculate the time in minutes to travel from one location to another. | ||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         p1 (Tuple[float, float]): Coordinates of the starting location. |         p1 (tuple[float, float]): Coordinates of the starting location. | ||||||
|         p2 (Tuple[float, float]): Coordinates of the destination. |         p2 (tuple[float, float]): Coordinates of the destination. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|         int: Time to travel from p1 to p2 in minutes. |         int: Time to travel from p1 to p2 in minutes. | ||||||
|   | |||||||
| @@ -79,6 +79,7 @@ class LandmarkManager: | |||||||
|  |  | ||||||
|         # Create a bbox using the around technique |         # Create a bbox using the around technique | ||||||
|         bbox = tuple((f"around:{reachable_bbox_side/2}", str(center_coordinates[0]), str(center_coordinates[1]))) |         bbox = tuple((f"around:{reachable_bbox_side/2}", str(center_coordinates[0]), str(center_coordinates[1]))) | ||||||
|  |          | ||||||
|         # list for sightseeing |         # list for sightseeing | ||||||
|         if preferences.sightseeing.score != 0: |         if preferences.sightseeing.score != 0: | ||||||
|             score_function = lambda score: score * 10 * preferences.sightseeing.score / 5 |             score_function = lambda score: score * 10 * preferences.sightseeing.score / 5 | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ class Optimizer: | |||||||
|             resx (list[float]): List of edge weights. |             resx (list[float]): List of edge weights. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[list[int], list[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector. |             tuple[list[int], list[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector. | ||||||
|         """ |         """ | ||||||
|          |          | ||||||
|         for i, elem in enumerate(resx): |         for i, elem in enumerate(resx): | ||||||
| @@ -79,7 +79,7 @@ class Optimizer: | |||||||
|             L (int): Number of landmarks. |             L (int): Number of landmarks. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[np.ndarray, list[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector. |             tuple[np.ndarray, list[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         l1 = [0]*L*L |         l1 = [0]*L*L | ||||||
| @@ -107,7 +107,7 @@ class Optimizer: | |||||||
|             resx (list): List of edge weights. |             resx (list): List of edge weights. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[list[int], Optional[list[list[int]]]]: A tuple containing the visit order and a list of any detected circles. |             tuple[list[int], Optional[list[list[int]]]]: A tuple containing the visit order and a list of any detected circles. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         # first round the results to have only 0-1 values |         # first round the results to have only 0-1 values | ||||||
| @@ -180,7 +180,7 @@ class Optimizer: | |||||||
|             max_time (int): Maximum time of visit allowed. |             max_time (int): Maximum time of visit allowed. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[list[float], list[float], list[int]]: Objective function coefficients, inequality constraint coefficients, and the right-hand side of the inequality constraint. |             tuple[list[float], list[float], list[int]]: Objective function coefficients, inequality constraint coefficients, and the right-hand side of the inequality constraint. | ||||||
|         """ |         """ | ||||||
|          |          | ||||||
|         # Objective function coefficients. a*x1 + b*x2 + c*x3 + ... |         # Objective function coefficients. a*x1 + b*x2 + c*x3 + ... | ||||||
| @@ -212,7 +212,7 @@ class Optimizer: | |||||||
|             L (int): Number of landmarks. |             L (int): Number of landmarks. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. |             tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         ones = [1]*L |         ones = [1]*L | ||||||
| @@ -239,7 +239,7 @@ class Optimizer: | |||||||
|             L (int): Number of landmarks. |             L (int): Number of landmarks. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. |             tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         upper_ind = np.triu_indices(L,0,L) |         upper_ind = np.triu_indices(L,0,L) | ||||||
| @@ -270,7 +270,7 @@ class Optimizer: | |||||||
|             L (int): Number of landmarks. |             L (int): Number of landmarks. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[list[np.ndarray], list[int]]: Equality constraint coefficients and the right-hand side of the equality constraints. |             tuple[list[np.ndarray], list[int]]: Equality constraint coefficients and the right-hand side of the equality constraints. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         l = [0]*L*L |         l = [0]*L*L | ||||||
| @@ -293,7 +293,7 @@ class Optimizer: | |||||||
|             landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_do'. |             landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_do'. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. |             tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         L = len(landmarks) |         L = len(landmarks) | ||||||
| @@ -319,7 +319,7 @@ class Optimizer: | |||||||
|             landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_avoid'. |             landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_avoid'. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. |             tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         L = len(landmarks) |         L = len(landmarks) | ||||||
| @@ -346,7 +346,7 @@ class Optimizer: | |||||||
|             L (int): Number of landmarks. |             L (int): Number of landmarks. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. |             tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         l_start = [1]*L + [0]*L*(L-1)   # sets departures only for start (horizontal ones) |         l_start = [1]*L + [0]*L*(L-1)   # sets departures only for start (horizontal ones) | ||||||
| @@ -374,7 +374,7 @@ class Optimizer: | |||||||
|             L (int): Number of landmarks. |             L (int): Number of landmarks. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. |             tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         A = [0]*L*L |         A = [0]*L*L | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ import yaml, logging | |||||||
|  |  | ||||||
| from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull | from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull | ||||||
| from math import pi | from math import pi | ||||||
| from typing import List |  | ||||||
|  |  | ||||||
| from ..structs.landmark import Landmark | from ..structs.landmark import Landmark | ||||||
| from . import take_most_important, get_time_separation | from . import take_most_important, get_time_separation | ||||||
| @@ -135,7 +134,7 @@ class Refiner : | |||||||
|      |      | ||||||
|         return tour |         return tour | ||||||
|      |      | ||||||
|     def integrate_landmarks(self, sub_list: List[Landmark], main_list: List[Landmark]) : |     def integrate_landmarks(self, sub_list: list[Landmark], main_list: list[Landmark]) : | ||||||
|         """ |         """ | ||||||
|         Inserts 'sub_list' of Landmarks inside the 'main_list' by leaving the ends untouched. |         Inserts 'sub_list' of Landmarks inside the 'main_list' by leaving the ends untouched. | ||||||
|          |          | ||||||
|   | |||||||
							
								
								
									
										78
									
								
								backend/src/utils/toilets_manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								backend/src/utils/toilets_manager.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | import logging, yaml | ||||||
|  | from OSMPythonTools.overpass import Overpass, overpassQueryBuilder | ||||||
|  | from OSMPythonTools.cachingStrategy import CachingStrategy, JSON | ||||||
|  |  | ||||||
|  | from ..structs.landmark import Toilets | ||||||
|  | from ..constants import LANDMARK_PARAMETERS_PATH, OSM_CACHE_DIR | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # silence the overpass logger | ||||||
|  | logging.getLogger('OSMPythonTools').setLevel(level=logging.CRITICAL) | ||||||
|  |  | ||||||
|  | class ToiletsManager: | ||||||
|  |  | ||||||
|  |     logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |     location: tuple[float, float] | ||||||
|  |     radius: int    # radius in meters | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def __init__(self, location: tuple[float, float], radius : int) -> None: | ||||||
|  |  | ||||||
|  |         self.radius = radius | ||||||
|  |         self.location = location | ||||||
|  |         self.overpass = Overpass() | ||||||
|  |         CachingStrategy.use(JSON, cacheDir=OSM_CACHE_DIR) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def generate_toilet_list(self) -> list[Toilets] : | ||||||
|  |  | ||||||
|  |          | ||||||
|  |         # Create a bbox using the around technique | ||||||
|  |         bbox = tuple((f"around:{self.radius}", str(self.location[0]), str(self.location[1]))) | ||||||
|  |         toilets_list = [] | ||||||
|  |  | ||||||
|  |         query = overpassQueryBuilder( | ||||||
|  |                 bbox = bbox, | ||||||
|  |                 elementType = ['node', 'way', 'relation'], | ||||||
|  |                 # selector can in principle be a list already, | ||||||
|  |                 # but it generates the intersection of the queries | ||||||
|  |                 # we want the union | ||||||
|  |                 selector = ['"amenity"="toilets"'], | ||||||
|  |                 includeCenter = True, | ||||||
|  |                 out = 'center' | ||||||
|  |                 ) | ||||||
|  |         self.logger.debug(f"Query: {query}") | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             result = self.overpass.query(query) | ||||||
|  |         except Exception as e: | ||||||
|  |             self.logger.error(f"Error fetching landmarks: {e}") | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |         for elem in result.elements(): | ||||||
|  |             location = (elem.centerLat(), elem.centerLon()) | ||||||
|  |  | ||||||
|  |             # handle unprecise and no-name locations | ||||||
|  |             if location[0] is None: | ||||||
|  |                     location = (elem.lat(), elem.lon()) | ||||||
|  |             else :  | ||||||
|  |                 continue | ||||||
|  |              | ||||||
|  |             toilets = Toilets(location=location) | ||||||
|  |              | ||||||
|  |             if 'wheelchair' in elem.tags().keys() and elem.tag('wheelchair') == 'yes': | ||||||
|  |                 toilets.wheelchair = True | ||||||
|  |  | ||||||
|  |             if 'changing_table' in elem.tags().keys() and elem.tag('changing_table') == 'yes': | ||||||
|  |                 toilets.changing_table = True | ||||||
|  |  | ||||||
|  |             if 'fee' in elem.tags().keys() and elem.tag('fee') == 'yes': | ||||||
|  |                 toilets.fee = True | ||||||
|  |  | ||||||
|  |             if 'opening_hours' in elem.tags().keys() : | ||||||
|  |                 toilets.opening_hours = elem.tag('opening_hours') | ||||||
|  |  | ||||||
|  |             toilets_list.append(toilets) | ||||||
|  |  | ||||||
|  |         return toilets_list | ||||||
		Reference in New Issue
	
	Block a user