From 49ce8527a36abded3d564f6abf60eecd149c2405 Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Sun, 30 Jun 2024 18:42:59 +0200 Subject: [PATCH] cleanup path handling for easier dockerization --- ...ld-image.yaml => backend_build-image.yaml} | 2 + backend/Dockerfile | 9 +- backend/src/constants.py | 9 ++ backend/src/landmarks_manager.py | 84 ++++++++----------- backend/src/main_example.py | 23 ----- backend/src/optimizer.py | 39 +++++---- backend/src/parameters/amenity_selectors.yaml | 26 ++++++ .../src/parameters/landmark_parameters.yaml | 6 ++ .../src/parameters/landmarks_manager.params | 8 -- backend/src/parameters/optimizer.params | 5 -- .../src/parameters/optimizer_parameters.yaml | 3 + backend/src/refiner.py | 30 ++++--- 12 files changed, 126 insertions(+), 118 deletions(-) rename .gitea/workflows/{backed_build-image.yaml => backend_build-image.yaml} (95%) create mode 100644 backend/src/constants.py delete mode 100644 backend/src/main_example.py create mode 100644 backend/src/parameters/amenity_selectors.yaml create mode 100644 backend/src/parameters/landmark_parameters.yaml delete mode 100644 backend/src/parameters/landmarks_manager.params delete mode 100644 backend/src/parameters/optimizer.params create mode 100644 backend/src/parameters/optimizer_parameters.yaml diff --git a/.gitea/workflows/backed_build-image.yaml b/.gitea/workflows/backend_build-image.yaml similarity index 95% rename from .gitea/workflows/backed_build-image.yaml rename to .gitea/workflows/backend_build-image.yaml index 5542a1f..1552c03 100644 --- a/.gitea/workflows/backed_build-image.yaml +++ b/.gitea/workflows/backend_build-image.yaml @@ -2,6 +2,8 @@ on: pull_request: branches: - main + paths: + - backend/** name: Build and push docker image diff --git a/backend/Dockerfile b/backend/Dockerfile index 79dbe76..01d58a2 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,6 +6,11 @@ COPY Pipfile Pipfile.lock . RUN pip install pipenv 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'] diff --git a/backend/src/constants.py b/backend/src/constants.py new file mode 100644 index 0000000..515a040 --- /dev/null +++ b/backend/src/constants.py @@ -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') diff --git a/backend/src/landmarks_manager.py b/backend/src/landmarks_manager.py index 618e169..39fa404 100644 --- a/backend/src/landmarks_manager.py +++ b/backend/src/landmarks_manager.py @@ -1,9 +1,11 @@ import math as m import json, os +import yaml from typing import List, Tuple, Optional from OSMPythonTools.overpass import Overpass, overpassQueryBuilder +import constants from structs.landmarks import Landmark, LandmarkType from structs.preferences import Preferences, Preference @@ -13,34 +15,40 @@ NATURE = LandmarkType(landmark_type='nature') SHOPPING = LandmarkType(landmark_type='shopping') + # Include the json here # 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]) : + 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 = [] # List for sightseeing 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) L += L1 # List for nature 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) L += L2 # List for shopping 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) L += L3 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]] : @@ -69,37 +77,8 @@ def generate_landmarks(preferences: Preferences, coordinates: Tuple[float, float return remove_duplicates(L), take_most_important(L) """ # 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 -def take_most_important(L: List[Landmark], N = 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'] - +def take_most_important(L: List[Landmark], parameters: dict, N: int = 0) -> List[Landmark]: L_copy = [] L_clean = [] 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) : 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) : 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 -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 : - parameters = json.loads(f.read()) - tag_coeff = parameters['tag coeff'] - park_coeff = parameters['park coeff'] - church_coeff = parameters['church coeff'] - radius = parameters['radius close to'] - bbox_side = parameters['city bbox side'] + + # # 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()) + # tag_coeff = parameters['tag coeff'] + # park_coeff = parameters['park coeff'] + # church_coeff = parameters['church coeff'] + # radius = parameters['radius close to'] + # bbox_side = parameters['city bbox side'] # Create bbox around start location - bbox = create_bbox(coordinates, bbox_side) + bbox = create_bbox(coordinates, parameters['city_bbox_side']) # Initialize some variables 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 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'" : - 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 : - 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 : # Generate the landmark and append it to the list diff --git a/backend/src/main_example.py b/backend/src/main_example.py deleted file mode 100644 index 1a49305..0000000 --- a/backend/src/main_example.py +++ /dev/null @@ -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() diff --git a/backend/src/optimizer.py b/backend/src/optimizer.py index b346a8a..68362c6 100644 --- a/backend/src/optimizer.py +++ b/backend/src/optimizer.py @@ -1,13 +1,13 @@ import numpy as np import json, os +import yaml from typing import List, Tuple from scipy.optimize import linprog from math import radians, sin, cos, acos -from shapely import Polygon from structs.landmarks import Landmark - +import constants # Function to print the result 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 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 : - parameters = json.loads(f.read()) - detour = parameters['detour factor'] - speed = parameters['average walking speed'] + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + detour = parameters['detour_factor'] + speed = parameters['average_walking_speed'] # Objective function coefficients. a*x1 + b*x2 + c*x3 + ... c = [] @@ -194,9 +195,9 @@ def respect_number(L:int, A_ub, b_ub): b_ub.append(1) # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - max_landmarks = parameters['max landmarks'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + max_landmarks = parameters['max_landmarks'] A_ub = np.vstack((A_ub, ones*L)) b_ub.append(max_landmarks+1) @@ -300,13 +301,14 @@ def respect_order(N: int, A_eq, b_eq): # Computes the time to reach from each landmark to the next -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 - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour_factor = parameters['detour factor'] - speed = parameters['average walking speed'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour_factor = parameters['detour_factor'] + speed = parameters['average_walking_speed'] L = [] 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] : # Read the parameters from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour_factor = parameters['detour factor'] - speed = parameters['average walking speed'] + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour_factor = parameters['detour_factor'] + speed = parameters['average_walking_speed'] L = [] j = 0 diff --git a/backend/src/parameters/amenity_selectors.yaml b/backend/src/parameters/amenity_selectors.yaml new file mode 100644 index 0000000..43e8220 --- /dev/null +++ b/backend/src/parameters/amenity_selectors.yaml @@ -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'" diff --git a/backend/src/parameters/landmark_parameters.yaml b/backend/src/parameters/landmark_parameters.yaml new file mode 100644 index 0000000..3cf1c76 --- /dev/null +++ b/backend/src/parameters/landmark_parameters.yaml @@ -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 diff --git a/backend/src/parameters/landmarks_manager.params b/backend/src/parameters/landmarks_manager.params deleted file mode 100644 index 942c214..0000000 --- a/backend/src/parameters/landmarks_manager.params +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/backend/src/parameters/optimizer.params b/backend/src/parameters/optimizer.params deleted file mode 100644 index 18ca240..0000000 --- a/backend/src/parameters/optimizer.params +++ /dev/null @@ -1,5 +0,0 @@ -{ - "detour factor" : 1.4, - "average walking speed" : 4.8, - "max landmarks" : 10 -} \ No newline at end of file diff --git a/backend/src/parameters/optimizer_parameters.yaml b/backend/src/parameters/optimizer_parameters.yaml new file mode 100644 index 0000000..b6dca0e --- /dev/null +++ b/backend/src/parameters/optimizer_parameters.yaml @@ -0,0 +1,3 @@ +detour_factor: 1.4 +average_walking_speed: 4.8 +max_landmarks: 10 diff --git a/backend/src/refiner.py b/backend/src/refiner.py index 282f2f9..a292b73 100644 --- a/backend/src/refiner.py +++ b/backend/src/refiner.py @@ -1,7 +1,8 @@ from collections import defaultdict from heapq import heappop, heappush 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 typing import List, Tuple @@ -10,6 +11,7 @@ from math import pi from structs.landmarks import Landmark from landmarks_manager import take_most_important from optimizer import solve_optimization, link_list_simple, print_res, get_distance +import constants 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]: - - # Read from data - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - detour = parameters['detour factor'] - speed = parameters['average walking speed'] + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + + detour = parameters['detour_factor'] + speed = parameters['average_walking_speed'] # Step 1: Find 'start' and 'finish' landmarks start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start') @@ -174,8 +176,10 @@ def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[L for landmark in all_landmarks : if is_in_area(area, landmark.location) and landmark.name not in visited_names: 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] : - # Read from the file - with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : - parameters = json.loads(f.read()) - max_landmarks = parameters['max landmarks'] + # Read the parameters from the file + with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: + parameters = yaml.safe_load(f) + max_landmarks = parameters['max_landmarks'] if len(base_tour)-2 >= max_landmarks : return base_tour