corrected overpass return and switched to json
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m32s
Run linting on the backend code / Build (pull_request) Successful in 27s
Run testing on the backend code / Build (pull_request) Failing after 7m11s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 24s

This commit is contained in:
2025-01-28 08:04:54 +01:00
parent bab6cfe74e
commit 978cae290b
7 changed files with 196 additions and 193 deletions

View File

@@ -3,7 +3,7 @@ import os
import urllib
import math
import logging
import xml.etree.ElementTree as ET
import json
from .caching_strategy import get_cache_key, CachingStrategy
from ..constants import OSM_CACHE_DIR, OSM_TYPES
@@ -20,7 +20,7 @@ class Overpass :
logger = logging.getLogger(__name__)
def __init__(self, caching_strategy: str = 'XML', cache_dir: str = OSM_CACHE_DIR) :
def __init__(self, caching_strategy: str = 'JSON', cache_dir: str = OSM_CACHE_DIR) :
"""
Initialize the Overpass instance with the url, headers and caching strategy.
"""
@@ -30,9 +30,9 @@ class Overpass :
def send_query(self, bbox: tuple, osm_types: OSM_TYPES,
selector: str, conditions=[], out='center') -> ET:
selector: str, conditions=[], out='center'):
"""
Sends the Overpass QL query to the Overpass API and returns the parsed XML response.
Sends the Overpass QL query to the Overpass API and returns the parsed json response.
Args:
bbox (tuple): Bounding box for the query.
@@ -42,7 +42,7 @@ class Overpass :
out (str): Output format ('center', 'body', etc.). Defaults to 'center'.
Returns:
ET.Element: Parsed XML response from the Overpass API, or cached data if available.
dict: Parsed json response from the Overpass API, or cached data if available.
"""
# Determine which grid cells overlap with this bounding box.
overlapping_cells = Overpass._get_overlapping_cells(bbox)
@@ -59,10 +59,10 @@ class Overpass :
# Missing data: Make a query to Overpass API
query_str = Overpass.build_query(bbox, osm_types, selector, conditions, out)
self.fetch_data_from_api(query_str)
return self.fetch_data_from_api(query_str)
def fetch_data_from_api(self, query_str: str, cache_key: str = None) -> ET.Element:
def fetch_data_from_api(self, query_str: str, cache_key: str = None) -> dict:
"""
Fetch data from the Overpass API and update the cache.
@@ -72,33 +72,37 @@ class Overpass :
hollow_cache_keys (list): Cache keys for missing data to be updated.
Returns:
ET.Element: Combined cached and fetched data.
dict: Combined cached and fetched data.
"""
try:
data = urllib.parse.urlencode({'data': query_str}).encode('utf-8')
request = urllib.request.Request(self.overpass_url, data=data, headers=self.headers)
with urllib.request.urlopen(request) as response:
response_data = response.read().decode('utf-8')
root = ET.fromstring(response_data)
response_data = response.read().decode('utf-8') # Convert the HTTPResponse to a string
data = json.loads(response_data) # Load the JSON from the string
elements = data.get('elements', [])
if cache_key is not None :
self.caching_strategy.set(cache_key, root)
self.caching_strategy.set(cache_key, elements)
self.logger.debug(f'Cache set.')
else :
self.logger.debug(f'Cache miss. Fetching data through Overpass\nQuery = {query_str}')
return root
return elements
except urllib.error.URLError as e:
self.logger.error(f"Error connecting to Overpass API: {e}")
raise ConnectionError(f"Error connecting to Overpass API: {e}")
except Exception as exc :
raise Exception(f'An unexpected error occured: {str(exc)}') from exc
def fill_cache(self, xml_string: str) :
def fill_cache(self, json_data) :
"""
Fill cache with data by using a hollow cache entry's information.
"""
query_str, cache_key = Overpass._build_query_from_hollow(xml_string)
query_str, cache_key = Overpass._build_query_from_hollow(json_data)
self.fetch_data_from_api(query_str, cache_key)
@@ -133,7 +137,7 @@ class Overpass :
if not isinstance(osm_types, list) :
osm_types = [osm_types]
query = '('
query = '[out:json];('
# convert the bbox to string.
bbox_str = f"({','.join(map(str, bbox))})"
@@ -190,21 +194,21 @@ class Overpass :
@staticmethod
def _build_query_from_hollow(xml_string):
def _build_query_from_hollow(json_data):
"""
Build query string using information from a hollow cache entry.
"""
# Parse the XML string into an ElementTree object
root = ET.fromstring(xml_string)
# Parse the JSON string into a dictionary
data = json.loads(json_data)
# Extract values from the XML tree
key = root.find('key').text
cell = tuple(map(float, root.find('cell').text.strip('()').split(',')))
# Extract values from the JSON object
key = data.get('key')
cell = tuple(data.get('cell'))
bbox = Overpass._get_bbox_from_grid_cell(cell[0], cell[1])
osm_types = root.find('osm_types').text.split(',')
selector = root.find('selector').text
conditions = root.find('conditions').text.split(',') if root.find('conditions').text != "none" else []
out = root.find('out').text
osm_types = data.get('osm_types')
selector = data.get('selector')
conditions = data.get('conditions') if data.get('conditions') != "none" else []
out = data.get('out')
query_str = Overpass.build_query(bbox, osm_types, selector, conditions, out)
@@ -265,14 +269,14 @@ class Overpass :
"""
Combines data from multiple cached responses into a single result.
"""
combined_data = ET.Element("osm")
combined_data = []
for cached_data in cached_data_list:
for element in cached_data:
combined_data.append(element)
return combined_data
def get_base_info(elem: ET.Element, osm_type: OSM_TYPES, with_name=False) :
def get_base_info(elem: dict, osm_type: OSM_TYPES, with_name=False) :
"""
Extracts base information (coordinates, OSM ID, and optionally a name) from an OSM element.
@@ -281,7 +285,7 @@ def get_base_info(elem: ET.Element, osm_type: OSM_TYPES, with_name=False) :
extracting coordinates either directly or from a center tag, depending on the element type.
Args:
elem (ET.Element): The XML element representing the OSM entity.
elem (dict): The JSON element representing the OSM entity.
osm_type (str): The type of the OSM entity (e.g., 'node', 'way'). If 'node', the coordinates
are extracted directly from the element; otherwise, from the 'center' tag.
with_name (bool): Whether to extract and return the name of the element. If True, it attempts
@@ -295,7 +299,7 @@ def get_base_info(elem: ET.Element, osm_type: OSM_TYPES, with_name=False) :
"""
# 1. extract coordinates
if osm_type != 'node' :
center = elem.find('center')
center = elem.get('center')
lat = float(center.get('lat'))
lon = float(center.get('lon'))
@@ -310,7 +314,7 @@ def get_base_info(elem: ET.Element, osm_type: OSM_TYPES, with_name=False) :
# 3. Extract name if specified and return
if with_name :
name = elem.find("tag[@k='name']").get('v') if elem.find("tag[@k='name']") is not None else None
name = elem.get('tags', {}).get('name')
return osm_id, coords, name
else :
return osm_id, coords
@@ -318,7 +322,7 @@ def get_base_info(elem: ET.Element, osm_type: OSM_TYPES, with_name=False) :
def fill_cache():
overpass = Overpass(caching_strategy='XML', cache_dir=OSM_CACHE_DIR)
overpass = Overpass(caching_strategy='JSON', cache_dir=OSM_CACHE_DIR)
with os.scandir(OSM_CACHE_DIR) as it:
for entry in it:
@@ -326,10 +330,10 @@ def fill_cache():
# Read the whole file content as a string
with open(entry.path, 'r') as f:
xml_string = f.read()
json_data = f.read()
# Fill the cache with the query and key
overpass.fill_cache(xml_string)
overpass.fill_cache(json_data)
# Now delete the file as the cache is filled
os.remove(entry.path)