import math as m import json, os from typing import List, Tuple from OSMPythonTools.overpass import Overpass, overpassQueryBuilder, Nominatim from structs.landmarks import Landmark, LandmarkType from structs.preferences import Preferences, Preference SIGHTSEEING = LandmarkType(landmark_type='sightseeing') NATURE = LandmarkType(landmark_type='nature') SHOPPING = LandmarkType(landmark_type='shopping') # Include the json here # Create a list of all things to visit given some preferences and a city. Ready for the optimizer def generate_landmarks(preferences: Preferences, 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 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_clean = [] scores = [0]*len(L) names = [] name_id = {} for i, elem in enumerate(L) : if elem.name not in names : 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:] 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 def correct_score(L: List[Landmark], preference: Preference) : if len(L) == 0 : return if L[0].type != preference.type : raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {L[0].name}") for elem in L : elem.attractiveness = int(elem.attractiveness*preference.score/500) # arbitrary computation # Function to count elements within a certain radius of a location def count_elements_within_radius(coordinates: Tuple[float, float], radius: int) -> int: lat = coordinates[0] lon = coordinates[1] alpha = (180*radius)/(6371000*m.pi) bbox = {'latLower':lat-alpha,'lonLower':lon-alpha,'latHigher':lat+alpha,'lonHigher': lon+alpha} # Build the query to find elements within the radius radius_query = overpassQueryBuilder(bbox=[bbox['latLower'],bbox['lonLower'],bbox['latHigher'],bbox['lonHigher']], elementType=['node', 'way', 'relation']) try : overpass = Overpass() radius_result = overpass.query(radius_query) return radius_result.countElements() 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, 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 : # 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