reviewed code structure, cleaned comments, now pep8 conform
This commit is contained in:
parent
af4d68f36f
commit
111e6836f6
11
backend/src/amenities/nature.am
Normal file
11
backend/src/amenities/nature.am
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
'leisure'='park'
|
||||||
|
geological
|
||||||
|
'natural'='geyser'
|
||||||
|
'natural'='hot_spring'
|
||||||
|
'natural'='arch'
|
||||||
|
'natural'='volcano'
|
||||||
|
'natural'='stone'
|
||||||
|
'tourism'='alpine_hut'
|
||||||
|
'tourism'='viewpoint'
|
||||||
|
'tourism'='zoo'
|
||||||
|
'waterway'='waterfall'
|
2
backend/src/amenities/shopping.am
Normal file
2
backend/src/amenities/shopping.am
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
'shop'='department_store'
|
||||||
|
'shop'='mall'
|
9
backend/src/amenities/sightseeing.am
Normal file
9
backend/src/amenities/sightseeing.am
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
'tourism'='museum'
|
||||||
|
'tourism'='attraction'
|
||||||
|
'tourism'='gallery'
|
||||||
|
historic
|
||||||
|
'amenity'='arts_centre'
|
||||||
|
'amenity'='planetarium'
|
||||||
|
'amenity'='place_of_worship'
|
||||||
|
'amenity'='fountain'
|
||||||
|
'water'='reflecting_pool'
|
@ -1,21 +1,13 @@
|
|||||||
import math as m
|
import math as m
|
||||||
|
import json, os
|
||||||
|
|
||||||
from OSMPythonTools.api import Api
|
from typing import List, Tuple
|
||||||
from OSMPythonTools.overpass import Overpass, overpassQueryBuilder, Nominatim
|
from OSMPythonTools.overpass import Overpass, overpassQueryBuilder, Nominatim
|
||||||
|
|
||||||
from structs.landmarks import Landmark, LandmarkType
|
from structs.landmarks import Landmark, LandmarkType
|
||||||
from structs.preferences import Preferences, Preference
|
from structs.preferences import Preferences, Preference
|
||||||
from typing import List
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
BBOX_SIDE = 10 # size of bbox in *km* for general area, 10km
|
|
||||||
RADIUS_CLOSE_TO = 27.5 # size of area in *m* for close features, 30m radius
|
|
||||||
MIN_SCORE = 30 # DEPRECIATED. discard elements with score < 30
|
|
||||||
MIN_TAGS = 5 # DEPRECIATED. discard elements withs less than 5 tags
|
|
||||||
CHURCH_PENALTY = 0.6 # penalty to reduce score of curches
|
|
||||||
PARK_COEFF = 1.4 # multiplier for parks
|
|
||||||
N_IMPORTANT = 40 # take the 30 most important landmarks
|
|
||||||
|
|
||||||
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')
|
||||||
@ -23,71 +15,64 @@ 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, 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]] :
|
||||||
|
|
||||||
l_sights = ["'tourism'='museum'", "'tourism'='attraction'", "'tourism'='gallery'", 'historic', "'amenity'='arts_centre'", "'amenity'='planetarium'", "'amenity'='place_of_worship'", "'amenity'='fountain'", '"water"="reflecting_pool"']
|
|
||||||
l_nature = ["'leisure'='park'", 'geological', "'natural'='geyser'", "'natural'='hot_spring'", '"natural"="arch"', '"natural"="cave_entrance"', '"natural"="volcano"', '"natural"="stone"', '"tourism"="alpine_hut"', '"tourism"="picnic_site"', '"tourism"="viewpoint"', '"tourism"="zoo"', '"waterway"="waterfall"']
|
|
||||||
l_shop = ["'shop'='department_store'", "'shop'='mall'"] #, '"shop"="collector"', '"shop"="antiques"']
|
|
||||||
|
|
||||||
|
l_sights, l_nature, l_shop = get_amenities()
|
||||||
L = []
|
L = []
|
||||||
|
|
||||||
# Use 'City, Country'
|
# List for sightseeing
|
||||||
if city_country is not None :
|
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 sightseeing
|
# List for nature
|
||||||
if preferences.sightseeing.score != 0 :
|
if preferences.nature.score != 0 :
|
||||||
L1 = get_landmarks_nominatim(city_country, l_sights, SIGHTSEEING)
|
L2 = get_landmarks(l_nature, NATURE, city_country=city_country, coordinates=coordinates)
|
||||||
correct_score(L1, preferences.sightseeing)
|
correct_score(L2, preferences.nature)
|
||||||
L += L1
|
L += L2
|
||||||
|
|
||||||
# List for nature
|
|
||||||
if preferences.nature.score != 0 :
|
|
||||||
L2 = get_landmarks_nominatim(city_country, l_nature, NATURE)
|
|
||||||
correct_score(L2, preferences.nature)
|
|
||||||
L += L2
|
|
||||||
|
|
||||||
# List for shopping
|
|
||||||
if preferences.shopping.score != 0 :
|
|
||||||
L3 = get_landmarks_nominatim(city_country, l_shop, SHOPPING)
|
|
||||||
correct_score(L3, preferences.shopping)
|
|
||||||
L += L3
|
|
||||||
|
|
||||||
# Use coordinates
|
|
||||||
elif coordinates is not None :
|
|
||||||
|
|
||||||
# List for sightseeing
|
|
||||||
if preferences.sightseeing.score != 0 :
|
|
||||||
L1 = get_landmarks_coords(coordinates, l_sights, SIGHTSEEING)
|
|
||||||
correct_score(L1, preferences.sightseeing)
|
|
||||||
L += L1
|
|
||||||
|
|
||||||
# List for nature
|
|
||||||
if preferences.nature.score != 0 :
|
|
||||||
L2 = get_landmarks_coords(coordinates, l_nature, NATURE)
|
|
||||||
correct_score(L2, preferences.nature)
|
|
||||||
L += L2
|
|
||||||
|
|
||||||
# List for shopping
|
|
||||||
if preferences.shopping.score != 0 :
|
|
||||||
L3 = get_landmarks_coords(coordinates, l_shop, SHOPPING)
|
|
||||||
correct_score(L3, preferences.shopping)
|
|
||||||
L += L3
|
|
||||||
|
|
||||||
|
# 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)
|
return remove_duplicates(L), take_most_important(L)
|
||||||
#return L, cleanup_list(L)
|
|
||||||
|
|
||||||
# Determines if two locations are close to each other
|
|
||||||
def is_close_to(loc1: Tuple[float, float], loc2: Tuple[float, float])->bool :
|
|
||||||
|
|
||||||
alpha = (180*RADIUS_CLOSE_TO)/(6371000*m.pi)
|
# Helper function to gather the amenities list
|
||||||
if abs(loc1[0] - loc2[0]) + abs(loc1[1] - loc2[1]) < alpha*2 :
|
def get_amenities() -> List[List[str]] :
|
||||||
return True
|
|
||||||
else :
|
# Get the list of amenities from the files
|
||||||
return False
|
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])->List[Landmark] :
|
def take_most_important(L: List[Landmark]) -> 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)
|
||||||
@ -110,11 +95,12 @@ def take_most_important(L: List[Landmark])->List[Landmark] :
|
|||||||
for old in L_copy :
|
for old in L_copy :
|
||||||
if old.name == elem.name :
|
if old.name == elem.name :
|
||||||
old.attractiveness = L[t].attractiveness
|
old.attractiveness = L[t].attractiveness
|
||||||
|
|
||||||
scores = [0]*len(L_copy)
|
scores = [0]*len(L_copy)
|
||||||
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:]
|
res = sorted(range(len(scores)), key = lambda sub: scores[sub])[-N_important:]
|
||||||
|
|
||||||
for i, elem in enumerate(L_copy) :
|
for i, elem in enumerate(L_copy) :
|
||||||
if i in res :
|
if i in res :
|
||||||
@ -122,13 +108,14 @@ def take_most_important(L: List[Landmark])->List[Landmark] :
|
|||||||
|
|
||||||
return L_clean
|
return L_clean
|
||||||
|
|
||||||
|
|
||||||
# Remove duplicate elements and elements with low score
|
# Remove duplicate elements and elements with low score
|
||||||
def remove_duplicates(L: List[Landmark])->List[Landmark] :
|
def remove_duplicates(L: List[Landmark]) -> List[Landmark] :
|
||||||
L_clean = []
|
L_clean = []
|
||||||
names = []
|
names = []
|
||||||
|
|
||||||
for landmark in L :
|
for landmark in L :
|
||||||
if landmark.name in names : # Remove duplicates
|
if landmark.name in names :
|
||||||
continue
|
continue
|
||||||
else :
|
else :
|
||||||
names.append(landmark.name)
|
names.append(landmark.name)
|
||||||
@ -136,7 +123,8 @@ def remove_duplicates(L: List[Landmark])->List[Landmark] :
|
|||||||
|
|
||||||
return L_clean
|
return L_clean
|
||||||
|
|
||||||
# Correct the score of a list of landmarks by taking into account preferences and the number of tags
|
|
||||||
|
# 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 :
|
||||||
@ -148,53 +136,32 @@ def correct_score(L: List[Landmark], preference: Preference) :
|
|||||||
for elem in L :
|
for elem in L :
|
||||||
elem.attractiveness = int(elem.attractiveness*preference.score/500) # arbitrary computation
|
elem.attractiveness = int(elem.attractiveness*preference.score/500) # arbitrary computation
|
||||||
|
|
||||||
# Correct the score of a list of landmarks by taking into account preferences and the number of tags
|
|
||||||
def correct_score_test(L: List[Landmark], preference: Preference) :
|
|
||||||
|
|
||||||
if len(L) == 0 :
|
# Function to count elements within a certain radius of a location
|
||||||
return
|
def count_elements_within_radius(coordinates: Tuple[float, float], radius: int) -> int:
|
||||||
|
|
||||||
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/100) + elem.n_tags # arbitrary correction of the balance score vs number of tags
|
|
||||||
elem.attractiveness = elem.attractiveness*preference.score # arbitrary computation
|
|
||||||
|
|
||||||
# Function to count elements within a 25m radius of a location
|
|
||||||
def count_elements_within_radius(coordinates: Tuple[float, float]) -> int:
|
|
||||||
|
|
||||||
lat = coordinates[0]
|
lat = coordinates[0]
|
||||||
lon = coordinates[1]
|
lon = coordinates[1]
|
||||||
|
|
||||||
alpha = (180*RADIUS_CLOSE_TO)/(6371000*m.pi)
|
alpha = (180*radius)/(6371000*m.pi)
|
||||||
|
|
||||||
bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha}
|
bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha}
|
||||||
overpass = Overpass()
|
|
||||||
|
|
||||||
# Build the query to find elements within the radius
|
# Build the query to find elements within the radius
|
||||||
radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']],
|
radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']],
|
||||||
elementType=['node', 'way', 'relation'])
|
elementType=['node', 'way', 'relation'])
|
||||||
|
|
||||||
try :
|
try :
|
||||||
|
overpass = Overpass()
|
||||||
radius_result = overpass.query(radius_query)
|
radius_result = overpass.query(radius_query)
|
||||||
|
|
||||||
# The count is the number of elements found
|
|
||||||
return radius_result.countElements()
|
return radius_result.countElements()
|
||||||
|
|
||||||
except :
|
except :
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Creates a bounding box around precise coordinates
|
|
||||||
|
# Creates a bounding box around given coordinates
|
||||||
def create_bbox(coordinates: Tuple[float, float], side_length: int) -> Tuple[float, float, float, float]:
|
def create_bbox(coordinates: Tuple[float, float], side_length: int) -> Tuple[float, float, float, float]:
|
||||||
"""
|
|
||||||
Create a simple bounding box around given coordinates.
|
|
||||||
:param coordinates: tuple (lat, lon)
|
|
||||||
-> lat: Latitude of the center point.
|
|
||||||
-> lon: Longitude of the center point.
|
|
||||||
:param side_length: int - side length of the bbox in km
|
|
||||||
:return: Bounding box as (min_lat, min_lon, max_lat, max_lon).
|
|
||||||
"""
|
|
||||||
lat = coordinates[0]
|
lat = coordinates[0]
|
||||||
lon = coordinates[1]
|
lon = coordinates[1]
|
||||||
|
|
||||||
@ -214,18 +181,26 @@ 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
|
||||||
|
|
||||||
# Generates the list of landmarks for a given Landmarktype. Needs coordinates, a list of amenities and the corresponding LandmarkType
|
# Generates the list of landmarks for a given Landmarktype. Needs coordinates, a list of amenities and the corresponding LandmarkType
|
||||||
def get_landmarks_coords(coordinates: Tuple[float, float], l: List[Landmark], landmarktype: LandmarkType)->List[Landmark]:
|
def get_landmarks_coords(coordinates: Tuple[float, float], list_amenity: list, landmarktype: LandmarkType) -> List[Landmark]:
|
||||||
|
|
||||||
overpass = Overpass()
|
# 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']
|
||||||
|
|
||||||
# Generate a bbox around current coordinates
|
# Generate a bbox around current coordinates
|
||||||
bbox = create_bbox(coordinates, BBOX_SIDE)
|
bbox = create_bbox(coordinates, bbox_side)
|
||||||
|
|
||||||
# Initialize some variables
|
# Initialize some variables
|
||||||
|
overpass = Overpass()
|
||||||
N = 0
|
N = 0
|
||||||
L = []
|
L = []
|
||||||
|
|
||||||
for amenity in l :
|
for amenity in list_amenity :
|
||||||
query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
|
query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
|
||||||
result = overpass.query(query)
|
result = overpass.query(query)
|
||||||
N += result.countElements()
|
N += result.countElements()
|
||||||
@ -235,16 +210,15 @@ def get_landmarks_coords(coordinates: Tuple[float, float], l: List[Landmark], la
|
|||||||
name = elem.tag('name') # Add name, decode to ASCII
|
name = elem.tag('name') # Add name, decode to ASCII
|
||||||
location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
|
location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon)
|
||||||
|
|
||||||
# skip if unprecise location
|
# Skip if unprecise location
|
||||||
if name is None or location[0] is None:
|
if name is None or location[0] is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# skip if unused
|
# Skip if unused
|
||||||
if 'disused:leisure' in elem.tags().keys():
|
if 'disused:leisure' in elem.tags().keys():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
else :
|
else :
|
||||||
|
|
||||||
osm_type = elem.type() # Add type : 'way' or 'relation'
|
osm_type = elem.type() # Add type : 'way' or 'relation'
|
||||||
osm_id = elem.id() # Add OSM id
|
osm_id = elem.id() # Add OSM id
|
||||||
elem_type = landmarktype # Add the landmark type as 'sightseeing
|
elem_type = landmarktype # Add the landmark type as 'sightseeing
|
||||||
@ -252,11 +226,11 @@ def get_landmarks_coords(coordinates: Tuple[float, float], l: List[Landmark], la
|
|||||||
|
|
||||||
# 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) + n_tags*100 )*CHURCH_PENALTY)
|
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*church_coeff)
|
||||||
elif amenity == "'leisure'='park'" :
|
elif amenity == "'leisure'='park'" :
|
||||||
score = int((count_elements_within_radius(location) + n_tags*100 )*PARK_COEFF)
|
score = int((count_elements_within_radius(location, radius) + n_tags*tag_coeff )*park_coeff)
|
||||||
else :
|
else :
|
||||||
score = count_elements_within_radius(location) + n_tags*100
|
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
|
||||||
@ -265,7 +239,15 @@ def get_landmarks_coords(coordinates: Tuple[float, float], l: List[Landmark], la
|
|||||||
|
|
||||||
return L
|
return L
|
||||||
|
|
||||||
def get_landmarks_nominatim(city_country: str, l: List[Landmark], landmarktype: LandmarkType)->List[Landmark] :
|
def get_landmarks_nominatim(city_country: str, list_amenity: list, landmarktype: LandmarkType) -> 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']
|
||||||
|
|
||||||
overpass = Overpass()
|
overpass = Overpass()
|
||||||
nominatim = Nominatim()
|
nominatim = Nominatim()
|
||||||
@ -275,7 +257,7 @@ def get_landmarks_nominatim(city_country: str, l: List[Landmark], landmarktype:
|
|||||||
N = 0
|
N = 0
|
||||||
L = []
|
L = []
|
||||||
|
|
||||||
for amenity in l :
|
for amenity in list_amenity :
|
||||||
query = overpassQueryBuilder(area=areaId, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
|
query = overpassQueryBuilder(area=areaId, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body')
|
||||||
result = overpass.query(query)
|
result = overpass.query(query)
|
||||||
N += result.countElements()
|
N += result.countElements()
|
||||||
@ -294,18 +276,96 @@ def get_landmarks_nominatim(city_country: str, l: List[Landmark], landmarktype:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
else :
|
else :
|
||||||
|
|
||||||
osm_type = elem.type() # Add type : 'way' or 'relation'
|
osm_type = elem.type() # Add type : 'way' or 'relation'
|
||||||
osm_id = elem.id() # Add OSM id
|
osm_id = elem.id() # Add OSM id
|
||||||
elem_type = landmarktype # Add the landmark type as 'sightseeing
|
elem_type = landmarktype # Add the landmark type as 'sightseeing
|
||||||
n_tags = len(elem.tags().keys()) # Add number of tags
|
n_tags = len(elem.tags().keys()) # Add number of tags
|
||||||
|
|
||||||
# Add score of given landmark based on the number of surrounding elements
|
# 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)*CHURCH_PENALTY)
|
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 :
|
else :
|
||||||
score = count_elements_within_radius(location)
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 :
|
if score is not None :
|
||||||
# Generate the landmark and append it to the list
|
# Generate the landmark and append it to the list
|
||||||
|
@ -12,50 +12,37 @@ app = FastAPI()
|
|||||||
|
|
||||||
# Assuming frontend is calling like this :
|
# Assuming frontend is calling like this :
|
||||||
#"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}"
|
#"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}"
|
||||||
@app.post("/optimizer_coords/{longitude}/{latitude}/{city_country}")
|
@app.post("/optimizer_coords/{latitude}/{longitude}/{city_country}")
|
||||||
def main1(preferences: Preferences = Body(...), longitude: float = None, latitude: float = None, city_country: str = None) -> List[Landmark]:
|
def main1(preferences: Preferences = Body(...), latitude: float = None, longitude: float = None, city_country: str = None) -> List[Landmark]:
|
||||||
|
|
||||||
|
if preferences is None :
|
||||||
|
raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.")
|
||||||
|
elif latitude is None and longitude is None and city_country is None :
|
||||||
|
raise ValueError("Please provide GPS coordinates or a 'city_country' string.")
|
||||||
|
elif latitude is not None and longitude is not None and city_country is not None :
|
||||||
|
raise ValueError("Please provide EITHER GPS coordinates or a 'city_country' string.")
|
||||||
|
|
||||||
|
|
||||||
# From frontend get longitude, latitude and prefence list
|
# From frontend get longitude, latitude and prefence list
|
||||||
|
if city_country is None :
|
||||||
# Generate the landmark list
|
coordinates = tuple((latitude, longitude))
|
||||||
landmarks = generate_landmarks(preferences=preferences, city_country=city_country, coordinates=tuple((longitude, latitude)))
|
|
||||||
|
|
||||||
# Set the max distance
|
|
||||||
max_steps = 90
|
|
||||||
|
|
||||||
# Compute the visiting order
|
|
||||||
visiting_order = solve_optimization(landmarks, max_steps, True)
|
|
||||||
|
|
||||||
return visiting_order
|
|
||||||
|
|
||||||
|
|
||||||
|
[], landmarks_short = generate_landmarks(preferences=preferences, city_country=city_country, coordinates=coordinates)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
@app.get("test")
|
landmarks_short.insert(0, start)
|
||||||
def test():
|
landmarks_short.append(finish)
|
||||||
|
|
||||||
# CONSTRAINT TO RESPECT MAX NUMBER OF STEPS
|
max_walking_time = 4 # hours
|
||||||
max_steps = 16
|
|
||||||
|
|
||||||
|
visiting_list = solve_optimization(landmarks_short, max_walking_time*60, True)
|
||||||
|
|
||||||
# Initialize all landmarks (+ start and goal). Order matters here
|
return visiting_list
|
||||||
landmarks = []
|
|
||||||
landmarks.append(LandmarkTest("départ", -1, (0, 0)))
|
|
||||||
landmarks.append(LandmarkTest("tour eiffel", 99, (0,2))) # PUT IN JSON
|
|
||||||
landmarks.append(LandmarkTest("arc de triomphe", 99, (0,4)))
|
|
||||||
landmarks.append(LandmarkTest("louvre", 99, (0,6)))
|
|
||||||
landmarks.append(LandmarkTest("montmartre", 99, (0,10)))
|
|
||||||
landmarks.append(LandmarkTest("concorde", 99, (0,8)))
|
|
||||||
landmarks.append(LandmarkTest("arrivée", -1, (0, 0)))
|
|
||||||
|
|
||||||
|
|
||||||
visiting_order = solve_optimization(landmarks, max_steps, True)
|
|
||||||
|
|
||||||
return visiting_order
|
|
||||||
|
|
||||||
# should return landmarks = the list of Landmark (ordered list)
|
|
||||||
#return("max steps :", max_steps, "\n", visiting_order)
|
|
||||||
|
|
||||||
|
|
||||||
# input city, country in the form of 'Paris, France'
|
# input city, country in the form of 'Paris, France'
|
||||||
@app.post("/test2/{city_country}")
|
@app.post("/test2/{city_country}")
|
||||||
|
@ -1,85 +1,17 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
import json, os
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Tuple
|
||||||
from typing import Tuple
|
|
||||||
from scipy.optimize import linprog
|
from scipy.optimize import linprog
|
||||||
from scipy.linalg import block_diag
|
|
||||||
from structs.landmarks import Landmark
|
|
||||||
from math import radians, sin, cos, acos
|
from math import radians, sin, cos, acos
|
||||||
|
|
||||||
|
from structs.landmarks import Landmark
|
||||||
|
|
||||||
|
|
||||||
DETOUR_FACTOR = 1.3 # detour factor for straightline distance
|
# Function to print the result
|
||||||
AVG_WALKING_SPEED = 4.8 # average walking speed in km/h
|
def print_res(L: List[Landmark], L_tot) -> list:
|
||||||
|
|
||||||
|
if len(L) == L_tot:
|
||||||
# Function that returns the distance in meters from one location to another
|
|
||||||
def get_distance(p1: Tuple[float, float], p2: Tuple[float, float]) :
|
|
||||||
|
|
||||||
# Compute the straight-line distance in km
|
|
||||||
if p1 == p2 :
|
|
||||||
return 0, 0
|
|
||||||
else:
|
|
||||||
dist = 6371.01 * acos(sin(radians(p1[0]))*sin(radians(p2[0])) + cos(radians(p1[0]))*cos(radians(p2[0]))*cos(radians(p1[1]) - radians(p2[1])))
|
|
||||||
|
|
||||||
# Consider the detour factor for average city
|
|
||||||
wdist = dist*DETOUR_FACTOR
|
|
||||||
|
|
||||||
# Time to walk this distance (in minutes)
|
|
||||||
wtime = wdist/AVG_WALKING_SPEED*60
|
|
||||||
|
|
||||||
if wtime > 15 :
|
|
||||||
wtime = 5*round(wtime/5)
|
|
||||||
else :
|
|
||||||
wtime = round(wtime)
|
|
||||||
|
|
||||||
|
|
||||||
return round(wdist, 1), wtime
|
|
||||||
|
|
||||||
|
|
||||||
# landmarks = [Landmark_1, Landmark_2, ...]
|
|
||||||
|
|
||||||
# Convert the solution of the optimization into the list of edges to follow. Order is taken into account
|
|
||||||
def untangle(resx: list) -> list:
|
|
||||||
N = len(resx) # length of res
|
|
||||||
L = int(np.sqrt(N)) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
|
|
||||||
n_edges = resx.sum() # number of edges
|
|
||||||
|
|
||||||
order = []
|
|
||||||
nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
|
|
||||||
|
|
||||||
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
|
||||||
|
|
||||||
indx = nonzero_tup[0].tolist()
|
|
||||||
indy = nonzero_tup[1].tolist()
|
|
||||||
|
|
||||||
vert = (indx[0], indy[0])
|
|
||||||
|
|
||||||
order.append(vert[0])
|
|
||||||
order.append(vert[1])
|
|
||||||
|
|
||||||
while len(order) < n_edges + 1 :
|
|
||||||
ind = indx.index(vert[1])
|
|
||||||
|
|
||||||
vert = (indx[ind], indy[ind])
|
|
||||||
|
|
||||||
order.append(vert[1])
|
|
||||||
|
|
||||||
return order
|
|
||||||
|
|
||||||
|
|
||||||
# Just to print the result
|
|
||||||
def print_res(L: List[Landmark], landmarks: List[Landmark], P) -> list:
|
|
||||||
|
|
||||||
"""N = int(np.sqrt(len(X)))
|
|
||||||
for i in range(N):
|
|
||||||
print(X[i*N:i*N+N])
|
|
||||||
print("Optimal value:", -res.fun) # Minimization, so we negate to get the maximum
|
|
||||||
print("Optimal point:", res.x)
|
|
||||||
for i,x in enumerate(X) : X[i] = round(x,0)
|
|
||||||
print(order)"""
|
|
||||||
|
|
||||||
if len(L) == len(landmarks):
|
|
||||||
print('\nAll landmarks can be visited within max_steps, the following order is suggested : ')
|
print('\nAll landmarks can be visited within max_steps, the following order is suggested : ')
|
||||||
else :
|
else :
|
||||||
print('Could not visit all the landmarks, the following order is suggested : ')
|
print('Could not visit all the landmarks, the following order is suggested : ')
|
||||||
@ -92,26 +24,20 @@ def print_res(L: List[Landmark], landmarks: List[Landmark], P) -> list:
|
|||||||
else :
|
else :
|
||||||
print('- ' + elem.name)
|
print('- ' + elem.name)
|
||||||
|
|
||||||
#steps = path_length(P, abs(res.x))
|
|
||||||
print("\nMinutes walked : " + str(dist))
|
print("\nMinutes walked : " + str(dist))
|
||||||
print(f"\nVisited {len(L)} out of {len(landmarks)} landmarks")
|
print(f"Visited {len(L)} out of {L_tot} landmarks")
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
|
# Prevent the use of a particular set of nodes
|
||||||
|
def prevent_config(resx, A_ub, b_ub) -> bool:
|
||||||
|
|
||||||
# prevent the creation of similar circles
|
|
||||||
def prevent_circle(resx, landmarks: List[Landmark], A_ub, b_ub) -> bool:
|
|
||||||
for i, elem in enumerate(resx):
|
for i, elem in enumerate(resx):
|
||||||
resx[i] = round(elem)
|
resx[i] = round(elem)
|
||||||
|
|
||||||
N = len(resx) # length of res
|
N = len(resx) # Number of edges
|
||||||
L = int(np.sqrt(N)) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
|
L = int(np.sqrt(N)) # Number of landmarks
|
||||||
n_edges = resx.sum() # number of edges
|
|
||||||
|
|
||||||
|
|
||||||
nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
|
|
||||||
|
|
||||||
|
nonzeroind = np.nonzero(resx)[0] # the return is a little funky so I use the [0]
|
||||||
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
||||||
|
|
||||||
ind_a = nonzero_tup[0].tolist()
|
ind_a = nonzero_tup[0].tolist()
|
||||||
@ -130,19 +56,16 @@ def prevent_circle(resx, landmarks: List[Landmark], A_ub, b_ub) -> bool:
|
|||||||
return A_ub, b_ub
|
return A_ub, b_ub
|
||||||
|
|
||||||
|
|
||||||
def break_circle2(circle_vertices, landmarks: List[Landmark], A_ub, b_ub) -> bool:
|
# Prevent the possibility of a given set of vertices
|
||||||
|
def break_cricle(circle_vertices: list, L: int, A_ub: list, b_ub: list) -> bool:
|
||||||
L = len(landmarks) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
|
|
||||||
|
|
||||||
|
|
||||||
if L-1 in circle_vertices :
|
if L-1 in circle_vertices :
|
||||||
circle_vertices.remove(L-1)
|
circle_vertices.remove(L-1)
|
||||||
|
|
||||||
ones = [1]*L
|
|
||||||
h = [0]*L*L
|
h = [0]*L*L
|
||||||
for i in range(L) :
|
for i in range(L) :
|
||||||
if i in circle_vertices :
|
if i in circle_vertices :
|
||||||
h[i*L:i*L+L] = ones
|
h[i*L:i*L+L] = [1]*L
|
||||||
|
|
||||||
A_ub = np.vstack((A_ub, h))
|
A_ub = np.vstack((A_ub, h))
|
||||||
b_ub.append(len(circle_vertices)-1)
|
b_ub.append(len(circle_vertices)-1)
|
||||||
@ -150,8 +73,10 @@ def break_circle2(circle_vertices, landmarks: List[Landmark], A_ub, b_ub) -> boo
|
|||||||
return A_ub, b_ub
|
return A_ub, b_ub
|
||||||
|
|
||||||
|
|
||||||
# Checks if the path is connected
|
# Checks if the path is connected, returns a circle if it finds one
|
||||||
def is_connected(resx, landmarks: List[Landmark]) -> bool:
|
def is_connected(resx) -> bool:
|
||||||
|
|
||||||
|
# first round the results to have only 0-1 values
|
||||||
for i, elem in enumerate(resx):
|
for i, elem in enumerate(resx):
|
||||||
resx[i] = round(elem)
|
resx[i] = round(elem)
|
||||||
|
|
||||||
@ -159,7 +84,6 @@ def is_connected(resx, landmarks: List[Landmark]) -> bool:
|
|||||||
L = int(np.sqrt(N)) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
|
L = int(np.sqrt(N)) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
|
||||||
n_edges = resx.sum() # number of edges
|
n_edges = resx.sum() # number of edges
|
||||||
|
|
||||||
|
|
||||||
nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
|
nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
|
||||||
|
|
||||||
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
||||||
@ -178,12 +102,9 @@ def is_connected(resx, landmarks: List[Landmark]) -> bool:
|
|||||||
for i, a in enumerate(ind_a) :
|
for i, a in enumerate(ind_a) :
|
||||||
edges.append((a, ind_b[i])) # Create the list of edges
|
edges.append((a, ind_b[i])) # Create the list of edges
|
||||||
|
|
||||||
flag = False
|
|
||||||
|
|
||||||
remaining = edges
|
remaining = edges
|
||||||
remaining.remove(edge1)
|
remaining.remove(edge1)
|
||||||
# This can be further optimized
|
|
||||||
#while len(vertices_visited) < n_edges + 1 :
|
|
||||||
break_flag = False
|
break_flag = False
|
||||||
while len(remaining) > 0 and not break_flag:
|
while len(remaining) > 0 and not break_flag:
|
||||||
for edge2 in remaining :
|
for edge2 in remaining :
|
||||||
@ -192,7 +113,6 @@ def is_connected(resx, landmarks: List[Landmark]) -> bool:
|
|||||||
edges_visited.append(edge2)
|
edges_visited.append(edge2)
|
||||||
break_flag = True
|
break_flag = True
|
||||||
break
|
break
|
||||||
#continue # continue vs break vs needed at all ?
|
|
||||||
else :
|
else :
|
||||||
vertices_visited.append(edge1[1])
|
vertices_visited.append(edge1[1])
|
||||||
edges_visited.append(edge2)
|
edges_visited.append(edge2)
|
||||||
@ -202,171 +122,131 @@ def is_connected(resx, landmarks: List[Landmark]) -> bool:
|
|||||||
elif edge1[1] == L-1 or edge1[1] in vertices_visited:
|
elif edge1[1] == L-1 or edge1[1] in vertices_visited:
|
||||||
break_flag = True
|
break_flag = True
|
||||||
break
|
break
|
||||||
#break
|
|
||||||
#if flag is True :
|
|
||||||
# break
|
|
||||||
vertices_visited.append(edge1[1])
|
vertices_visited.append(edge1[1])
|
||||||
|
|
||||||
|
|
||||||
if len(vertices_visited) == n_edges +1 :
|
if len(vertices_visited) == n_edges +1 :
|
||||||
flag = True
|
return vertices_visited, []
|
||||||
circle = []
|
|
||||||
else:
|
else:
|
||||||
flag = False
|
return vertices_visited, edges_visited
|
||||||
circle = edges_visited
|
|
||||||
|
|
||||||
"""j = 0
|
|
||||||
for i in vertices_visited :
|
|
||||||
if landmarks[i].name == 'start' :
|
|
||||||
ordered_visit = vertices_visited[j:] + vertices_visited[:j]
|
|
||||||
break
|
|
||||||
j+=1"""
|
|
||||||
|
|
||||||
|
|
||||||
return flag, vertices_visited, circle
|
# Function that returns the distance in meters from one location to another
|
||||||
|
def get_distance(p1: Tuple[float, float], p2: Tuple[float, float], detour: float, speed: float) :
|
||||||
|
|
||||||
|
# Compute the straight-line distance in km
|
||||||
|
if p1 == p2 :
|
||||||
|
return 0, 0
|
||||||
|
else:
|
||||||
|
dist = 6371.01 * acos(sin(radians(p1[0]))*sin(radians(p2[0])) + cos(radians(p1[0]))*cos(radians(p2[0]))*cos(radians(p1[1]) - radians(p2[1])))
|
||||||
|
|
||||||
|
# Consider the detour factor for average city
|
||||||
|
wdist = dist*detour
|
||||||
|
|
||||||
|
# Time to walk this distance (in minutes)
|
||||||
|
wtime = wdist/speed*60
|
||||||
|
|
||||||
|
if wtime > 15 :
|
||||||
|
wtime = 5*round(wtime/5)
|
||||||
|
else :
|
||||||
|
wtime = round(wtime)
|
||||||
|
|
||||||
|
|
||||||
|
return round(wdist, 1), wtime
|
||||||
|
|
||||||
|
|
||||||
# Checks for cases of circular symmetry in the result
|
# Initialize A and c. Compute the distances from all landmarks to each other and store attractiveness
|
||||||
def has_circle(resx: list) :
|
# We want to maximize the sightseeing : max(c) st. A*x < b and A_eq*x = b_eq
|
||||||
N = len(resx) # length of res
|
def init_ub_dist(landmarks: List[Landmark], max_steps: int):
|
||||||
L = int(np.sqrt(N)) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
|
|
||||||
n_edges = resx.sum() # number of edges
|
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
|
# Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
|
||||||
|
c = []
|
||||||
|
# Coefficients of inequality constraints (left-hand side)
|
||||||
|
A_ub = []
|
||||||
|
|
||||||
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
for spot1 in landmarks :
|
||||||
|
dist_table = [0]*len(landmarks)
|
||||||
|
c.append(-spot1.attractiveness)
|
||||||
|
for j, spot2 in enumerate(landmarks) :
|
||||||
|
t = get_distance(spot1.location, spot2.location, detour, speed)[1]
|
||||||
|
dist_table[j] = t
|
||||||
|
A_ub += dist_table
|
||||||
|
c = c*len(landmarks)
|
||||||
|
|
||||||
indx = nonzero_tup[0].tolist()
|
return c, A_ub, [max_steps]
|
||||||
indy = nonzero_tup[1].tolist()
|
|
||||||
|
|
||||||
|
|
||||||
verts = []
|
|
||||||
|
|
||||||
for i, x in enumerate(indx) :
|
|
||||||
verts.append((x, indy[i]))
|
|
||||||
|
|
||||||
|
|
||||||
for vert in verts :
|
|
||||||
visited = []
|
|
||||||
visited.append(vert)
|
|
||||||
|
|
||||||
while len(visited) < n_edges + 1 :
|
|
||||||
|
|
||||||
try :
|
|
||||||
ind = indx.index(vert[1])
|
|
||||||
|
|
||||||
vert = (indx[ind], indy[ind])
|
|
||||||
|
|
||||||
if vert in visited :
|
|
||||||
return visited
|
|
||||||
else :
|
|
||||||
visited.append(vert)
|
|
||||||
except :
|
|
||||||
break
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Constraint to not have d14 and d41 simultaneously. Does not prevent circular symmetry with more elements
|
|
||||||
def break_sym(N, A_ub, b_ub):
|
|
||||||
upper_ind = np.triu_indices(N,0,N)
|
|
||||||
|
|
||||||
up_ind_x = upper_ind[0]
|
|
||||||
up_ind_y = upper_ind[1]
|
|
||||||
|
|
||||||
for i, _ in enumerate(up_ind_x) :
|
|
||||||
l = [0]*N*N
|
|
||||||
if up_ind_x[i] != up_ind_y[i] :
|
|
||||||
l[up_ind_x[i]*N + up_ind_y[i]] = 1
|
|
||||||
l[up_ind_y[i]*N + up_ind_x[i]] = 1
|
|
||||||
|
|
||||||
A_ub = np.vstack((A_ub,l))
|
|
||||||
b_ub.append(1)
|
|
||||||
|
|
||||||
"""for i in range(7):
|
|
||||||
print(l[i*7:i*7+7])
|
|
||||||
print("\n")"""
|
|
||||||
|
|
||||||
return A_ub, b_ub
|
|
||||||
|
|
||||||
# Constraint to not have circular paths. Want to go from start -> finish without unconnected loops
|
|
||||||
def break_circle(L, A_ub, b_ub, circle) :
|
|
||||||
l = [0]*L*L
|
|
||||||
|
|
||||||
for index in circle :
|
|
||||||
x = index[0]
|
|
||||||
y = index[1]
|
|
||||||
l[x*L+y] = 1
|
|
||||||
|
|
||||||
A_ub = np.vstack((A_ub,l))
|
|
||||||
b_ub.append(len(circle)-1)
|
|
||||||
|
|
||||||
"""print("\n\nPREVENT CIRCLE")
|
|
||||||
for i in range(7):
|
|
||||||
print(l[i*7:i*7+7])
|
|
||||||
print("\n")"""
|
|
||||||
|
|
||||||
return A_ub, b_ub
|
|
||||||
|
|
||||||
# Constraint to respect max number of travels
|
# Constraint to respect max number of travels
|
||||||
def respect_number(N, A_ub, b_ub):
|
def respect_number(L, A_ub, b_ub):
|
||||||
"""h = []
|
|
||||||
for i in range(N) : h.append([1]*N)
|
|
||||||
T = block_diag(*h)
|
|
||||||
for l in T :
|
|
||||||
for i in range(7):
|
|
||||||
print(l[i*7:i*7+7])
|
|
||||||
print("\n")"""
|
|
||||||
#return np.vstack((A_ub, T)), b_ub + [1]*N
|
|
||||||
ones = [1]*N
|
|
||||||
zeros = [0]*N
|
|
||||||
for i in range(N) :
|
|
||||||
h = zeros*i + ones + zeros*(N-1-i)
|
|
||||||
|
|
||||||
|
ones = [1]*L
|
||||||
|
zeros = [0]*L
|
||||||
|
for i in range(L) :
|
||||||
|
h = zeros*i + ones + zeros*(L-1-i)
|
||||||
A_ub = np.vstack((A_ub, h))
|
A_ub = np.vstack((A_ub, h))
|
||||||
b_ub.append(1)
|
b_ub.append(1)
|
||||||
|
|
||||||
return A_ub, b_ub
|
return A_ub, b_ub
|
||||||
|
|
||||||
|
|
||||||
# Constraint to tie the problem together. Necessary but not sufficient to avoid circles
|
# Constraint to not have d14 and d41 simultaneously. Does not prevent circular symmetry with more elements
|
||||||
def respect_order(N: int, A_eq, b_eq):
|
def break_sym(L, A_ub, b_ub):
|
||||||
for i in range(N-1) : # Prevent stacked ones
|
upper_ind = np.triu_indices(L,0,L)
|
||||||
if i == 0 or i == N-1: # Don't touch start or finish
|
|
||||||
continue
|
|
||||||
else :
|
|
||||||
l = [0]*N
|
|
||||||
l[i] = -1
|
|
||||||
l = l*N
|
|
||||||
for j in range(N) :
|
|
||||||
l[i*N + j] = 1
|
|
||||||
|
|
||||||
A_eq = np.vstack((A_eq,l))
|
up_ind_x = upper_ind[0]
|
||||||
b_eq.append(0)
|
up_ind_y = upper_ind[1]
|
||||||
|
|
||||||
return A_eq, b_eq
|
for i, _ in enumerate(up_ind_x) :
|
||||||
|
l = [0]*L*L
|
||||||
|
if up_ind_x[i] != up_ind_y[i] :
|
||||||
|
l[up_ind_x[i]*L + up_ind_y[i]] = 1
|
||||||
|
l[up_ind_y[i]*L + up_ind_x[i]] = 1
|
||||||
|
|
||||||
|
A_ub = np.vstack((A_ub,l))
|
||||||
|
b_ub.append(1)
|
||||||
|
|
||||||
|
return A_ub, b_ub
|
||||||
|
|
||||||
# Compute manhattan distance between 2 locations
|
|
||||||
def manhattan_distance(loc1: tuple, loc2: tuple):
|
|
||||||
x1, y1 = loc1
|
|
||||||
x2, y2 = loc2
|
|
||||||
return abs(x1 - x2) + abs(y1 - y2)
|
|
||||||
|
|
||||||
# Constraint to not stay in position. Removes d11, d22, d33, etc.
|
# Constraint to not stay in position. Removes d11, d22, d33, etc.
|
||||||
def init_eq_not_stay(N: int):
|
def init_eq_not_stay(L: int):
|
||||||
l = [0]*N*N
|
l = [0]*L*L
|
||||||
|
|
||||||
for i in range(N) :
|
for i in range(L) :
|
||||||
for j in range(N) :
|
for j in range(L) :
|
||||||
if j == i :
|
if j == i :
|
||||||
l[j + i*N] = 1
|
l[j + i*L] = 1
|
||||||
|
|
||||||
l = np.array(np.array(l))
|
l = np.array(np.array(l))
|
||||||
|
|
||||||
return [l], [0]
|
return [l], [0]
|
||||||
|
|
||||||
|
|
||||||
|
# Go through the landmarks and force the optimizer to use landmarks where attractiveness is set to -1
|
||||||
|
def respect_user_mustsee(landmarks: List[Landmark], A_eq: list, b_eq: list) :
|
||||||
|
L = len(landmarks)
|
||||||
|
|
||||||
|
for i, elem in enumerate(landmarks) :
|
||||||
|
if elem.must_do is True and elem.name not in ['finish', 'start']:
|
||||||
|
l = [0]*L*L
|
||||||
|
for j in range(L) : # sets the horizontal ones (go from)
|
||||||
|
l[j +i*L] = 1 # sets the vertical ones (go to) double check if good
|
||||||
|
|
||||||
|
for k in range(L-1) :
|
||||||
|
l[k*L+L-1] = 1
|
||||||
|
|
||||||
|
A_eq = np.vstack((A_eq,l))
|
||||||
|
b_eq.append(2)
|
||||||
|
|
||||||
|
return A_eq, b_eq
|
||||||
|
|
||||||
|
|
||||||
# Constraint to ensure start at start and finish at goal
|
# Constraint to ensure start at start and finish at goal
|
||||||
def respect_start_finish(L: int, A_eq: list, b_eq: list):
|
def respect_start_finish(L: int, A_eq: list, b_eq: list):
|
||||||
ls = [1]*L + [0]*L*(L-1) # sets only horizontal ones for start (go from)
|
ls = [1]*L + [0]*L*(L-1) # sets only horizontal ones for start (go from)
|
||||||
@ -391,144 +271,101 @@ def respect_start_finish(L: int, A_eq: list, b_eq: list):
|
|||||||
|
|
||||||
return A_eq, b_eq
|
return A_eq, b_eq
|
||||||
|
|
||||||
# Initialize A and c. Compute the distances from all landmarks to each other and store attractiveness
|
|
||||||
# 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):
|
|
||||||
# Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
|
|
||||||
c = []
|
|
||||||
# Coefficients of inequality constraints (left-hand side)
|
|
||||||
A_ub = []
|
|
||||||
for i, spot1 in enumerate(landmarks) :
|
|
||||||
dist_table = [0]*len(landmarks)
|
|
||||||
c.append(-spot1.attractiveness)
|
|
||||||
for j, spot2 in enumerate(landmarks) :
|
|
||||||
d, t = get_distance(spot1.location, spot2.location)
|
|
||||||
dist_table[j] = t
|
|
||||||
A_ub += dist_table
|
|
||||||
c = c*len(landmarks)
|
|
||||||
"""A_ub = []
|
|
||||||
for line in A :
|
|
||||||
#print(line)
|
|
||||||
A_ub += line"""
|
|
||||||
return c, A_ub, [max_steps]
|
|
||||||
|
|
||||||
# Go through the landmarks and force the optimizer to use landmarks where attractiveness is set to -1
|
|
||||||
def respect_user_mustsee(landmarks: List[Landmark], A_eq: list, b_eq: list) :
|
|
||||||
L = len(landmarks)
|
|
||||||
H = 0 # sort of heuristic to get an idea of the number of steps needed
|
|
||||||
|
|
||||||
elem_prev = landmarks[0]
|
|
||||||
|
|
||||||
for i, elem in enumerate(landmarks) :
|
|
||||||
if elem.must_do is True and elem.name not in ['finish', 'start']:
|
|
||||||
l = [0]*L*L
|
|
||||||
for j in range(L) : # sets the horizontal ones (go from)
|
|
||||||
l[j +i*L] = 1 # sets the vertical ones (go to) double check if good
|
|
||||||
|
|
||||||
for k in range(L-1) :
|
|
||||||
l[k*L+L-1] = 1
|
|
||||||
|
|
||||||
|
# Constraint to tie the problem together. Necessary but not sufficient to avoid circles
|
||||||
|
def respect_order(N: int, A_eq, b_eq):
|
||||||
|
for i in range(N-1) : # Prevent stacked ones
|
||||||
|
if i == 0 or i == N-1: # Don't touch start or finish
|
||||||
|
continue
|
||||||
|
else :
|
||||||
|
l = [0]*N
|
||||||
|
l[i] = -1
|
||||||
|
l = l*N
|
||||||
|
for j in range(N) :
|
||||||
|
l[i*N + j] = 1
|
||||||
|
|
||||||
A_eq = np.vstack((A_eq,l))
|
A_eq = np.vstack((A_eq,l))
|
||||||
b_eq.append(2)
|
b_eq.append(0)
|
||||||
|
|
||||||
d, t = get_distance(elem.location, elem_prev.location)
|
return A_eq, b_eq
|
||||||
H += t
|
|
||||||
elem_prev = elem
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return A_eq, b_eq, H
|
|
||||||
|
|
||||||
# Computes the path length given path matrix (dist_table) and a result
|
# Computes the path length given path matrix (dist_table) and a result
|
||||||
def path_length(P: list, resx: list) :
|
def add_time_to_reach(order: List[Landmark], landmarks: List[Landmark])->List[Landmark] :
|
||||||
return np.dot(P, resx)
|
|
||||||
|
j = 0
|
||||||
|
L = []
|
||||||
|
|
||||||
|
# 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 = parameters['detour factor']
|
||||||
|
speed = parameters['average walking speed']
|
||||||
|
|
||||||
|
prev = landmarks[0]
|
||||||
|
while(len(L) != len(order)) :
|
||||||
|
|
||||||
|
elem = landmarks[order[j]]
|
||||||
|
if elem != prev :
|
||||||
|
elem.time_to_reach = get_distance(elem.location, prev.location, detour, speed)[1]
|
||||||
|
L.append(elem)
|
||||||
|
prev = elem
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
return L
|
||||||
|
|
||||||
|
|
||||||
# Main optimization pipeline
|
# Main optimization pipeline
|
||||||
def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_details: bool) :
|
def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_details: bool) :
|
||||||
|
|
||||||
N = len(landmarks)
|
L = len(landmarks)
|
||||||
|
|
||||||
# SET CONSTRAINTS FOR INEQUALITY
|
# SET CONSTRAINTS FOR INEQUALITY
|
||||||
c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other
|
c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other
|
||||||
P = A_ub # store the paths for later. Needed to compute path length
|
A_ub, b_ub = respect_number(L, A_ub, b_ub) # Respect max number of visits (no more possible stops than landmarks).
|
||||||
A_ub, b_ub = respect_number(N, A_ub, b_ub) # Respect max number of visits (no more possible stops than landmarks).
|
A_ub, b_ub = break_sym(L, A_ub, b_ub) # break the 'zig-zag' symmetry
|
||||||
|
|
||||||
# TODO : Problems with circular symmetry
|
|
||||||
A_ub, b_ub = break_sym(N, A_ub, b_ub) # break the symmetry. Only use the upper diagonal values
|
|
||||||
|
|
||||||
# SET CONSTRAINTS FOR EQUALITY
|
# SET CONSTRAINTS FOR EQUALITY
|
||||||
A_eq, b_eq = init_eq_not_stay(N) # Force solution not to stay in same place
|
A_eq, b_eq = init_eq_not_stay(L) # Force solution not to stay in same place
|
||||||
A_eq, b_eq, H = respect_user_mustsee(landmarks, A_eq, b_eq) # Check if there are user_defined must_see. Also takes care of start/goal
|
A_eq, b_eq = respect_user_mustsee(landmarks, A_eq, b_eq) # Check if there are user_defined must_see. Also takes care of start/goal
|
||||||
A_eq, b_eq = respect_start_finish(N, A_eq, b_eq) # Force start and finish positions
|
A_eq, b_eq = respect_start_finish(L, A_eq, b_eq) # Force start and finish positions
|
||||||
A_eq, b_eq = respect_order(N, A_eq, b_eq) # Respect order of visit (only works when max_steps is limiting factor)
|
A_eq, b_eq = respect_order(L, A_eq, b_eq) # Respect order of visit (only works when max_steps is limiting factor)
|
||||||
|
|
||||||
# Bounds for variables (x can only be 0 or 1)
|
# SET BOUNDS FOR DECISION VARIABLE (x can only be 0 or 1)
|
||||||
x_bounds = [(0, 1)] * len(c)
|
x_bounds = [(0, 1)]*L*L
|
||||||
|
|
||||||
# Solve linear programming problem
|
# Solve linear programming problem
|
||||||
|
|
||||||
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
|
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Raise error if no solution is found
|
# Raise error if no solution is found
|
||||||
if not res.success :
|
if not res.success :
|
||||||
|
raise ArithmeticError("No solution could be found, the problem is overconstrained. Please adapt your must_dos")
|
||||||
|
|
||||||
# Override the max_steps using the heuristic
|
# If there is a solution, we're good to go, just check for connectiveness
|
||||||
for i, val in enumerate(b_ub) :
|
|
||||||
if val == max_steps : b_ub[i] = H
|
|
||||||
|
|
||||||
# Solve problem again :
|
|
||||||
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
|
|
||||||
|
|
||||||
if not res.success :
|
|
||||||
s = "No solution could be found, even when increasing max_steps using the heuristic"
|
|
||||||
return s
|
|
||||||
#raise ValueError("No solution could be found, even when increasing max_steps using the heuristic")
|
|
||||||
|
|
||||||
# If there is a solution, we're good to go, just check for
|
|
||||||
else :
|
else :
|
||||||
t, order, circle = is_connected(res.x, landmarks)
|
order, circle = is_connected(res.x)
|
||||||
i = 0
|
i = 0
|
||||||
|
timeout = 300
|
||||||
# Break the circular symmetry if needed
|
while len(circle) != 0 and i < timeout:
|
||||||
while len(circle) != 0 :
|
A_ub, b_ub = prevent_config(res.x, A_ub, b_ub)
|
||||||
A_ub, b_ub = prevent_circle(res.x, landmarks, A_ub, b_ub)
|
A_ub, b_ub = break_cricle(order, len(landmarks), A_ub, b_ub)
|
||||||
A_ub, b_ub = break_circle(len(landmarks), A_ub, b_ub, circle)
|
|
||||||
A_ub, b_ub = break_circle2(order, landmarks, A_ub, b_ub)
|
|
||||||
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
|
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
|
||||||
t, order, circle = is_connected(res.x, landmarks)
|
order, circle = is_connected(res.x)
|
||||||
if t :
|
if len(circle) == 0 :
|
||||||
|
# Add the times to reach and stop optimizing
|
||||||
|
L = add_time_to_reach(order, landmarks)
|
||||||
break
|
break
|
||||||
#circle = has_circle(res.x)
|
|
||||||
print(i)
|
print(i)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
t, order, [] = is_connected(res.x, landmarks)
|
if i == timeout :
|
||||||
|
raise TimeoutError(f"Optimization took too long. No solution found after {timeout} iterations.")
|
||||||
|
|
||||||
|
|
||||||
prev = landmarks[order[0]]
|
|
||||||
i = 0
|
|
||||||
L = []
|
|
||||||
#prev = landmarks[order[i]]
|
|
||||||
while(len(L) != len(order)) :
|
|
||||||
elem = landmarks[order[i]]
|
|
||||||
if elem != prev :
|
|
||||||
d, t = get_distance(elem.location, prev.location)
|
|
||||||
elem.time_to_reach = t
|
|
||||||
L.append(elem)
|
|
||||||
prev = elem
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
if printing_details is True :
|
if printing_details is True :
|
||||||
if i != 0 :
|
if i != 0 :
|
||||||
print(f"Neded to recompute paths {i} times because of unconnected loops...")
|
print(f"Neded to recompute paths {i} times because of unconnected loops...")
|
||||||
|
print_res(L, len(landmarks))
|
||||||
print_res(L, landmarks, P)
|
print("\nTotal score : " + str(int(-res.fun)))
|
||||||
|
|
||||||
print(np.dot(P, res.x))
|
|
||||||
|
|
||||||
return L
|
return L
|
||||||
|
|
||||||
|
8
backend/src/parameters/landmarks_manager.params
Normal file
8
backend/src/parameters/landmarks_manager.params
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"city bbox side" : 10,
|
||||||
|
"radius close to" : 27.5,
|
||||||
|
"church coeff" : 0.6,
|
||||||
|
"park coeff" : 1.4,
|
||||||
|
"tag coeff" : 100,
|
||||||
|
"N important" : 30
|
||||||
|
}
|
4
backend/src/parameters/optimizer.params
Normal file
4
backend/src/parameters/optimizer.params
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"detour factor" : 10,
|
||||||
|
"average walking speed" : 27.5
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from OSMPythonTools.api import Api
|
|
||||||
from .landmarktype import LandmarkType
|
|
||||||
from .preferences import Preferences
|
|
||||||
|
|
||||||
class LandmarkTest(BaseModel) :
|
from .landmarktype import LandmarkType
|
||||||
name : str
|
|
||||||
attractiveness : int
|
|
||||||
loc : tuple
|
|
||||||
|
|
||||||
# Output to frontend
|
# Output to frontend
|
||||||
class Landmark(BaseModel) :
|
class Landmark(BaseModel) :
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from optimizer import solve_optimization
|
|
||||||
|
from typing import List
|
||||||
from landmarks_manager import generate_landmarks
|
from landmarks_manager import generate_landmarks
|
||||||
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
|
||||||
|
from optimizer import solve_optimization
|
||||||
from structs.landmarks import Landmark
|
from structs.landmarks import Landmark
|
||||||
from structs.landmarktype import LandmarkType
|
from structs.landmarktype import LandmarkType
|
||||||
from structs.preferences import Preferences, Preference
|
from structs.preferences import Preferences, Preference
|
||||||
from fastapi.encoders import jsonable_encoder
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
# Helper function to create a .txt file with results
|
# Helper function to create a .txt file with results
|
||||||
@ -37,17 +39,24 @@ def test3(city_country: str) -> List[Landmark]:
|
|||||||
type=LandmarkType(landmark_type='shopping'),
|
type=LandmarkType(landmark_type='shopping'),
|
||||||
score = 5))
|
score = 5))
|
||||||
|
|
||||||
coords = None
|
coordinates = None
|
||||||
|
|
||||||
landmarks = generate_landmarks(preferences=preferences, city_country=city_country, coordinates=coords)
|
landmarks, landmarks_short = generate_landmarks(preferences=preferences, city_country=city_country, coordinates=coordinates)
|
||||||
|
|
||||||
max_steps = 9
|
#write_data(landmarks)
|
||||||
|
|
||||||
visiting_order = solve_optimization(landmarks, max_steps, True)
|
start = Landmark(name='start', type=LandmarkType(landmark_type='start'), location=(48.2044576, 16.3870242), 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.2044576, 16.3870242), osm_type='finish', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
|
||||||
|
|
||||||
print(len(visiting_order))
|
|
||||||
|
|
||||||
return len(visiting_order)
|
test = landmarks_short
|
||||||
|
|
||||||
|
test.insert(0, start)
|
||||||
|
test.append(finish)
|
||||||
|
|
||||||
|
max_walking_time = 2 # hours
|
||||||
|
|
||||||
|
visiting_list = solve_optimization(test, max_walking_time*60, True)
|
||||||
|
|
||||||
|
|
||||||
def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
||||||
@ -77,7 +86,6 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
|||||||
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)
|
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)
|
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)
|
||||||
|
|
||||||
|
|
||||||
test = landmarks_short
|
test = landmarks_short
|
||||||
|
|
||||||
test.insert(0, start)
|
test.insert(0, start)
|
||||||
@ -92,3 +100,4 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
|||||||
|
|
||||||
|
|
||||||
test4(tuple((48.8795156, 2.3660204)))
|
test4(tuple((48.8795156, 2.3660204)))
|
||||||
|
#test3('Vienna, Austria')
|
Loading…
x
Reference in New Issue
Block a user