better structure

This commit is contained in:
kscheidecker 2025-02-19 15:53:41 +01:00
parent 83be4b7616
commit 05092e55f1
9 changed files with 88 additions and 64 deletions

@ -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)

@ -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)

@ -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