Compare commits

..

2 Commits

Author SHA1 Message Date
f9c86261cb switch to osmnx 2024-07-07 14:49:10 +02:00
49ce8527a3 cleanup path handling for easier dockerization 2024-06-30 18:42:59 +02:00
19 changed files with 554 additions and 1035 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,12 @@ 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
ENV OSM_CACHE_DIR=/cache
CMD ["pipenv", "run", "fastapi", "run", "src/main.py", '--port 8000', '--workers $NUM_WORKERS']

View File

@ -7,8 +7,9 @@ name = "pypi"
numpy = "*" numpy = "*"
scipy = "*" scipy = "*"
fastapi = "*" fastapi = "*"
osmpythontools = "*"
pydantic = "*" pydantic = "*"
shapely = "*" shapely = "*"
osmnx = "*"
networkx = "*"
[dev-packages] [dev-packages]

943
backend/Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
'leisure'='park'
geological
'natural'='geyser'
'natural'='hot_spring'
'natural'='arch'
'natural'='volcano'
'natural'='stone'
'tourism'='alpine_hut'
'tourism'='viewpoint'
'tourism'='zoo'
'waterway'='waterfall'

View File

@ -1,2 +0,0 @@
'shop'='department_store'
'shop'='mall'

View File

@ -1,8 +0,0 @@
'tourism'='museum'
'tourism'='attraction'
'tourism'='gallery'
historic
'amenity'='planetarium'
'amenity'='place_of_worship'
'amenity'='fountain'
'water'='reflecting_pool'

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

@ -0,0 +1,12 @@
from pathlib import Path
import os
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)

View File

@ -1,170 +1,71 @@
import math as m import yaml
import json, os import os
import osmnx as ox
from typing import List, Tuple, Optional from shapely.geometry import Point, Polygon, LineString, MultiPolygon
from OSMPythonTools.overpass import Overpass, overpassQueryBuilder
from structs.landmarks import Landmark, LandmarkType from structs.landmarks import Landmark, LandmarkType
from structs.preferences import Preferences, Preference from structs.preferences import Preferences, Preference
import constants
SIGHTSEEING = LandmarkType(landmark_type='sightseeing') SIGHTSEEING = LandmarkType(landmark_type='sightseeing')
NATURE = LandmarkType(landmark_type='nature') NATURE = LandmarkType(landmark_type='nature')
SHOPPING = LandmarkType(landmark_type='shopping') SHOPPING = LandmarkType(landmark_type='shopping')
ox.config(cache_folder=constants.OSM_CACHE_DIR)
# 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, center_coordinates: tuple[float, float]) :
with constants.AMENITY_SELECTORS_PATH.open('r') as f:
l_sights, l_nature, l_shop = get_amenities() 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)
max_distance = parameters['city_bbox_side']
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) score_func = lambda loc, n_tags: int((count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff']) * parameters['church_coeff'])
L1 = get_landmarks(amenity_selectors['sightseeing'], SIGHTSEEING, center_coordinates, max_distance, score_func)
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) score_func = lambda loc, n_tags: int((count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff']) * parameters['park_coeff'])
L2 = get_landmarks(amenity_selectors['nature'], NATURE, center_coordinates, max_distance, score_func)
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) score_func = lambda loc, n_tags: count_elements_within_radius(loc, parameters['radius_close_to']) + n_tags * parameters['tag_coeff']
L3 = get_landmarks(amenity_selectors['shopping'], SHOPPING, center_coordinates, max_distance, score_func)
correct_score(L3, preferences.shopping) correct_score(L3, preferences.shopping)
L += L3 L += L3
L = remove_duplicates(L) # remove duplicates
L = list(set(L))
print(len(L))
L_constrained = take_most_important(L, parameters['N_important'])
print(len(L_constrained))
return L, L_constrained
return L, take_most_important(L)
"""def generate_landmarks(preferences: Preferences, city_country: str = None, coordinates: Tuple[float, float] = None) -> Tuple[List[Landmark], List[Landmark]] :
l_sights, l_nature, l_shop = get_amenities()
L = []
# List for sightseeing
if preferences.sightseeing.score != 0 :
L1 = get_landmarks(l_sights, SIGHTSEEING, city_country=city_country, coordinates=coordinates)
correct_score(L1, preferences.sightseeing)
L += L1
# List for nature
if preferences.nature.score != 0 :
L2 = get_landmarks(l_nature, NATURE, city_country=city_country, coordinates=coordinates)
correct_score(L2, preferences.nature)
L += L2
# List for shopping
if preferences.shopping.score != 0 :
L3 = get_landmarks(l_shop, SHOPPING, city_country=city_country, coordinates=coordinates)
correct_score(L3, preferences.shopping)
L += L3
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 # Take the most important landmarks from the list
def take_most_important(L: List[Landmark], N = 0) -> List[Landmark] : def take_most_important(landmarks: list[Landmark], n_max: int) -> 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_clean = []
scores = [0]*len(L)
names = []
name_id = {}
for i, elem in enumerate(L) : landmarks_sorted = sorted(landmarks, key=lambda x: x.attractiveness, reverse=True)
if elem.name not in names : return landmarks_sorted[:n_max]
names.append(elem.name)
name_id[elem.name] = [i]
L_copy.append(elem)
else :
name_id[elem.name] += [i]
scores = []
for j in name_id[elem.name] :
scores.append(L[j].attractiveness)
best_id = max(range(len(scores)), key=scores.__getitem__)
t = name_id[elem.name][best_id]
if t == i :
for old in L_copy :
if old.name == elem.name :
old.attractiveness = L[t].attractiveness
scores = [0]*len(L_copy)
for i, elem in enumerate(L_copy) :
scores[i] = elem.attractiveness
res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-(N_important-N):]
for i, elem in enumerate(L_copy) :
if i in res :
L_clean.append(elem)
return L_clean
# Remove duplicate elements and elements with low score
def remove_duplicates(L: List[Landmark]) -> List[Landmark] :
"""
Removes duplicate landmarks based on their names from the given list.
Parameters:
L (List[Landmark]): A list of Landmark objects.
Returns:
List[Landmark]: A list of unique Landmark objects based on their names.
"""
L_clean = []
names = []
for landmark in L :
if landmark.name in names:
continue
else :
names.append(landmark.name)
L_clean.append(landmark)
return L_clean
# Correct the score of a list of landmarks by taking into account preference settings # Correct the score of a list of landmarks by taking into account preference settings
def correct_score(L: List[Landmark], preference: Preference) : def correct_score(L: list[Landmark], preference: Preference) :
if len(L) == 0 : if len(L) == 0 :
return return
@ -177,189 +78,111 @@ def correct_score(L: List[Landmark], preference: Preference) :
# Function to count elements within a certain radius of a location # Function to count elements within a certain radius of a location
def count_elements_within_radius(coordinates: Tuple[float, float], radius: int) -> int: def count_elements_within_radius(point: Point, radius: int) -> int:
lat = coordinates[0] center_coordinates = (point.x, point.y)
lon = coordinates[1] 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
alpha = (180*radius)/(6371000*m.pi)
bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha}
def get_landmarks(
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
)
# 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
# print(description['nodes'])
print(description['name'])
# print(location, type(location))
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)
print(score)
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
# for elem in G.iterrows():
# print(elem)
# print(elem.name)
# print(elem.address)
# name = elem.tag('name') # Add name
# location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
# # skip if unprecise location
# if name is None or location[0] is None:
# continue
# # skip if unused
# if 'disused:leisure' in elem.tags().keys():
# continue
# Build the query to find elements within the radius # # skip if part of another building
radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']], # if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes':
elementType=['node', 'way', 'relation']) # continue
try : # else :
overpass = Overpass() # osm_type = elem.type() # Add type : 'way' or 'relation'
radius_result = overpass.query(radius_query) # osm_id = elem.id() # Add OSM id
return radius_result.countElements() # elem_type = landmarktype # Add the landmark type as 'sightseeing
# n_tags = len(elem.tags().keys()) # Add number of tags
except :
return None
# Creates a bounding box around given coordinates
def create_bbox(coordinates: Tuple[float, float], side_length: int) -> Tuple[float, float, float, float]:
lat = coordinates[0]
lon = coordinates[1]
# Half the side length in km (since it's a square bbox)
half_side_length_km = side_length / 2.0
# Convert distance to degrees
lat_diff = half_side_length_km / 111 # 1 degree latitude is approximately 111 km
lon_diff = half_side_length_km / (111 * m.cos(m.radians(lat))) # Adjust for longitude based on latitude
# Calculate bbox
min_lat = lat - lat_diff
max_lat = lat + lat_diff
min_lon = lon - lon_diff
max_lon = lon + lon_diff
return min_lat, min_lon, max_lat, max_lon
def get_landmarks(list_amenity: list, landmarktype: LandmarkType, coordinates: Tuple[float, float]) -> 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']
# Create bbox around start location
bbox = create_bbox(coordinates, bbox_side)
# Initialize some variables
N = 0
L = []
overpass = Overpass()
for amenity in list_amenity :
query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
result = overpass.query(query)
N += result.countElements()
for elem in result.elements():
name = elem.tag('name') # Add name
location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
# skip if unprecise location
if name is None or location[0] is None:
continue
# skip if unused
if 'disused:leisure' in elem.tags().keys():
continue
# skip if part of another building # # Add score of given landmark based on the number of surrounding elements. Penalty for churches as there are A LOT
if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': # if amenity == "'amenity'='place_of_worship'" :
continue # score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['church_coeff'])
# elif amenity == "'leisure'='park'" :
else : # score = int((count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff'] )*parameters['park_coeff'])
osm_type = elem.type() # Add type : 'way' or 'relation' # else :
osm_id = elem.id() # Add OSM id # score = count_elements_within_radius(location, parameters['radius_close_to']) + n_tags*parameters['tag_coeff']
elem_type = landmarktype # Add the landmark type as 'sightseeing
n_tags = len(elem.tags().keys()) # Add number of tags
# 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)
elif amenity == "'leisure'='park'" :
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff)
else :
score = count_elements_within_radius(location, radius) + n_tags*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
landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=n_tags) # landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=n_tags)
L.append(landmark) # L.append(landmark)
return L # return L
"""def get_landmarks(list_amenity: list, landmarktype: LandmarkType, city_country: str = None, coordinates: Tuple[float, float] = None) -> List[Landmark] :
if city_country is None and coordinates is None :
raise ValueError("Either one of 'city_country' and 'coordinates' arguments must be specified")
if city_country is not None and coordinates is not None :
raise ValueError("Cannot specify both 'city_country' and 'coordinates' at the same time, please choose either one")
# 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']
# If city_country is specified :
if city_country is not None :
nominatim = Nominatim()
areaId = nominatim.query(city_country).areaId()
bbox = None
# If coordinates are specified :
elif coordinates is not None :
bbox = create_bbox(coordinates, bbox_side)
areaId = None
else :
raise ValueError("Argument number is not corresponding.")
# Initialize some variables
N = 0
L = []
overpass = Overpass()
for amenity in list_amenity :
query = overpassQueryBuilder(area=areaId, bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
result = overpass.query(query)
N += result.countElements()
for elem in result.elements():
name = elem.tag('name') # Add name
location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
# skip if unprecise location
if name is None or location[0] is None:
continue
# skip if unused
if 'disused:leisure' in elem.tags().keys():
continue
# skip if part of another building
if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes':
continue
else :
osm_type = elem.type() # Add type : 'way' or 'relation'
osm_id = elem.id() # Add OSM id
elem_type = landmarktype # Add the landmark type as 'sightseeing
n_tags = len(elem.tags().keys()) # Add number of tags
# 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)
elif amenity == "'leisure'='park'" :
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff)
else :
score = count_elements_within_radius(location, radius) + n_tags*tag_coeff
if score is not None :
# Generate the landmark and append it to the list
landmark = Landmark(name=name, type=elem_type, location=location, osm_type=osm_type, osm_id=osm_id, attractiveness=score, must_do=False, n_tags=n_tags)
L.append(landmark)
return L
"""

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,12 @@
import numpy as np import numpy as np
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 +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 # 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 +194,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)
@ -300,13 +300,14 @@ def respect_order(N: int, A_eq, b_eq):
# Computes the time to reach from each landmark to the next # 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 # 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 +330,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,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

View File

@ -0,0 +1,6 @@
city_bbox_side: 1500 #m
radius_close_to: 30
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
# Read from data with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f : parameters = yaml.safe_load(f)
parameters = json.loads(f.read())
detour = parameters['detour factor'] detour = parameters['detour_factor']
speed = parameters['average walking speed'] 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')
@ -174,8 +176,10 @@ def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[L
for landmark in all_landmarks : for landmark in all_landmarks :
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

View File

@ -1,6 +1,5 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from .landmarktype import LandmarkType from .landmarktype import LandmarkType
from uuid import uuid4 from uuid import uuid4
@ -28,3 +27,6 @@ class Landmark(BaseModel) :
time_to_reach_next : Optional[int] = 0 # TODO fix this in existing code time_to_reach_next : Optional[int] = 0 # TODO fix this in existing code
next_uuid : Optional[str] = None # TODO implement this ASAP next_uuid : Optional[str] = None # TODO implement this ASAP
def __hash__(self) -> int:
return self.uuid.int

View File

@ -90,7 +90,7 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
#finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.847132, 2.312359), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0) #finish = Landmark(name='finish', type=LandmarkType(landmark_type='finish'), location=(48.847132, 2.312359), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
# Generate the landmarks from the start location # Generate the landmarks from the start location
landmarks, landmarks_short = generate_landmarks(preferences=preferences, coordinates=start.location) landmarks, landmarks_short = generate_landmarks(preferences=preferences, center_coordinates=start.location)
#write_data(landmarks, "landmarks.txt") #write_data(landmarks, "landmarks.txt")
# Insert start and finish to the landmarks list # Insert start and finish to the landmarks list
@ -98,7 +98,7 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
landmarks_short.append(finish) landmarks_short.append(finish)
# TODO use these parameters in another way # TODO use these parameters in another way
max_walking_time = 2 # hours max_walking_time = 3 # hours
detour = 30 # minutes detour = 30 # minutes
# First stage optimization # First stage optimization