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:
parent
2033941953
commit
edd8a8b2b9
@ -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
|
Loading…
x
Reference in New Issue
Block a user