fixed input as coordinates
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | cache/ | ||||||
| @@ -2,77 +2,190 @@ from OSMPythonTools.api import Api | |||||||
| from OSMPythonTools.overpass import Overpass, overpassQueryBuilder, Nominatim | from OSMPythonTools.overpass import Overpass, overpassQueryBuilder, Nominatim | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel | ||||||
|  | import math as m | ||||||
|  | from structs.landmarks import Landmark, LandmarkType | ||||||
|  | from structs.preferences import Preferences, Preference | ||||||
|  | from typing import List | ||||||
|  | from typing import Tuple | ||||||
|  |  | ||||||
|  | RADIUS = 0.0005             # size of the bbox in degrees. 0.0005 ~ 50m | ||||||
|  | BBOX_SIDE = 10              # size of bbox in km for general area, 10km | ||||||
|  | RADIUS_CLOSE_TO = 50       # size of area in m for close features, 5àm radius | ||||||
|  | MIN_SCORE = 100             # discard elements with score < 100 | ||||||
|  | MIN_TAGS = 5                # discard elements withs less than 5 tags | ||||||
|  |  | ||||||
|  |  | ||||||
| # Defines the landmark class (aka some place there is to visit) | # Include th json here | ||||||
| @dataclass | # Create a list of all things to visit given some preferences and a city. Ready for the optimizer | ||||||
| class Landmarkkkk : | def generate_landmarks(coordinates: Tuple[float, float], preferences: Preferences) : | ||||||
|     name : str |  | ||||||
|     attractiveness : int |  | ||||||
|     id : int |  | ||||||
|  |  | ||||||
| class Landmark(BaseModel) : |     l_sights = ["'tourism'='museum'", "'tourism'='attraction'", "'tourism'='gallery'", 'historic', "'amenity'='arts_centre'", "'amenity'='planetarium'", "'amenity'='place_of_worship'", "'amenity'='fountain'", '"water"="reflecting_pool"']  | ||||||
|     name : str |     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"']  | ||||||
|     attractiveness : int |     l_shop = ["'shop'='department_store'", "'shop'='mall'"] #, '"shop"="collector"', '"shop"="antiques"']  | ||||||
|     loc : tuple |  | ||||||
|    |    | ||||||
| # Converts a OSM id to a landmark |     # List for sightseeing | ||||||
| def add_from_id(id: int, score: int) : |     L1 = get_landmarks(coordinates, l_sights, LandmarkType(landmark_type='sightseeing')) | ||||||
|  |     correct_score(L1, preferences.sightseeing) | ||||||
|      |      | ||||||
|     try : |     # List for nature | ||||||
|         s = 'way/' + str(id)           # prepare string for query |     L2 = get_landmarks(coordinates, l_nature, LandmarkType(landmark_type='nature')) | ||||||
|         obj =  api.query(s)                             # object to add |     correct_score(L2, preferences.nature) | ||||||
|     except : |  | ||||||
|         s = 'relation/' + str(id)           # prepare string for query |  | ||||||
|         obj =  api.query(s)                             # object to add |  | ||||||
|      |      | ||||||
|     return Landmarkkkk(obj.tag('name:fr'), score, id)      # create Landmark out of it |     # List for shopping | ||||||
|  |     L3 = get_landmarks(coordinates, l_shop, LandmarkType(landmark_type='shopping')) | ||||||
|  |     correct_score(L3, preferences.shopping) | ||||||
|  |  | ||||||
|  |     L = L1 + L2 + L3 | ||||||
|  |  | ||||||
|  |     return cleanup_list(L) | ||||||
|  |  | ||||||
|  | # Determines if two locations are close to each other | ||||||
|  | def is_close_to(loc1: Tuple[float, float], loc2: Tuple[float, float]) : | ||||||
|  |  | ||||||
|  |     alpha = (180*RADIUS_CLOSE_TO)/(6371000*m.pi) | ||||||
|  |     if abs(loc1[0] - loc2[0]) + abs(loc1[1] - loc2[1]) < alpha*2 : | ||||||
|  |         return True | ||||||
|  |     else :  | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  | # Remove duplicate elements and elements with low score | ||||||
|  | def cleanup_list(L: List[Landmark]) : | ||||||
|  |     L_clean = [] | ||||||
|  |     names = [] | ||||||
|  |  | ||||||
|  |     for landmark in L : | ||||||
|  |  | ||||||
|  |         if landmark.name in names :                 # Remove duplicates | ||||||
|  |             continue      | ||||||
|  |  | ||||||
|  |         elif landmark.attractiveness < MIN_SCORE :  # Remove uninteresting | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         elif landmark.n_tags < MIN_TAGS :           # Remove uninteresting 2.0 | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         else : | ||||||
|  |             names.append(landmark.name) | ||||||
|  |             L_clean.append(landmark) | ||||||
|  |      | ||||||
|  |     return L_clean | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_sights(city_country: str): | # Correct the score of a list of landmarks by taking into account preferences and the number of tags | ||||||
|     nominatim = Nominatim() | def correct_score(L: List[Landmark], preference: Preference) : | ||||||
|     areaId = nominatim.query(city_country).areaId() |  | ||||||
|  |     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/100) + elem.n_tags      # arbitrary correction of the balance score vs number of tags | ||||||
|  |         elem.attractiveness = elem.attractiveness*preference.score        # 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 : | ||||||
|  |         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/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] | ||||||
|  |     lon = coordinates[1] | ||||||
|  |  | ||||||
|  |     bbox = {'latLower':lat-RADIUS,'lonLower':lon-RADIUS,'latHigher':lat+RADIUS,'lonHigher': lon+RADIUS} | ||||||
|     overpass = Overpass() |     overpass = Overpass() | ||||||
|      |      | ||||||
|     # list of stuff we want to define as sights |     # Build the query to find elements within the radius | ||||||
|     l = ["'tourism'='museum'", "'tourism'='attraction'", "'tourism'='gallery'", 'historic', "'amenity'='arts_centre'", "'amenity'='planetarium'", '"amenity"="place_of_worship"']  |     radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']], | ||||||
|     score = 0 |                              elementType=['node', 'way', 'relation']) | ||||||
|  |  | ||||||
|  |     try :  | ||||||
|  |         radius_result = overpass.query(radius_query) | ||||||
|  |      | ||||||
|  |         # The count is the number of elements found | ||||||
|  |         return radius_result.countElements() | ||||||
|  |      | ||||||
|  |     except : | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  | # Creates a bounding box around precise coordinates | ||||||
|  | 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] | ||||||
|  |     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 | ||||||
|  |  | ||||||
|  | # Generates the list of landmarks for a given Landmarktype. Needs coordinates, a list of amenities and the corresponding LandmarkType | ||||||
|  | def get_landmarks(coordinates: Tuple[float, float], l: List[Landmark], landmarktype: LandmarkType): | ||||||
|  |  | ||||||
|  |     overpass = Overpass() | ||||||
|  |  | ||||||
|  |     # Generate a bbox around currunt coordinates | ||||||
|  |     bbox = create_bbox(coordinates, BBOX_SIDE) | ||||||
|  |  | ||||||
|  |     # Initialize some variables | ||||||
|  |     N = 0 | ||||||
|  |     L = [] | ||||||
|  |  | ||||||
|     for amenity in l : |     for amenity in l : | ||||||
|         query = overpassQueryBuilder(area=areaId, elementType=['way', 'relation'], selector=amenity, includeGeometry=True) |         query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body') | ||||||
|         result = overpass.query(query) |         result = overpass.query(query) | ||||||
|         score += result.countElements() |         N += result.countElements() | ||||||
|  |  | ||||||
|     return score |         for elem in result.elements(): | ||||||
|  |  | ||||||
|  |             name = elem.tag('name')                             # Add name | ||||||
|  |             location = (elem.centerLat(), elem.centerLon())     # Add coordinates (lat, lon) | ||||||
|  |  | ||||||
| # take a lsit of tuples (id, score) to generate a list of landmarks |             # skip if unprecise location | ||||||
| def generate_landmarks(ids_and_scores: list) : |             if name is None or location[0] is None: | ||||||
|  |                 continue | ||||||
|  |             else : | ||||||
|                  |                  | ||||||
|     L = [] |                 osm_type = elem.type()              # Add type : 'way' or 'relation' | ||||||
|     for tup in ids_and_scores : |                 osm_id = elem.id()                  # Add OSM id  | ||||||
|         L.append(add_from_id(tup[0], tup[1])) |                 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 | ||||||
|  |                 score = count_elements_within_radius(location) | ||||||
|  |  | ||||||
|  |                 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 |     return L | ||||||
| """ |  | ||||||
| api = Api() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| l = (7515426, 70) |  | ||||||
| t = (5013364, 100) |  | ||||||
| n = (201611261, 99) |  | ||||||
| a = (226413508, 50) |  | ||||||
| m = (23762981, 30) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ids_and_scores = [t, l, n, a, m] |  | ||||||
|  |  | ||||||
| landmarks = generate_landmarks(ids_and_scores) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| for obj in landmarks : |  | ||||||
|     print(obj)""" |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| print(get_sights('Paris, France')) |  | ||||||
| @@ -1,7 +1,9 @@ | |||||||
| from optimizer import solve_optimization | from optimizer import solve_optimization | ||||||
|  | from landmarks_manager import generate_landmarks | ||||||
| from structs.landmarks import LandmarkTest | from structs.landmarks import LandmarkTest | ||||||
| from structs.landmarks import Landmark | from structs.landmarks import Landmark | ||||||
| from structs.preferences import Preferences | from structs.landmarktype import LandmarkType | ||||||
|  | from structs.preferences import Preferences, Preference | ||||||
| from fastapi import FastAPI, Query, Body | from fastapi import FastAPI, Query, Body | ||||||
| from typing import List | from typing import List | ||||||
|  |  | ||||||
| @@ -12,13 +14,22 @@ app = FastAPI() | |||||||
| #"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}" | #"http://127.0.0.1:8000/process?param1={param1}¶m2={param2}" | ||||||
| # This should become main at some point | # This should become main at some point | ||||||
| @app.post("/optimizer/{longitude}/{latitude}") | @app.post("/optimizer/{longitude}/{latitude}") | ||||||
| def main(longitude: float, latitude: float, prefrences: Preferences = Body(...)) -> List[Landmark]: | def main(longitude: float, latitude: float, preferences: Preferences = Body(...)) -> List[Landmark]: | ||||||
|     # From frontend get longitude, latitude and prefence list |     # From frontend get longitude, latitude and prefence list | ||||||
|      |      | ||||||
|     landmarks = [] |     # Generate the landmark list | ||||||
|  |     landmarks = generate_landmarks(tuple((longitude, latitude)), preferences) | ||||||
|  |  | ||||||
|  |     # Set the max distance | ||||||
|  |     max_steps = 90 | ||||||
|  |  | ||||||
|  |     # Compute the visiting order | ||||||
|  |     visiting_order = solve_optimization(landmarks, max_steps, True) | ||||||
|  |  | ||||||
|  |     return visiting_order | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     return landmarks |  | ||||||
|  |  | ||||||
| @app.get("test") | @app.get("test") | ||||||
| def test(): | def test(): | ||||||
| @@ -46,6 +57,16 @@ def test(): | |||||||
|     #return("max steps :", max_steps, "\n", visiting_order) |     #return("max steps :", max_steps, "\n", visiting_order) | ||||||
|  |  | ||||||
|  |  | ||||||
| """# keep this for debug | # input city, country in the form of 'Paris, France' | ||||||
| if __name__ == "__main__": | @app.post("/test2/{city_country}") | ||||||
|     main()""" | 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) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -212,7 +212,7 @@ def init_ub_dist(landmarks: list, max_steps: int): | |||||||
|         dist_table = [0]*len(landmarks) |         dist_table = [0]*len(landmarks) | ||||||
|         c.append(-spot1.attractiveness) |         c.append(-spot1.attractiveness) | ||||||
|         for j, spot2 in enumerate(landmarks) : |         for j, spot2 in enumerate(landmarks) : | ||||||
|             dist_table[j] = manhattan_distance(spot1.loc, spot2.loc) |             dist_table[j] = manhattan_distance(spot1.location, spot2.location) | ||||||
|         A.append(dist_table) |         A.append(dist_table) | ||||||
|     c = c*len(landmarks) |     c = c*len(landmarks) | ||||||
|     A_ub = [] |     A_ub = [] | ||||||
| @@ -238,7 +238,7 @@ def respect_user_mustsee(landmarks: list, A_eq: list, b_eq: list) : | |||||||
|                 for k in range(L-1) : |                 for k in range(L-1) : | ||||||
|                         l[k*L+L-1] = 1   |                         l[k*L+L-1] = 1   | ||||||
|  |  | ||||||
|             H += manhattan_distance(elem.loc, elem_prev.loc) |             H += manhattan_distance(elem.location, elem_prev.location) | ||||||
|             elem_prev = elem |             elem_prev = elem | ||||||
|  |  | ||||||
|             """for i in range(7): |             """for i in range(7): | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| from pydantic import BaseModel | from pydantic import BaseModel | ||||||
|  | from OSMPythonTools.api import Api | ||||||
| from .landmarktype import LandmarkType | from .landmarktype import LandmarkType | ||||||
| from .preferences import Preferences | from .preferences import Preferences | ||||||
|  |  | ||||||
| @@ -12,19 +13,10 @@ class Landmark(BaseModel) : | |||||||
|     name : str |     name : str | ||||||
|     type: LandmarkType      # De facto mapping depending on how the query was executed with overpass. Should still EXACTLY correspond to the preferences |     type: LandmarkType      # De facto mapping depending on how the query was executed with overpass. Should still EXACTLY correspond to the preferences | ||||||
|     location : tuple |     location : tuple | ||||||
|  |     osm_type : str | ||||||
|     # loop through the preferences and assign a score to the landmark |     osm_id : int | ||||||
|     def score(self, preferences: Preferences): |     attractiveness : int | ||||||
|  |     must_do : bool | ||||||
|         for preference_name, preference in preferences.__dict__.items(): |     n_tags : int | ||||||
|              |  | ||||||
|             if (preference_name == self.type.landmark_type) : |  | ||||||
|                 score = preference.score |  | ||||||
|          |  | ||||||
|         if (not score) : |  | ||||||
|             raise Exception(f"Could not determine score for landmark {self.name}") |  | ||||||
|  |  | ||||||
|         else : |  | ||||||
|             return score |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ from .landmarktype import LandmarkType | |||||||
|  |  | ||||||
| class Preference(BaseModel) : | class Preference(BaseModel) : | ||||||
|     name: str |     name: str | ||||||
|     type: LandmarkType |     type: LandmarkType  # should match the attributes of the Preferences class | ||||||
|     score: int |     score: int          # score could be from 1 to 5 | ||||||
|  |  | ||||||
| # Input for optimization | # Input for optimization | ||||||
| class Preferences(BaseModel) : | class Preferences(BaseModel) : | ||||||
| @@ -17,11 +17,11 @@ class Preferences(BaseModel) : | |||||||
|     # Shopping (diriger plutôt vers des zones / rues commerçantes) |     # Shopping (diriger plutôt vers des zones / rues commerçantes) | ||||||
|     shopping : Preference |     shopping : Preference | ||||||
|  |  | ||||||
|     # Food (price low or high. Combien on veut dépenser pour manger à midi/soir) | """    # Food (price low or high. Combien on veut dépenser pour manger à midi/soir) | ||||||
|     food_budget : Preference |     food_budget : Preference | ||||||
|      |      | ||||||
|     # Tolérance au détour (ce qui détermine (+ ou -) le chemin emprunté) |     # Tolérance au détour (ce qui détermine (+ ou -) le chemin emprunté) | ||||||
|     detour_tol : Preference |     detour_tol : Preference""" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								backend/src/tester.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								backend/src/tester.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | from optimizer import solve_optimization | ||||||
|  | from landmarks_manager import generate_landmarks | ||||||
|  | from structs.landmarks import LandmarkTest | ||||||
|  | from structs.landmarks import Landmark | ||||||
|  | from structs.landmarktype import LandmarkType | ||||||
|  | from structs.preferences import Preferences, Preference | ||||||
|  | from fastapi import FastAPI, Query, Body | ||||||
|  | from typing import List | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test3(city_country: str) -> List[Landmark]: | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     preferences = Preferences( | ||||||
|  |                     sightseeing=Preference( | ||||||
|  |                                   name='sightseeing',  | ||||||
|  |                                   type=LandmarkType(landmark_type='sightseeing'), | ||||||
|  |                                   score = 5), | ||||||
|  |                     nature=Preference( | ||||||
|  |                                   name='nature',  | ||||||
|  |                                   type=LandmarkType(landmark_type='nature'), | ||||||
|  |                                   score = 0), | ||||||
|  |                     shopping=Preference( | ||||||
|  |                                   name='shopping',  | ||||||
|  |                                   type=LandmarkType(landmark_type='shopping'), | ||||||
|  |                                   score = 5)) | ||||||
|  |  | ||||||
|  |     landmarks = generate_landmarks(city_country, preferences) | ||||||
|  |  | ||||||
|  |     max_steps = 9 | ||||||
|  |  | ||||||
|  |     visiting_order = solve_optimization(landmarks, max_steps, True) | ||||||
|  |  | ||||||
|  |     print(len(visiting_order)) | ||||||
|  |  | ||||||
|  |     return len(visiting_order) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test4(coordinates: tuple[float, float]) -> List[Landmark]: | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     preferences = Preferences( | ||||||
|  |                     sightseeing=Preference( | ||||||
|  |                                   name='sightseeing',  | ||||||
|  |                                   type=LandmarkType(landmark_type='sightseeing'), | ||||||
|  |                                   score = 5), | ||||||
|  |                     nature=Preference( | ||||||
|  |                                   name='nature',  | ||||||
|  |                                   type=LandmarkType(landmark_type='nature'), | ||||||
|  |                                   score = 0), | ||||||
|  |                     shopping=Preference( | ||||||
|  |                                   name='shopping',  | ||||||
|  |                                   type=LandmarkType(landmark_type='shopping'), | ||||||
|  |                                   score = 5)) | ||||||
|  |  | ||||||
|  |     landmarks = generate_landmarks(coordinates, preferences) | ||||||
|  |  | ||||||
|  |     max_steps = 90 | ||||||
|  |  | ||||||
|  |     visiting_order = solve_optimization(landmarks, max_steps, True) | ||||||
|  |  | ||||||
|  |     print(len(visiting_order)) | ||||||
|  |  | ||||||
|  |     return len(visiting_order) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | test3(tuple((48.834378, 2.322113))) | ||||||
		Reference in New Issue
	
	Block a user