from OSMPythonTools.api import Api from OSMPythonTools.overpass import Overpass, overpassQueryBuilder, Nominatim from dataclasses import dataclass 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 # Include th json here # Create a list of all things to visit given some preferences and a city. Ready for the optimizer def generate_landmarks(coordinates: Tuple[float, float], preferences: Preferences) : 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"'] # List for sightseeing L1 = get_landmarks(coordinates, l_sights, LandmarkType(landmark_type='sightseeing')) correct_score(L1, preferences.sightseeing) # List for nature L2 = get_landmarks(coordinates, l_nature, LandmarkType(landmark_type='nature')) correct_score(L2, preferences.nature) # 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 # Correct the score of a list of landmarks by taking into account preferences and the number of tags 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/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() # 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 : 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 : query = overpassQueryBuilder(bbox=bbox, elementType=['way', 'relation'], selector=amenity, includeCenter=True, out='body') result = overpass.query(query) N += result.countElements() for elem in result.elements(): name = elem.tag('name') # Add name location = (elem.centerLat(), elem.centerLon()) # Add coordinates (lat, lon) # skip if unprecise location if name is None or location[0] is None: continue 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 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