Merge branch 'feature/backend/initial-deployment'
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
'shop'='department_store'
|
||||
'shop'='mall'
|
19
backend/src/constants.py
Normal file
19
backend/src/constants.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from pathlib import Path
|
||||
import os
|
||||
import logging
|
||||
|
||||
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'
|
||||
|
||||
|
||||
|
||||
cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache')
|
||||
OSM_CACHE_DIR = Path(cache_dir_string)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(
|
||||
level = logging.INFO,
|
||||
format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
151
backend/src/example_landmarks_manager.py
Normal file
151
backend/src/example_landmarks_manager.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import yaml
|
||||
import logging
|
||||
from OSMPythonTools import cachingStrategy, overpass
|
||||
from structs.landmarks import Landmark, LandmarkType
|
||||
from structs.preferences import Preferences, Preference
|
||||
import constants
|
||||
|
||||
SIGHTSEEING = LandmarkType(landmark_type='sightseeing')
|
||||
NATURE = LandmarkType(landmark_type='nature')
|
||||
SHOPPING = LandmarkType(landmark_type='shopping')
|
||||
|
||||
|
||||
class LandmarkManager:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self) -> None:
|
||||
strategy = cachingStrategy.JSON(cacheDir=constants.OSM_CACHE_DIR)
|
||||
self.query_builder = overpass.Overpass()
|
||||
with constants.AMENITY_SELECTORS_PATH.open('r') as f:
|
||||
self.amenity_selectors = yaml.safe_load(f)
|
||||
with constants.LANDMARK_PARAMETERS_PATH.open('r') as f:
|
||||
self.parameters = yaml.safe_load(f)
|
||||
# max_distance = parameters['city_bbox_side']
|
||||
|
||||
|
||||
def get_landmark_lists(self, preferences: Preferences, center_coordinates: tuple[float, float]) -> tuple[list[Landmark], list[Landmark]]:
|
||||
'''
|
||||
Generate a list of landmarks based on the preferences of the user and the center (ie. start) coordinates.
|
||||
The list is then used by the pathfinding algorithm to generate a path that goes through the most interesting landmarks.
|
||||
:param preferences: the preferences specified by the user
|
||||
:param center_coordinates: the coordinates of the starting point
|
||||
'''
|
||||
|
||||
L = []
|
||||
|
||||
# List for sightseeing
|
||||
if preferences.sightseeing.score != 0:
|
||||
score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff']) * self.parameters['church_coeff'])
|
||||
L1 = self.fetch_landmarks(self.amenity_selectors['sightseeing'], SIGHTSEEING, center_coordinates, self.parameters['city_bbox_side'], score_func)
|
||||
self.correct_score(L1, preferences.sightseeing)
|
||||
L += L1
|
||||
|
||||
# List for nature
|
||||
if preferences.nature.score != 0:
|
||||
score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff']) * self.parameters['park_coeff'])
|
||||
L2 = self.fetch_landmarks(self.amenity_selectors['nature'], NATURE, center_coordinates, self.parameters['city_bbox_side'], score_func)
|
||||
self.correct_score(L2, preferences.nature)
|
||||
L += L2
|
||||
|
||||
# List for shopping
|
||||
if preferences.shopping.score != 0:
|
||||
score_func = lambda loc, n_tags: int((10 + n_tags * self.parameters['tag_coeff']))
|
||||
L3 = self.fetch_landmarks(self.amenity_selectors['shopping'], SHOPPING, center_coordinates, self.parameters['city_bbox_side'], score_func)
|
||||
self.correct_score(L3, preferences.shopping)
|
||||
L += L3
|
||||
|
||||
# remove duplicates
|
||||
L = list(set(L))
|
||||
L_constrained = self.take_most_important(L, self.parameters['N_important'])
|
||||
self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.')
|
||||
return L, L_constrained
|
||||
|
||||
|
||||
|
||||
# Take the most important landmarks from the list
|
||||
def take_most_important(self, landmarks: list[Landmark], n_max: int) -> list[Landmark]:
|
||||
|
||||
landmarks_sorted = sorted(landmarks, key=lambda x: x.attractiveness, reverse=True)
|
||||
return landmarks_sorted[:n_max]
|
||||
|
||||
|
||||
|
||||
# Correct the score of a list of landmarks by taking into account preference settings
|
||||
def correct_score(self, L: list[Landmark], preference: Preference):
|
||||
|
||||
if len(L) == 0 :
|
||||
return
|
||||
|
||||
if L[0].type != preference.type :
|
||||
raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {L[0].name}")
|
||||
|
||||
for elem in L :
|
||||
elem.attractiveness = int(elem.attractiveness*preference.score/500) # arbitrary computation
|
||||
|
||||
|
||||
# Function to count elements within a certain radius of a location
|
||||
def count_elements_within_radius(self, point: Point, radius: int) -> int:
|
||||
|
||||
center_coordinates = (point.x, point.y)
|
||||
try:
|
||||
landmarks = ox.features_from_point(
|
||||
center_point = center_coordinates,
|
||||
dist = radius,
|
||||
tags = {'building': True} # this is a common tag to give an estimation of the number of elements in the area
|
||||
)
|
||||
return len(landmarks)
|
||||
except ox._errors.InsufficientResponseError:
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
|
||||
def fetch_landmarks(
|
||||
self,
|
||||
amenity_selectors: list[dict],
|
||||
landmarktype: LandmarkType,
|
||||
center_coordinates: tuple[float, float],
|
||||
distance: int,
|
||||
score_function: callable
|
||||
) -> list[Landmark]:
|
||||
|
||||
landmarks = ox.features_from_point(
|
||||
center_point = center_coordinates,
|
||||
dist = distance,
|
||||
tags = amenity_selectors
|
||||
)
|
||||
self.logger.info(f'Fetched {len(landmarks)} landmarks around {center_coordinates}.')
|
||||
|
||||
# cleanup the list
|
||||
# remove rows where name is None
|
||||
landmarks = landmarks[landmarks['name'].notna()]
|
||||
# TODO: remove rows that are part of another building
|
||||
|
||||
ret_landmarks = []
|
||||
for element, description in landmarks.iterrows():
|
||||
osm_type = element[0]
|
||||
osm_id = element[1]
|
||||
location = description['geometry']
|
||||
n_tags = len(description['nodes']) if type(description['nodes']) == list else 1
|
||||
|
||||
if type(location) == Point:
|
||||
location = location
|
||||
elif type(location) == Polygon or type(location) == MultiPolygon:
|
||||
location = location.centroid
|
||||
elif type(location) == LineString:
|
||||
location = location.interpolate(location.length/2)
|
||||
|
||||
score = score_function(location, n_tags)
|
||||
landmark = Landmark(
|
||||
name = description['name'],
|
||||
type = landmarktype,
|
||||
location = (location.x, location.y),
|
||||
osm_type = osm_type,
|
||||
osm_id = osm_id,
|
||||
attractiveness = score,
|
||||
must_do = False,
|
||||
n_tags = n_tags
|
||||
)
|
||||
ret_landmarks.append(landmark)
|
||||
|
||||
return ret_landmarks
|
@@ -1,13 +1,12 @@
|
||||
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 +160,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 +194,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 +300,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 +330,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
|
303
backend/src/example_refiner.py
Normal file
303
backend/src/example_refiner.py
Normal file
@@ -0,0 +1,303 @@
|
||||
from collections import defaultdict
|
||||
from heapq import heappop, heappush
|
||||
from itertools import permutations
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
|
||||
from typing import List, Tuple
|
||||
from math import pi
|
||||
|
||||
from structs.landmarks import Landmark
|
||||
from landmarks_manager import take_most_important
|
||||
from backend.src.example_optimizer import solve_optimization, link_list_simple, print_res, get_distance
|
||||
import constants
|
||||
|
||||
|
||||
# Create corridor from tour
|
||||
def create_corridor(landmarks: List[Landmark], width: float) :
|
||||
|
||||
corrected_width = (180*width)/(6371000*pi)
|
||||
|
||||
path = create_linestring(landmarks)
|
||||
obj = buffer(path, corrected_width, join_style="mitre", cap_style="square", mitre_limit=2)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
# Create linestring from tour
|
||||
def create_linestring(landmarks: List[Landmark])->List[Point] :
|
||||
|
||||
points = []
|
||||
|
||||
for landmark in landmarks :
|
||||
points.append(Point(landmark.location))
|
||||
|
||||
return LineString(points)
|
||||
|
||||
|
||||
# Check if some coordinates are in area. Used for the corridor
|
||||
def is_in_area(area: Polygon, coordinates) -> bool :
|
||||
point = Point(coordinates)
|
||||
return point.within(area)
|
||||
|
||||
|
||||
# Function to determine if two landmarks are close to each other
|
||||
def is_close_to(location1: Tuple[float], location2: Tuple[float]):
|
||||
"""Determine if two locations are close by rounding their coordinates to 3 decimals."""
|
||||
absx = abs(location1[0] - location2[0])
|
||||
absy = abs(location1[1] - location2[1])
|
||||
|
||||
return absx < 0.001 and absy < 0.001
|
||||
#return (round(location1[0], 3), round(location1[1], 3)) == (round(location2[0], 3), round(location2[1], 3))
|
||||
|
||||
|
||||
# Rearrange some landmarks in the order of visit to group visit
|
||||
def rearrange(landmarks: List[Landmark]) -> List[Landmark]:
|
||||
|
||||
i = 1
|
||||
while i < len(landmarks):
|
||||
j = i+1
|
||||
while j < len(landmarks):
|
||||
if is_close_to(landmarks[i].location, landmarks[j].location) and landmarks[i].name not in ['start', 'finish'] and landmarks[j].name not in ['start', 'finish']:
|
||||
# If they are not adjacent, move the j-th element to be adjacent to the i-th element
|
||||
if j != i + 1:
|
||||
landmarks.insert(i + 1, landmarks.pop(j))
|
||||
break # Move to the next i-th element after rearrangement
|
||||
j += 1
|
||||
i += 1
|
||||
|
||||
return landmarks
|
||||
|
||||
|
||||
# Step 1: Build the graph
|
||||
graph = defaultdict(list)
|
||||
for i in range(len(landmarks)):
|
||||
for j in range(len(landmarks)):
|
||||
if i != j:
|
||||
distance = get_distance(landmarks[i].location, landmarks[j].location, detour, speed)[1]
|
||||
graph[i].append((distance, j))
|
||||
|
||||
# Step 2: Dijkstra's algorithm to find the shortest path from start to finish
|
||||
start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start')
|
||||
finish_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'finish')
|
||||
|
||||
distances = {i: float('inf') for i in range(len(landmarks))}
|
||||
previous_nodes = {i: None for i in range(len(landmarks))}
|
||||
distances[start_idx] = 0
|
||||
priority_queue = [(0, start_idx)]
|
||||
|
||||
while priority_queue:
|
||||
current_distance, current_index = heappop(priority_queue)
|
||||
|
||||
if current_distance > distances[current_index]:
|
||||
continue
|
||||
|
||||
for neighbor_distance, neighbor_index in graph[current_index]:
|
||||
distance = current_distance + neighbor_distance
|
||||
|
||||
if distance < distances[neighbor_index]:
|
||||
distances[neighbor_index] = distance
|
||||
previous_nodes[neighbor_index] = current_index
|
||||
heappush(priority_queue, (distance, neighbor_index))
|
||||
|
||||
# Step 3: Backtrack from finish to start to find the path
|
||||
path = []
|
||||
current_index = finish_idx
|
||||
while current_index is not None:
|
||||
path.append(landmarks[current_index])
|
||||
current_index = previous_nodes[current_index]
|
||||
path.reverse()
|
||||
|
||||
return path
|
||||
"""
|
||||
def total_path_distance(path: List[Landmark], detour, speed) -> float:
|
||||
total_distance = 0
|
||||
for i in range(len(path) - 1):
|
||||
total_distance += get_distance(path[i].location, path[i + 1].location, detour, speed)[1]
|
||||
return total_distance
|
||||
"""
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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')
|
||||
finish_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'finish')
|
||||
|
||||
start_landmark = landmarks[start_idx]
|
||||
finish_landmark = landmarks[finish_idx]
|
||||
|
||||
|
||||
# Step 2: Create a list of unvisited landmarks excluding 'start' and 'finish'
|
||||
unvisited_landmarks = [lm for i, lm in enumerate(landmarks) if i not in [start_idx, finish_idx]]
|
||||
|
||||
# Step 3: Initialize the path with the 'start' landmark
|
||||
path = [start_landmark]
|
||||
coordinates = [landmarks[start_idx].location]
|
||||
|
||||
current_landmark = start_landmark
|
||||
|
||||
# Step 4: Use nearest neighbor heuristic to visit all landmarks
|
||||
while unvisited_landmarks:
|
||||
nearest_landmark = min(unvisited_landmarks, key=lambda lm: get_time(current_landmark.location, lm.location, detour, speed))
|
||||
path.append(nearest_landmark)
|
||||
coordinates.append(nearest_landmark.location)
|
||||
current_landmark = nearest_landmark
|
||||
unvisited_landmarks.remove(nearest_landmark)
|
||||
|
||||
# Step 5: Finally add the 'finish' landmark to the path
|
||||
path.append(finish_landmark)
|
||||
coordinates.append(landmarks[finish_idx].location)
|
||||
|
||||
path_poly = Polygon(coordinates)
|
||||
|
||||
return path, path_poly
|
||||
|
||||
|
||||
# Returns a list of minor landmarks around the planned path to enhance experience
|
||||
def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[Landmark], width: float) -> List[Landmark] :
|
||||
|
||||
second_order_landmarks = []
|
||||
visited_names = []
|
||||
area = create_corridor(visited_landmarks, width)
|
||||
|
||||
for visited in visited_landmarks :
|
||||
visited_names.append(visited.name)
|
||||
|
||||
for landmark in all_landmarks :
|
||||
if is_in_area(area, landmark.location) and landmark.name not in visited_names:
|
||||
second_order_landmarks.append(landmark)
|
||||
|
||||
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))
|
||||
|
||||
|
||||
# Try fix the shortest path using shapely
|
||||
def fix_using_polygon(tour: List[Landmark])-> List[Landmark] :
|
||||
|
||||
coords = []
|
||||
coords_dict = {}
|
||||
for landmark in tour :
|
||||
coords.append(landmark.location)
|
||||
if landmark.name != 'finish' :
|
||||
coords_dict[landmark.location] = landmark
|
||||
|
||||
tour_poly = Polygon(coords)
|
||||
|
||||
better_tour_poly = tour_poly.buffer(0)
|
||||
try :
|
||||
xs, ys = better_tour_poly.exterior.xy
|
||||
|
||||
if len(xs) != len(tour) :
|
||||
better_tour_poly = concave_hull(MultiPoint(coords)) # Create concave hull with "core" of tour leaving out start and finish
|
||||
xs, ys = better_tour_poly.exterior.xy
|
||||
|
||||
except :
|
||||
better_tour_poly = concave_hull(MultiPoint(coords)) # Create concave hull with "core" of tour leaving out start and finish
|
||||
xs, ys = better_tour_poly.exterior.xy
|
||||
|
||||
|
||||
# reverse the xs and ys
|
||||
xs.reverse()
|
||||
ys.reverse()
|
||||
|
||||
better_tour = [] # List of ordered visit
|
||||
name_index = {} # Maps the name of a landmark to its index in the concave polygon
|
||||
|
||||
# Loop through the polygon and generate the better (ordered) tour
|
||||
for i,x in enumerate(xs[:-1]) :
|
||||
y = ys[i]
|
||||
better_tour.append(coords_dict[tuple((x,y))])
|
||||
name_index[coords_dict[tuple((x,y))].name] = i
|
||||
|
||||
|
||||
# Scroll the list to have start in front again
|
||||
start_index = name_index['start']
|
||||
better_tour = better_tour[start_index:] + better_tour[:start_index]
|
||||
|
||||
# Append the finish back and correct the time to reach
|
||||
better_tour.append(tour[-1])
|
||||
|
||||
# Rearrange only if polygon still not simple
|
||||
if not better_tour_poly.is_simple :
|
||||
better_tour = rearrange(better_tour)
|
||||
|
||||
return better_tour
|
||||
|
||||
|
||||
# Second stage of the optimization. Use linear programming again to refine the path
|
||||
def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, detour: int, print_infos: bool) -> List[Landmark] :
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# No need to refine if no detour is taken
|
||||
# if detour == 0 :
|
||||
if False :
|
||||
new_tour = base_tour
|
||||
|
||||
else :
|
||||
minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200)
|
||||
|
||||
if print_infos : print("Using " + str(len(minor_landmarks)) + " minor landmarks around the predicted path")
|
||||
|
||||
# full set of visitable landmarks
|
||||
full_set = base_tour[:-1] + minor_landmarks # create full set of possible landmarks (without finish)
|
||||
full_set.append(base_tour[-1]) # add finish back
|
||||
|
||||
# get a new tour
|
||||
new_tour = solve_optimization(full_set, max_time+detour, False, max_landmarks)
|
||||
if new_tour is None :
|
||||
new_tour = base_tour
|
||||
|
||||
# Link the new tour
|
||||
new_tour, new_dist = link_list_simple(new_tour)
|
||||
|
||||
# If the tour contains only one landmark, return
|
||||
if len(new_tour) < 4 :
|
||||
return new_tour
|
||||
|
||||
# Find shortest path using the nearest neighbor heuristic
|
||||
better_tour, better_poly = find_shortest_path_through_all_landmarks(new_tour)
|
||||
|
||||
# Fix the tour using Polygons if the path looks weird
|
||||
if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid :
|
||||
better_tour = fix_using_polygon(better_tour)
|
||||
|
||||
# Link the tour again
|
||||
better_tour, better_dist = link_list_simple(better_tour)
|
||||
|
||||
# Choose the better tour depending on walked distance
|
||||
if new_dist < better_dist :
|
||||
final_tour = new_tour
|
||||
else :
|
||||
final_tour = better_tour
|
||||
|
||||
if print_infos :
|
||||
print("\n\n\nRefined tour (result of second stage optimization): ")
|
||||
print_res(final_tour)
|
||||
total_score = 0
|
||||
for elem in final_tour :
|
||||
total_score += elem.attractiveness
|
||||
|
||||
print("\nTotal score : " + str(total_score))
|
||||
|
||||
|
||||
|
||||
return final_tour
|
||||
|
||||
|
80
backend/src/example_tester.py
Normal file
80
backend/src/example_tester.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import pandas as pd
|
||||
|
||||
from typing import List
|
||||
from landmarks_manager import LandmarkManager
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from backend.src.example_optimizer import solve_optimization
|
||||
# from refiner import refine_optimization
|
||||
from structs.landmarks import Landmark
|
||||
from structs.landmarktype import LandmarkType
|
||||
from structs.preferences import Preferences, Preference
|
||||
|
||||
|
||||
# Helper function to create a .txt file with results
|
||||
def write_data(L: List[Landmark], file_name: str):
|
||||
|
||||
data = pd.DataFrame()
|
||||
i = 0
|
||||
|
||||
for landmark in L :
|
||||
data[i] = jsonable_encoder(landmark)
|
||||
i += 1
|
||||
|
||||
data.to_json(file_name, indent = 2, force_ascii=False)
|
||||
|
||||
def main(coordinates: tuple[float, float]) -> List[Landmark]:
|
||||
|
||||
manager = LandmarkManager()
|
||||
|
||||
preferences = Preferences(
|
||||
sightseeing=Preference(
|
||||
name='sightseeing',
|
||||
type=LandmarkType(landmark_type='sightseeing'),
|
||||
score = 5
|
||||
),
|
||||
nature=Preference(
|
||||
name='nature',
|
||||
type=LandmarkType(landmark_type='nature'),
|
||||
score = 5
|
||||
),
|
||||
shopping=Preference(
|
||||
name='shopping',
|
||||
type=LandmarkType(landmark_type='shopping'),
|
||||
score = 5
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Create start and finish
|
||||
start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=coordinates, osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=coordinates, osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
# Generate the landmarks from the start location
|
||||
landmarks, landmarks_short = manager.get_landmark_lists(preferences=preferences, center_coordinates=start.location)
|
||||
print([l.name for l in landmarks_short])
|
||||
|
||||
#write_data(landmarks, "landmarks.txt")
|
||||
|
||||
# Insert start and finish to the landmarks list
|
||||
landmarks_short.insert(0, start)
|
||||
landmarks_short.append(finish)
|
||||
|
||||
# TODO use these parameters in another way
|
||||
max_walking_time = 3 # hours
|
||||
detour = 30 # minutes
|
||||
|
||||
# First stage optimization
|
||||
base_tour = solve_optimization(landmarks_short, max_walking_time*60, True)
|
||||
|
||||
# Second stage optimization
|
||||
# refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True)
|
||||
|
||||
return base_tour
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start = (48.847132, 2.312359) # Café Chez César
|
||||
# start = (47.377859, 8.540585) # Zurich HB
|
||||
main(start)
|
@@ -1,51 +1,45 @@
|
||||
from optimizer import solve_optimization
|
||||
from refiner import refine_optimization
|
||||
from landmarks_manager import generate_landmarks
|
||||
from backend.src.example_optimizer import solve_optimization
|
||||
# from refiner import refine_optimization
|
||||
from landmarks_manager import LandmarkManager
|
||||
from structs.landmarks import Landmark
|
||||
from structs.landmarktype import LandmarkType
|
||||
from structs.preferences import Preferences, Preference
|
||||
from structs.preferences import Preferences
|
||||
from fastapi import FastAPI, Query, Body
|
||||
from typing import List
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
manager = LandmarkManager()
|
||||
|
||||
# TODO: needs a global variable to store the landmarks accross function calls
|
||||
# linked_tour = []
|
||||
|
||||
|
||||
# Assuming frontend is calling like this :
|
||||
#"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}"
|
||||
@app.post("/optimizer_coords/{start_lat}/{start_lon}/{finish_lat}/{finish_lon}")
|
||||
def main1(start_lat: float, start_lon: float, preferences: Preferences = Body(...), finish_lat: float = None, finish_lon: float = None) -> List[Landmark]:
|
||||
|
||||
@app.post("/route/new")
|
||||
def main1(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] = None) -> str:
|
||||
'''
|
||||
Main function to call the optimizer.
|
||||
:param preferences: the preferences specified by the user as the post body
|
||||
:param start: the coordinates of the starting point as a tuple of floats (as url query parameters)
|
||||
:param end: the coordinates of the finishing point as a tuple of floats (as url query parameters)
|
||||
:return: the uuid of the first landmark in the optimized route
|
||||
'''
|
||||
if preferences is None :
|
||||
raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.")
|
||||
if bool(start_lat) ^ bool(start_lon) :
|
||||
raise ValueError("Please provide both latitude and longitude for the starting point")
|
||||
if bool(finish_lat) ^ bool(finish_lon) :
|
||||
raise ValueError("Please provide both latitude and longitude for the finish point")
|
||||
if start is None:
|
||||
raise ValueError("Please provide the starting coordinates as a tuple of floats.")
|
||||
if end is None:
|
||||
end = start
|
||||
|
||||
start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(start_lat, start_lon), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
start_landmark = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(start[0], start[1]), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
end_landmark = Landmark(name='end', type=LandmarkType(landmark_type='end'), location=(end[0], end[1]), osm_type='end', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
if bool(finish_lat) and bool(finish_lon) :
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(finish_lat, finish_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
else :
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(start_lat, start_lon), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
|
||||
start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.8375946, 2.2949904), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.8375946, 2.2949904), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||
|
||||
# Generate the landmarks from the start location
|
||||
landmarks, landmarks_short = generate_landmarks(preferences=preferences, coordinates=start.location)
|
||||
|
||||
landmarks, landmarks_short = LandmarkManager.get_landmark_lists(preferences=preferences, coordinates=start.location)
|
||||
print([l.name for l in landmarks_short])
|
||||
# insert start and finish to the landmarks list
|
||||
landmarks_short.insert(0, start)
|
||||
landmarks_short.append(finish)
|
||||
landmarks_short.insert(0, start_landmark)
|
||||
landmarks_short.append(end_landmark)
|
||||
|
||||
|
||||
# TODO use these parameters in another way
|
||||
# TODO infer these parameters from the preferences
|
||||
max_walking_time = 4 # hours
|
||||
detour = 30 # minutes
|
||||
|
||||
@@ -53,32 +47,11 @@ def main1(start_lat: float, start_lon: float, preferences: Preferences = Body(..
|
||||
base_tour = solve_optimization(landmarks_short, max_walking_time*60, True)
|
||||
|
||||
# Second stage optimization
|
||||
refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True)
|
||||
# refined_tour = refine_optimization(landmarks, base_tour, max_walking_time*60+detour, True)
|
||||
|
||||
|
||||
# TODO: should look something like this
|
||||
# # set time to reach and transform into fully functional linked list
|
||||
# linked_tour += link(refined_tour)
|
||||
# return {
|
||||
# 'city_name': 'Paris',
|
||||
# 'n_stops': len(linked_tour),
|
||||
# 'first_landmark_uuid': linked_tour[0].uuid,
|
||||
# }
|
||||
|
||||
return refined_tour
|
||||
|
||||
|
||||
|
||||
|
||||
# input city, country in the form of 'Paris, France'
|
||||
@app.post("/test2/{city_country}")
|
||||
def test2(city_country: str, preferences: Preferences = Body(...)) -> List[Landmark]:
|
||||
|
||||
landmarks = generate_landmarks(city_country, preferences)
|
||||
|
||||
max_steps = 9000000
|
||||
|
||||
visiting_order = solve_optimization(landmarks, max_steps, True)
|
||||
# linked_tour = ...
|
||||
# return linked_tour[0].uuid
|
||||
return base_tour[0].uuid
|
||||
|
||||
|
||||
|
||||
@@ -86,5 +59,3 @@ def test2(city_country: str, preferences: Preferences = Body(...)) -> List[Landm
|
||||
def get_landmark(landmark_uuid: str) -> Landmark:
|
||||
#cherche dans linked_tour et retourne le landmark correspondant
|
||||
pass
|
||||
|
||||
|
||||
|
32
backend/src/parameters/amenity_selectors.yaml
Normal file
32
backend/src/parameters/amenity_selectors.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
nature:
|
||||
leisure: park
|
||||
geological: ''
|
||||
natural:
|
||||
- geyser
|
||||
- hot_spring
|
||||
- arch
|
||||
- volcano
|
||||
- stone
|
||||
tourism:
|
||||
- alpine_hut
|
||||
- viewpoint
|
||||
- zoo
|
||||
waterway: waterfall
|
||||
|
||||
shopping:
|
||||
shop:
|
||||
- department_store
|
||||
- mall
|
||||
|
||||
sightseeing:
|
||||
tourism:
|
||||
- museum
|
||||
- attraction
|
||||
- gallery
|
||||
historic: ''
|
||||
amenity:
|
||||
- planetarium
|
||||
- place_of_worship
|
||||
- fountain
|
||||
water:
|
||||
- reflecting_pool
|
6
backend/src/parameters/landmark_parameters.yaml
Normal file
6
backend/src/parameters/landmark_parameters.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
city_bbox_side: 5000 #m
|
||||
radius_close_to: 30
|
||||
church_coeff: 0.6
|
||||
park_coeff: 1.5
|
||||
tag_coeff: 100
|
||||
N_important: 40
|
3
backend/src/parameters/optimizer_parameters.yaml
Normal file
3
backend/src/parameters/optimizer_parameters.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
detour_factor: 1.4
|
||||
average_walking_speed: 4.8
|
||||
max_landmarks: 40
|
@@ -1,6 +1,5 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .landmarktype import LandmarkType
|
||||
|
||||
from uuid import uuid4
|
||||
@@ -29,3 +28,6 @@ class Landmark(BaseModel) :
|
||||
|
||||
time_to_reach_next : Optional[int] = 0 # TODO fix this in existing code
|
||||
next_uuid : Optional[str] = None # TODO implement this ASAP
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return self.uuid.int
|
@@ -1,7 +1,7 @@
|
||||
import pandas as pd
|
||||
|
||||
from typing import List
|
||||
from landmarks_manager import generate_landmarks
|
||||
from landmarks_manager import LandmarkManager
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from optimizer_v4 import solve_optimization
|
||||
@@ -26,6 +26,7 @@ def write_data(L: List[Landmark], file_name: str):
|
||||
|
||||
def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
||||
|
||||
manager = LandmarkManager()
|
||||
|
||||
preferences = Preferences(
|
||||
sightseeing=Preference(
|
||||
@@ -77,4 +78,4 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
||||
#test4(tuple((47.377859, 8.540585))) # Zurich HB
|
||||
#test4(tuple((45.7576485, 4.8330241))) # Lyon Bellecour
|
||||
test4(tuple((48.5848435, 7.7332974))) # Strasbourg Gare
|
||||
#test4(tuple((48.2067858, 16.3692340))) # Vienne
|
||||
#test4(tuple((48.2067858, 16.3692340))) # Vienne
|
||||
|
Reference in New Issue
Block a user