cleanup path handling for easier dockerization

This commit is contained in:
Remy Moll 2024-06-30 18:42:59 +02:00
parent bec1827891
commit 49ce8527a3
12 changed files with 126 additions and 118 deletions

View File

@ -2,6 +2,8 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
paths:
- backend/**
name: Build and push docker image name: Build and push docker image

View File

@ -6,6 +6,11 @@ COPY Pipfile Pipfile.lock .
RUN pip install pipenv RUN pip install pipenv
RUN pipenv install --deploy --system RUN pipenv install --deploy --system
COPY . /src COPY src src
CMD ["pipenv", "run", "python", "/app/src/main.py"] EXPOSE 8000
# Set environment variables used by the deployment. These can be overridden by the user using this image.
ENV NUM_WORKERS=1
CMD ["pipenv", "run", "fastapi", "run", "src/main.py", '--port 8000', '--workers $NUM_WORKERS']

9
backend/src/constants.py Normal file
View File

@ -0,0 +1,9 @@
from pathlib import Path
PARAMETERS_DIR = Path('src/parameters')
AMENITY_SELECTORS_PATH = PARAMETERS_DIR / 'amenity_selectors.yaml'
LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml'
OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml'
OSM_CACHE_DIR = Path('cache')

View File

@ -1,9 +1,11 @@
import math as m import math as m
import json, os import json, os
import yaml
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
from OSMPythonTools.overpass import Overpass, overpassQueryBuilder from OSMPythonTools.overpass import Overpass, overpassQueryBuilder
import constants
from structs.landmarks import Landmark, LandmarkType from structs.landmarks import Landmark, LandmarkType
from structs.preferences import Preferences, Preference from structs.preferences import Preferences, Preference
@ -13,34 +15,40 @@ NATURE = LandmarkType(landmark_type='nature')
SHOPPING = LandmarkType(landmark_type='shopping') SHOPPING = LandmarkType(landmark_type='shopping')
# Include the json here # Include the json here
# Create a list of all things to visit given some preferences and a city. Ready for the optimizer # Create a list of all things to visit given some preferences and a city. Ready for the optimizer
def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float]) : def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float]) :
with constants.AMENITY_SELECTORS_PATH.open('r') as f:
amenity_selectors = yaml.safe_load(f)
with constants.LANDMARK_PARAMETERS_PATH.open('r') as f:
# even though we don't use the parameters here, we already load them to avoid unnecessary io operations
parameters = yaml.safe_load(f)
l_sights, l_nature, l_shop = get_amenities()
L = [] L = []
# List for sightseeing # List for sightseeing
if preferences.sightseeing.score != 0 : if preferences.sightseeing.score != 0 :
L1 = get_landmarks(l_sights, SIGHTSEEING, coordinates=coordinates) L1 = get_landmarks(amenity_selectors['sightseeing'], SIGHTSEEING, coordinates, parameters)
correct_score(L1, preferences.sightseeing) correct_score(L1, preferences.sightseeing)
L += L1 L += L1
# List for nature # List for nature
if preferences.nature.score != 0 : if preferences.nature.score != 0 :
L2 = get_landmarks(l_nature, NATURE, coordinates=coordinates) L2 = get_landmarks(amenity_selectors['nature'], NATURE, coordinates, parameters)
correct_score(L2, preferences.nature) correct_score(L2, preferences.nature)
L += L2 L += L2
# List for shopping # List for shopping
if preferences.shopping.score != 0 : if preferences.shopping.score != 0 :
L3 = get_landmarks(l_shop, SHOPPING, coordinates=coordinates) L3 = get_landmarks(amenity_selectors['shopping'], SHOPPING, coordinates, parameters)
correct_score(L3, preferences.shopping) correct_score(L3, preferences.shopping)
L += L3 L += L3
L = remove_duplicates(L) L = remove_duplicates(L)
return L, take_most_important(L) return L, take_most_important(L, parameters)
"""def generate_landmarks(preferences: Preferences, city_country: str = None, coordinates: Tuple[float, float] = None) -> Tuple[List[Landmark], List[Landmark]] : """def generate_landmarks(preferences: Preferences, city_country: str = None, coordinates: Tuple[float, float] = None) -> Tuple[List[Landmark], List[Landmark]] :
@ -69,37 +77,8 @@ def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float
return remove_duplicates(L), take_most_important(L) return remove_duplicates(L), take_most_important(L)
""" """
# Helper function to gather the amenities list # Helper function to gather the amenities list
def get_amenities() -> List[List[str]] :
# Get the list of amenities from the files
sightseeing = get_list('/amenities/sightseeing.am')
nature = get_list('/amenities/nature.am')
shopping = get_list('/amenities/shopping.am')
return sightseeing, nature, shopping
# Helper function to read a .am file and generate the corresponding list
def get_list(path: str) -> List[str] :
with open(os.path.dirname(os.path.abspath(__file__)) + path) as f :
content = f.readlines()
amenities = []
for line in content :
amenities.append(line.strip('\n'))
return amenities
# Take the most important landmarks from the list # Take the most important landmarks from the list
def take_most_important(L: List[Landmark], N = 0) -> List[Landmark] : def take_most_important(L: List[Landmark], parameters: dict, N: int = 0) -> List[Landmark]:
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f :
parameters = json.loads(f.read())
N_important = parameters['N important']
L_copy = [] L_copy = []
L_clean = [] L_clean = []
scores = [0]*len(L) scores = [0]*len(L)
@ -127,7 +106,8 @@ def take_most_important(L: List[Landmark], N = 0) -> List[Landmark] :
for i, elem in enumerate(L_copy) : for i, elem in enumerate(L_copy) :
scores[i] = elem.attractiveness scores[i] = elem.attractiveness
res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-(N_important-N):]
res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-(parameters['N_important']-N):]
for i, elem in enumerate(L_copy) : for i, elem in enumerate(L_copy) :
if i in res : if i in res :
@ -220,19 +200,25 @@ def create_bbox(coordinates: Tuple[float, float], side_length: int) -> Tuple[flo
return min_lat, min_lon, max_lat, max_lon return min_lat, min_lon, max_lat, max_lon
def get_landmarks(list_amenity: list, landmarktype: LandmarkType, coordinates: Tuple[float, float]) -> List[Landmark] : def get_landmarks(
list_amenity: list,
landmarktype: LandmarkType,
coordinates: Tuple[float, float],
parameters: dict
) -> List[Landmark]:
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f : # # Read the parameters from the file
parameters = json.loads(f.read()) # with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/landmarks_manager.params', "r") as f :
tag_coeff = parameters['tag coeff'] # parameters = json.loads(f.read())
park_coeff = parameters['park coeff'] # tag_coeff = parameters['tag coeff']
church_coeff = parameters['church coeff'] # park_coeff = parameters['park coeff']
radius = parameters['radius close to'] # church_coeff = parameters['church coeff']
bbox_side = parameters['city bbox side'] # radius = parameters['radius close to']
# bbox_side = parameters['city bbox side']
# Create bbox around start location # Create bbox around start location
bbox = create_bbox(coordinates, bbox_side) bbox = create_bbox(coordinates, parameters['city_bbox_side'])
# Initialize some variables # Initialize some variables
N = 0 N = 0
@ -269,11 +255,11 @@ def get_landmarks(list_amenity: list, landmarktype: LandmarkType, coordinates: T
# Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT
if amenity == "'amenity'='place_of_worship'" : if amenity == "'amenity'='place_of_worship'" :
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*church_coeff) score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['church_coeff'])
elif amenity == "'leisure'='park'" : elif amenity == "'leisure'='park'" :
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff) score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['park_coeff'])
else : else :
score = count_elements_within_radius(location, radius) + n_tags*tag_coeff score = count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff']
if score is not None : if score is not None :
# Generate the landmark and append it to the list # Generate the landmark and append it to the list

View File

@ -1,23 +0,0 @@
import fastapi
from dataclasses import dataclass
@dataclass
class Destination:
name: str
location: tuple
attractiveness: int
d = Destination()
def get_route() -> list[Destination]:
return {"route": "Hello World"}
endpoint = ("/get_route", get_route)
end
if __name__ == "__main__":
fastapi.run()

View File

@ -1,13 +1,13 @@
import numpy as np import numpy as np
import json, os import json, os
import yaml
from typing import List, Tuple from typing import List, Tuple
from scipy.optimize import linprog from scipy.optimize import linprog
from math import radians, sin, cos, acos from math import radians, sin, cos, acos
from shapely import Polygon
from structs.landmarks import Landmark from structs.landmarks import Landmark
import constants
# Function to print the result # Function to print the result
def print_res(L: List[Landmark], L_tot): def print_res(L: List[Landmark], L_tot):
@ -161,10 +161,11 @@ def get_distance(p1: Tuple[float, float], p2: Tuple[float, float], detour: float
# We want to maximize the sightseeing : max(c) st. A*x < b and A_eq*x = b_eq # We want to maximize the sightseeing : max(c) st. A*x < b and A_eq*x = b_eq
def init_ub_dist(landmarks: List[Landmark], max_steps: int): def init_ub_dist(landmarks: List[Landmark], max_steps: int):
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : # Read the parameters from the file
parameters = json.loads(f.read()) with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
detour = parameters['detour factor'] parameters = yaml.safe_load(f)
speed = parameters['average walking speed'] detour = parameters['detour_factor']
speed = parameters['average_walking_speed']
# Objective function coefficients. a*x1 + b*x2 + c*x3 + ... # Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
c = [] c = []
@ -194,9 +195,9 @@ def respect_number(L:int, A_ub, b_ub):
b_ub.append(1) b_ub.append(1)
# Read the parameters from the file # Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
parameters = json.loads(f.read()) parameters = yaml.safe_load(f)
max_landmarks = parameters['max landmarks'] max_landmarks = parameters['max_landmarks']
A_ub = np.vstack((A_ub, ones*L)) A_ub = np.vstack((A_ub, ones*L))
b_ub.append(max_landmarks+1) b_ub.append(max_landmarks+1)
@ -303,10 +304,11 @@ def respect_order(N: int, A_eq, b_eq):
def link_list(order: List[int], landmarks: List[Landmark]) -> List[Landmark]: def link_list(order: List[int], landmarks: List[Landmark]) -> List[Landmark]:
# Read the parameters from the file # Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
parameters = json.loads(f.read()) parameters = yaml.safe_load(f)
detour_factor = parameters['detour factor']
speed = parameters['average walking speed'] detour_factor = parameters['detour_factor']
speed = parameters['average_walking_speed']
L = [] L = []
j = 0 j = 0
@ -329,10 +331,11 @@ def link_list(order: List[int], landmarks: List[Landmark])->List[Landmark] :
def link_list_simple(ordered_visit: List[Landmark])-> List[Landmark] : def link_list_simple(ordered_visit: List[Landmark])-> List[Landmark] :
# Read the parameters from the file # Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
parameters = json.loads(f.read()) parameters = yaml.safe_load(f)
detour_factor = parameters['detour factor']
speed = parameters['average walking speed'] detour_factor = parameters['detour_factor']
speed = parameters['average_walking_speed']
L = [] L = []
j = 0 j = 0

View File

@ -0,0 +1,26 @@
nature:
- "'leisure'='park'"
- "geological"
- "'natural'='geyser'"
- "'natural'='hot_spring'"
- "'natural'='arch'"
- "'natural'='volcano'"
- "'natural'='stone'"
- "'tourism'='alpine_hut'"
- "'tourism'='viewpoint'"
- "'tourism'='zoo'"
- "'waterway'='waterfall'"
shopping:
- "'shop'='department_store'"
- "'shop'='mall'"
sightseeing:
- "'tourism'='museum'"
- "'tourism'='attraction'"
- "'tourism'='gallery'"
- "historic"
- "'amenity'='planetarium'"
- "'amenity'='place_of_worship'"
- "'amenity'='fountain'"
- "'water'='reflecting_pool'"

View File

@ -0,0 +1,6 @@
city_bbox_side: 10
radius_close_to: 27.5
church_coeff: 0.6
park_coeff: 1.5
tag_coeff: 100
N_important: 40

View File

@ -1,8 +0,0 @@
{
"city bbox side" : 10,
"radius close to" : 27.5,
"church coeff" : 0.6,
"park coeff" : 1.5,
"tag coeff" : 100,
"N important" : 40
}

View File

@ -1,5 +0,0 @@
{
"detour factor" : 1.4,
"average walking speed" : 4.8,
"max landmarks" : 10
}

View File

@ -0,0 +1,3 @@
detour_factor: 1.4
average_walking_speed: 4.8
max_landmarks: 10

View File

@ -1,7 +1,8 @@
from collections import defaultdict from collections import defaultdict
from heapq import heappop, heappush from heapq import heappop, heappush
from itertools import permutations from itertools import permutations
import os, json import os
import yaml
from shapely import buffer, LineString, Point, Polygon, MultiPoint, convex_hull, concave_hull, LinearRing from shapely import buffer, LineString, Point, Polygon, MultiPoint, convex_hull, concave_hull, LinearRing
from typing import List, Tuple from typing import List, Tuple
@ -10,6 +11,7 @@ from math import pi
from structs.landmarks import Landmark from structs.landmarks import Landmark
from landmarks_manager import take_most_important from landmarks_manager import take_most_important
from optimizer import solve_optimization, link_list_simple, print_res, get_distance from optimizer import solve_optimization, link_list_simple, print_res, get_distance
import constants
def create_corridor(landmarks: List[Landmark], width: float) : def create_corridor(landmarks: List[Landmark], width: float) :
@ -122,12 +124,12 @@ def total_path_distance(path: List[Landmark], detour, speed) -> float:
def find_shortest_path_through_all_landmarks(landmarks: List[Landmark]) -> List[Landmark]: def find_shortest_path_through_all_landmarks(landmarks: List[Landmark]) -> List[Landmark]:
# Read the parameters from the file
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
parameters = yaml.safe_load(f)
# Read from data detour = parameters['detour_factor']
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : speed = parameters['average_walking_speed']
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
# Step 1: Find 'start' and 'finish' landmarks # Step 1: Find 'start' and 'finish' landmarks
start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start') start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start')
@ -175,7 +177,9 @@ def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[L
if is_in_area(area, landmark.location) and landmark.name not in visited_names: if is_in_area(area, landmark.location) and landmark.name not in visited_names:
second_order_landmarks.append(landmark) second_order_landmarks.append(landmark)
return take_most_important(second_order_landmarks, len(visited_landmarks)) with constants.LANDMARK_PARAMETERS_PATH.open('r') as f:
parameters = yaml.safe_load(f)
return take_most_important(second_order_landmarks, parameters, len(visited_landmarks))
@ -195,10 +199,10 @@ def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[L
def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] : def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] :
# Read from the file # Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
parameters = json.loads(f.read()) parameters = yaml.safe_load(f)
max_landmarks = parameters['max landmarks'] max_landmarks = parameters['max_landmarks']
if len(base_tour)-2 >= max_landmarks : if len(base_tour)-2 >= max_landmarks :
return base_tour return base_tour