overpass as class
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m4s
				
			
		
			
				
	
				Run linting on the backend code / Build (pull_request) Successful in 29s
				
			
		
			
				
	
				Run testing on the backend code / Build (pull_request) Failing after 4m39s
				
			
		
			
				
	
				Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 24s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m4s
				
			Run linting on the backend code / Build (pull_request) Successful in 29s
				
			Run testing on the backend code / Build (pull_request) Failing after 4m39s
				
			Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 24s
				
			This commit is contained in:
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -5,116 +5,122 @@ import logging
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
 | 
			
		||||
from .caching_strategy import get_cache_key, CachingStrategy
 | 
			
		||||
from ..constants import OSM_CACHE_DIR
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('Overpass')
 | 
			
		||||
osm_types = List[Literal['way', 'node', 'relation']]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('overpass')
 | 
			
		||||
ElementTypes = List[Literal['way', 'node', 'relation']]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def build_query(area: tuple, element_types: ElementTypes,
 | 
			
		||||
                selector: str, conditions=[], out='center'):
 | 
			
		||||
class Overpass :
 | 
			
		||||
    """
 | 
			
		||||
    Constructs a query string for the Overpass API to retrieve OpenStreetMap (OSM) data.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        area (tuple): A tuple representing the geographical search area, typically in the format 
 | 
			
		||||
                      (radius, latitude, longitude). The first element is a string like "around:2000" 
 | 
			
		||||
                      specifying the search radius, and the second and third elements represent 
 | 
			
		||||
                      the latitude and longitude as floats or strings.
 | 
			
		||||
        element_types (list[str]): A list of OSM element types to search for. Must be one or more of 
 | 
			
		||||
                                   'Way', 'Node', or 'Relation'.
 | 
			
		||||
        selector (str): The key or tag to filter the OSM elements (e.g., 'amenity', 'highway', etc.).
 | 
			
		||||
        conditions (list, optional): A list of conditions to apply as additional filters for the 
 | 
			
		||||
                                     selected OSM elements. The conditions should be written in 
 | 
			
		||||
                                     the Overpass QL format, and they are combined with '&&' if 
 | 
			
		||||
                                     multiple are provided. Defaults to an empty list.
 | 
			
		||||
        out (str, optional): Specifies the output type, such as 'center', 'body', or 'tags'. 
 | 
			
		||||
                             Defaults to 'center'.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        str: The constructed Overpass QL query string.
 | 
			
		||||
 | 
			
		||||
    Notes:
 | 
			
		||||
        - If no conditions are provided, the query will just use the `selector` to filter the OSM 
 | 
			
		||||
          elements without additional constraints.
 | 
			
		||||
        - The search area must always formatted as "(radius, lat, lon)".
 | 
			
		||||
    Overpass class to manage the query building and sending to overpass api.
 | 
			
		||||
    The caching strategy is a part of this class and initialized upon creation of the Overpass object.
 | 
			
		||||
    """
 | 
			
		||||
    if not isinstance(conditions, list) :
 | 
			
		||||
        conditions = [conditions]
 | 
			
		||||
    if not isinstance(element_types, list) :
 | 
			
		||||
        element_types = [element_types]
 | 
			
		||||
 | 
			
		||||
    query = '('
 | 
			
		||||
 | 
			
		||||
    # Round the radius to nearest 50 and coordinates to generate less queries
 | 
			
		||||
    if area[0] > 500 :
 | 
			
		||||
        search_radius = round(area[0] / 50) * 50
 | 
			
		||||
        loc = tuple((round(area[1], 2), round(area[2], 2)))
 | 
			
		||||
    else :
 | 
			
		||||
        search_radius = round(area[0] / 25) * 25
 | 
			
		||||
        loc = tuple((round(area[1], 3), round(area[2], 3)))
 | 
			
		||||
 | 
			
		||||
    search_area = f"(around:{search_radius}, {str(loc[0])}, {str(loc[1])})"
 | 
			
		||||
 | 
			
		||||
    if conditions :
 | 
			
		||||
        conditions = '(if: ' + ' && '.join(conditions) + ')'
 | 
			
		||||
    else :
 | 
			
		||||
        conditions = ''
 | 
			
		||||
 | 
			
		||||
    for elem in element_types :
 | 
			
		||||
        query += elem + '[' + selector + ']' + conditions + search_area + ';'
 | 
			
		||||
 | 
			
		||||
    query += ');' + f'out {out};'
 | 
			
		||||
 | 
			
		||||
    return query
 | 
			
		||||
    def __init__(self, caching_strategy: str = 'XML', cache_dir: str = OSM_CACHE_DIR) :
 | 
			
		||||
        """
 | 
			
		||||
        Initialize the Overpass instance with the url, headers and caching strategy.
 | 
			
		||||
        """
 | 
			
		||||
        self.overpass_url = "https://overpass-api.de/api/interpreter"
 | 
			
		||||
        self.headers = {'User-Agent': 'Mozilla/5.0 (compatible; OverpassQuery/1.0; +http://example.com)',}
 | 
			
		||||
        self.caching_strategy = CachingStrategy.use(caching_strategy, cache_dir=cache_dir)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def send_query(query: str) -> dict:
 | 
			
		||||
    """
 | 
			
		||||
    Sends the Overpass QL query to the Overpass API and returns the parsed JSON response.
 | 
			
		||||
    def build_query(self, area: tuple, osm_types: osm_types,
 | 
			
		||||
                    selector: str, conditions=[], out='center'):
 | 
			
		||||
        """
 | 
			
		||||
        Constructs a query string for the Overpass API to retrieve OpenStreetMap (OSM) data.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        query (str): The Overpass QL query to be sent to the Overpass API.
 | 
			
		||||
        Args:
 | 
			
		||||
            area (tuple): A tuple representing the geographical search area, typically in the format 
 | 
			
		||||
                        (radius, latitude, longitude). The first element is a string like "around:2000" 
 | 
			
		||||
                        specifying the search radius, and the second and third elements represent 
 | 
			
		||||
                        the latitude and longitude as floats or strings.
 | 
			
		||||
            osm_types (list[str]): A list of OSM element types to search for. Must be one or more of 
 | 
			
		||||
                                    'Way', 'Node', or 'Relation'.
 | 
			
		||||
            selector (str): The key or tag to filter the OSM elements (e.g., 'amenity', 'highway', etc.).
 | 
			
		||||
            conditions (list, optional): A list of conditions to apply as additional filters for the 
 | 
			
		||||
                                        selected OSM elements. The conditions should be written in 
 | 
			
		||||
                                        the Overpass QL format, and they are combined with '&&' if 
 | 
			
		||||
                                        multiple are provided. Defaults to an empty list.
 | 
			
		||||
            out (str, optional): Specifies the output type, such as 'center', 'body', or 'tags'. 
 | 
			
		||||
                                Defaults to 'center'.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        dict: The parsed JSON response from the Overpass API, or None if the request fails.
 | 
			
		||||
    """
 | 
			
		||||
        Returns:
 | 
			
		||||
            str: The constructed Overpass QL query string.
 | 
			
		||||
 | 
			
		||||
    # Generate a cache key for the current query
 | 
			
		||||
    cache_key = get_cache_key(query)
 | 
			
		||||
        Notes:
 | 
			
		||||
            - If no conditions are provided, the query will just use the `selector` to filter the OSM 
 | 
			
		||||
            elements without additional constraints.
 | 
			
		||||
            - The search area must always formatted as "(radius, lat, lon)".
 | 
			
		||||
        """
 | 
			
		||||
        if not isinstance(conditions, list) :
 | 
			
		||||
            conditions = [conditions]
 | 
			
		||||
        if not isinstance(osm_types, list) :
 | 
			
		||||
            osm_types = [osm_types]
 | 
			
		||||
 | 
			
		||||
    # Try to fetch the result from the cache
 | 
			
		||||
    cached_response = CachingStrategy.get(cache_key)
 | 
			
		||||
    if cached_response is not None :
 | 
			
		||||
        logger.debug("Cache hit.")
 | 
			
		||||
        return cached_response
 | 
			
		||||
        query = '('
 | 
			
		||||
 | 
			
		||||
    # Define the Overpass API endpoint
 | 
			
		||||
    overpass_url = "https://overpass-api.de/api/interpreter"
 | 
			
		||||
        # Round the radius to nearest 50 and coordinates to generate less queries
 | 
			
		||||
        if area[0] > 500 :
 | 
			
		||||
            search_radius = round(area[0] / 50) * 50
 | 
			
		||||
            loc = tuple((round(area[1], 2), round(area[2], 2)))
 | 
			
		||||
        else :
 | 
			
		||||
            search_radius = round(area[0] / 25) * 25
 | 
			
		||||
            loc = tuple((round(area[1], 3), round(area[2], 3)))
 | 
			
		||||
 | 
			
		||||
    # Prepare the data to be sent as POST request, encoded as bytes
 | 
			
		||||
    data = urllib.parse.urlencode({'data': query}).encode('utf-8')
 | 
			
		||||
        search_area = f"(around:{search_radius}, {str(loc[0])}, {str(loc[1])})"
 | 
			
		||||
 | 
			
		||||
    # Create a custom header with a User-Agent
 | 
			
		||||
    headers = {
 | 
			
		||||
        'User-Agent': 'Mozilla/5.0 (compatible; OverpassQuery/1.0; +http://example.com)',
 | 
			
		||||
    }
 | 
			
		||||
        if conditions :
 | 
			
		||||
            conditions = '(if: ' + ' && '.join(conditions) + ')'
 | 
			
		||||
        else :
 | 
			
		||||
            conditions = ''
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        # Create a Request object with the specified URL, data, and headers
 | 
			
		||||
        request = urllib.request.Request(overpass_url, data=data, headers=headers)
 | 
			
		||||
        for elem in osm_types :
 | 
			
		||||
            query += elem + '[' + selector + ']' + conditions + search_area + ';'
 | 
			
		||||
 | 
			
		||||
        # Send the request and read the response
 | 
			
		||||
        with urllib.request.urlopen(request) as response:
 | 
			
		||||
            # Read and decode the response
 | 
			
		||||
            response_data = response.read().decode('utf-8')
 | 
			
		||||
            root = ET.fromstring(response_data)
 | 
			
		||||
        query += ');' + f'out {out};'
 | 
			
		||||
 | 
			
		||||
            # Cache the response data as an ElementTree root
 | 
			
		||||
            CachingStrategy.set(cache_key, root)
 | 
			
		||||
            logger.debug("Response data added to cache.")
 | 
			
		||||
        return query
 | 
			
		||||
 | 
			
		||||
            return root
 | 
			
		||||
 | 
			
		||||
    except urllib.error.URLError as e:
 | 
			
		||||
        raise ConnectionError(f"Error connecting to Overpass API: {e}") from e
 | 
			
		||||
    def send_query(self, query: str) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Sends the Overpass QL query to the Overpass API and returns the parsed JSON response.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            query (str): The Overpass QL query to be sent to the Overpass API.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            dict: The parsed JSON response from the Overpass API, or None if the request fails.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # Generate a cache key for the current query
 | 
			
		||||
        cache_key = get_cache_key(query)
 | 
			
		||||
 | 
			
		||||
        # Try to fetch the result from the cache
 | 
			
		||||
        cached_response = self.caching_strategy.get(cache_key)
 | 
			
		||||
        if cached_response is not None :
 | 
			
		||||
            logger.debug("Cache hit.")
 | 
			
		||||
            return cached_response
 | 
			
		||||
 | 
			
		||||
        # Prepare the data to be sent as POST request, encoded as bytes
 | 
			
		||||
        data = urllib.parse.urlencode({'data': query}).encode('utf-8')
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # Create a Request object with the specified URL, data, and headers
 | 
			
		||||
            request = urllib.request.Request(self.overpass_url, data=data, headers=self.headers)
 | 
			
		||||
 | 
			
		||||
            # Send the request and read the response
 | 
			
		||||
            with urllib.request.urlopen(request) as response:
 | 
			
		||||
                # Read and decode the response
 | 
			
		||||
                response_data = response.read().decode('utf-8')
 | 
			
		||||
                root = ET.fromstring(response_data)
 | 
			
		||||
 | 
			
		||||
                # Cache the response data as an ElementTree root
 | 
			
		||||
                self.caching_strategy.set(cache_key, root)
 | 
			
		||||
                logger.debug("Response data added to cache.")
 | 
			
		||||
 | 
			
		||||
                return root
 | 
			
		||||
 | 
			
		||||
        except urllib.error.URLError as e:
 | 
			
		||||
            raise ConnectionError(f"Error connecting to Overpass API: {e}") from e
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,14 @@ import numpy as np
 | 
			
		||||
from sklearn.cluster import DBSCAN
 | 
			
		||||
from pydantic import BaseModel
 | 
			
		||||
 | 
			
		||||
from ..overpass.overpass import build_query, send_query
 | 
			
		||||
from ..overpass.caching_strategy import CachingStrategy
 | 
			
		||||
from ..overpass.overpass import Overpass
 | 
			
		||||
from ..structs.landmark import Landmark
 | 
			
		||||
from .get_time_distance import get_distance
 | 
			
		||||
from ..constants import OSM_CACHE_DIR
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# silence the overpass logger
 | 
			
		||||
logging.getLogger('overpass').setLevel(level=logging.CRITICAL)
 | 
			
		||||
logging.getLogger('Overpass').setLevel(level=logging.CRITICAL)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Cluster(BaseModel):
 | 
			
		||||
@@ -79,7 +78,9 @@ class ClusterManager:
 | 
			
		||||
        Args: 
 | 
			
		||||
            bbox: The bounding box coordinates (around:radius, center_lat, center_lon).
 | 
			
		||||
        """
 | 
			
		||||
        CachingStrategy.use('XML', cache_dir=OSM_CACHE_DIR)
 | 
			
		||||
        # Setup the caching in the Overpass class.
 | 
			
		||||
        self.overpass = Overpass(caching_strategy='XML', cache_dir=OSM_CACHE_DIR)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        self.cluster_type = cluster_type
 | 
			
		||||
        if cluster_type == 'shopping' :
 | 
			
		||||
@@ -94,16 +95,16 @@ class ClusterManager:
 | 
			
		||||
            raise NotImplementedError("Please choose only an available option for cluster detection")
 | 
			
		||||
 | 
			
		||||
        # Initialize the points for cluster detection
 | 
			
		||||
        query = build_query(
 | 
			
		||||
        query = self.overpass.build_query(
 | 
			
		||||
            area = bbox,
 | 
			
		||||
            element_types = osm_types,
 | 
			
		||||
            osm_types = osm_types,
 | 
			
		||||
            selector = sel,
 | 
			
		||||
            out = out
 | 
			
		||||
        )
 | 
			
		||||
        self.logger.debug(f"Cluster query: {query}")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            result = send_query(query)
 | 
			
		||||
            result = self.overpass.send_query(query)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            self.logger.error(f"Error fetching landmarks: {e}")
 | 
			
		||||
 | 
			
		||||
@@ -243,15 +244,15 @@ class ClusterManager:
 | 
			
		||||
        osm_types = ['node', 'way', 'relation']
 | 
			
		||||
 | 
			
		||||
        for sel in selectors :
 | 
			
		||||
            query = build_query(
 | 
			
		||||
            query = self.overpass.build_query(
 | 
			
		||||
                area = bbox,
 | 
			
		||||
                element_types = osm_types,
 | 
			
		||||
                osm_types = osm_types,
 | 
			
		||||
                selector = sel,
 | 
			
		||||
                out = 'ids center'
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                result = send_query(query)
 | 
			
		||||
                result = self.overpass.send_query(query)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                self.logger.error(f"Error fetching landmarks: {e}")
 | 
			
		||||
                continue
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,12 @@ from ..structs.preferences import Preferences
 | 
			
		||||
from ..structs.landmark import Landmark
 | 
			
		||||
from .take_most_important import take_most_important
 | 
			
		||||
from .cluster_manager import ClusterManager
 | 
			
		||||
from ..overpass.overpass import build_query, send_query
 | 
			
		||||
from ..overpass.caching_strategy import CachingStrategy
 | 
			
		||||
from ..overpass.overpass import Overpass
 | 
			
		||||
 | 
			
		||||
from ..constants import AMENITY_SELECTORS_PATH, LANDMARK_PARAMETERS_PATH, OPTIMIZER_PARAMETERS_PATH, OSM_CACHE_DIR
 | 
			
		||||
 | 
			
		||||
# silence the overpass logger
 | 
			
		||||
logging.getLogger('overpass').setLevel(level=logging.CRITICAL)
 | 
			
		||||
logging.getLogger('Overpass').setLevel(level=logging.CRITICAL)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LandmarkManager:
 | 
			
		||||
@@ -56,7 +55,8 @@ class LandmarkManager:
 | 
			
		||||
            self.walking_speed = parameters['average_walking_speed']
 | 
			
		||||
            self.detour_factor = parameters['detour_factor']
 | 
			
		||||
 | 
			
		||||
        CachingStrategy.use('XML', cache_dir=OSM_CACHE_DIR)
 | 
			
		||||
        # Setup the caching in the Overpass class.
 | 
			
		||||
        self.overpass = Overpass(caching_strategy='XML', cache_dir=OSM_CACHE_DIR)
 | 
			
		||||
 | 
			
		||||
        self.logger.info('LandmakManager successfully initialized.')
 | 
			
		||||
 | 
			
		||||
@@ -189,15 +189,15 @@ class LandmarkManager:
 | 
			
		||||
        for sel in dict_to_selector_list(amenity_selector):
 | 
			
		||||
            # self.logger.debug(f"Current selector: {sel}")
 | 
			
		||||
 | 
			
		||||
            element_types = ['way', 'relation']
 | 
			
		||||
            osm_types = ['way', 'relation']
 | 
			
		||||
 | 
			
		||||
            if 'viewpoint' in sel :
 | 
			
		||||
                query_conditions = []
 | 
			
		||||
                element_types.append('node')
 | 
			
		||||
                osm_types.append('node')
 | 
			
		||||
 | 
			
		||||
            query = build_query(
 | 
			
		||||
            query = self.overpass.build_query(
 | 
			
		||||
                area = bbox,
 | 
			
		||||
                element_types = element_types,
 | 
			
		||||
                osm_types = osm_types,
 | 
			
		||||
                selector = sel,
 | 
			
		||||
                conditions = query_conditions,        # except for nature....
 | 
			
		||||
                out = 'center'
 | 
			
		||||
@@ -205,7 +205,7 @@ class LandmarkManager:
 | 
			
		||||
            self.logger.debug(f"Query: {query}")
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                result = send_query(query)
 | 
			
		||||
                result = self.overpass.send_query(query)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                self.logger.error(f"Error fetching landmarks: {e}")
 | 
			
		||||
                continue
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,13 @@
 | 
			
		||||
import logging
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
 | 
			
		||||
from ..overpass.overpass import build_query, send_query
 | 
			
		||||
from ..overpass.caching_strategy import CachingStrategy
 | 
			
		||||
from ..overpass.overpass import Overpass
 | 
			
		||||
from ..structs.landmark import Toilets
 | 
			
		||||
from ..constants import OSM_CACHE_DIR
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# silence the overpass logger
 | 
			
		||||
logging.getLogger('overpass').setLevel(level=logging.CRITICAL)
 | 
			
		||||
logging.getLogger('Overpass').setLevel(level=logging.CRITICAL)
 | 
			
		||||
 | 
			
		||||
class ToiletsManager:
 | 
			
		||||
    """
 | 
			
		||||
@@ -40,7 +39,9 @@ class ToiletsManager:
 | 
			
		||||
 | 
			
		||||
        self.radius = radius
 | 
			
		||||
        self.location = location
 | 
			
		||||
        CachingStrategy.use('XML', cache_dir=OSM_CACHE_DIR)
 | 
			
		||||
 | 
			
		||||
        # Setup the caching in the Overpass class.
 | 
			
		||||
        self.overpass = Overpass(caching_strategy='XML', cache_dir=OSM_CACHE_DIR)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def generate_toilet_list(self) -> list[Toilets] :
 | 
			
		||||
@@ -56,16 +57,16 @@ class ToiletsManager:
 | 
			
		||||
        osm_types = ['node', 'way', 'relation']
 | 
			
		||||
        toilets_list = []
 | 
			
		||||
 | 
			
		||||
        query = build_query(
 | 
			
		||||
        query = self.overpass.build_query(
 | 
			
		||||
                area = bbox,
 | 
			
		||||
                element_types = osm_types,
 | 
			
		||||
                osm_types = osm_types,
 | 
			
		||||
                selector = '"amenity"="toilets"',
 | 
			
		||||
                out = 'ids center tags'
 | 
			
		||||
                )
 | 
			
		||||
        self.logger.debug(f"Query: {query}")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            result = send_query(query)
 | 
			
		||||
            result = self.overpass.send_query(query)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            self.logger.error(f"Error fetching landmarks: {e}")
 | 
			
		||||
            return None
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user