amazing cache #55
| @@ -328,7 +328,7 @@ div.media { | |||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <h1 id="title">Backend Testing Report</h1> |     <h1 id="title">Backend Testing Report</h1> | ||||||
|     <p>Report generated on 28-Jan-2025 at 16:30:56 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a> |     <p>Report generated on 29-Jan-2025 at 09:35:03 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a> | ||||||
|         v4.1.1</p> |         v4.1.1</p> | ||||||
|     <div id="environment-header"> |     <div id="environment-header"> | ||||||
|       <h2>Environment</h2> |       <h2>Environment</h2> | ||||||
| @@ -382,7 +382,7 @@ div.media { | |||||||
|         <h2>Summary</h2> |         <h2>Summary</h2> | ||||||
|         <div class="additional-summary prefix"> |         <div class="additional-summary prefix"> | ||||||
|         </div> |         </div> | ||||||
|         <p class="run-count">1 test took 00:00:10.</p> |         <p class="run-count">1 test took 97 ms.</p> | ||||||
|         <p class="filter">(Un)check the boxes to filter the results.</p> |         <p class="filter">(Un)check the boxes to filter the results.</p> | ||||||
|         <div class="summary__reload"> |         <div class="summary__reload"> | ||||||
|           <div class="summary__reload__button hidden" onclick="location.reload()"> |           <div class="summary__reload__button hidden" onclick="location.reload()"> | ||||||
| @@ -432,7 +432,7 @@ div.media { | |||||||
|     </table> |     </table> | ||||||
|   </body> |   </body> | ||||||
|   <footer> |   <footer> | ||||||
|     <div id="data-container" data-jsonblob="{"environment": {"Python": "3.12.3", "Platform": "Linux-6.8.0-51-generic-x86_64-with-glibc2.39", "Packages": {"pytest": "8.3.4", "pluggy": "1.5.0"}, "Plugins": {"html": "4.1.1", "anyio": "4.8.0", "metadata": "3.1.1"}}, "tests": {"src/tests/test_main.py::test_turckheim": [{"extras": [], "result": "Passed", "testId": "src/tests/test_main.py::test_turckheim", "resultsTableRow": ["<td class=\"col-result\">Passed</td>", "<td class=\"col-testId\">src/tests/test_main.py::test_turckheim</td>", "<td>start (0 | 0) - 6 - H\u00f4tel des Deux-Clefs (217 | 5) - 1 - H\u00f4tel de ville (238 | 5) - 6 - finish (0 | 0) - 0</td>", "<td>23 min</td>", "<td>20 min</td>", "<td class=\"col-duration\">00:00:10</td>", "<td class=\"col-links\"></td>"], "log": "------------------------------ Captured log call -------------------------------\nDEBUG    asyncio:selector_events.py:64 Using selector: EpollSelector\nINFO     src.main:main.py:67 No end coordinates provided. Using start=end.\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:76 Starting to fetch landmarks...\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:88 Fetching sightseeing landmarks...\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:204 Fetched 20 landmarks of type sightseeing in (48.079458619727674, 7.272726663391359, 48.08971738027232, 7.288083336608641)\nINFO     src.utils.landmarks_manager:landmarks_manager.py:91 Found 20 sightseeing landmarks\nINFO     src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO     src.utils.cluster_manager:cluster_manager.py:145 Found 0 sightseeing clusters.\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:100 Fetching nature landmarks...\nINFO     src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:204 Fetched 10 landmarks of type nature in (48.079458619727674, 7.272726663391359, 48.08971738027232, 7.288083336608641)\nINFO     src.utils.landmarks_manager:landmarks_manager.py:103 Found 10 nature landmarks\nINFO     src.main:main.py:104 Fetched 22 landmarks in  \t: 5.367 seconds\nDEBUG    src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO     src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 455\nDEBUG    src.optimization.refiner:refiner.py:345 Using 5 minor landmarks around the predicted path\nDEBUG    src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO     src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 455\nDEBUG    src.main:main.py:130 First stage optimization\t: 0.126 seconds\nDEBUG    src.main:main.py:131 Second stage optimization\t: 0.033 seconds\nINFO     src.main:main.py:132 Total computation time\t: 0.159 seconds\nINFO     src.main:main.py:137 Generated a trip of 23 minutes with 4 landmarks in 5.526 seconds.\nDEBUG    src.overpass.overpass:overpass.py:112 Cache set for b2fd047a07f9563c8c0925aad9d61052\nDEBUG    src.overpass.overpass:overpass.py:112 Cache set for 7ee38297ba20e3bc47b984dce2785e22\nDEBUG    src.overpass.overpass:overpass.py:112 Cache set for 7f98015c7bc3a9100e10bc1c1ddfa572\nDEBUG    src.overpass.overpass:overpass.py:112 Cache set for 43424dbebba69d06a66a34728c3bb93e\nDEBUG    src.overpass.overpass:overpass.py:112 Cache set for b7d4ee874adac132bc9e80d7172fab8e\nINFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/trip/new &quot;HTTP/1.1 200 OK&quot;\n\n"}]}, "renderCollapsed": ["passed"], "initialSort": "result", "title": "Backend Testing Report"}"></div> |     <div id="data-container" data-jsonblob="{"environment": {"Python": "3.12.3", "Platform": "Linux-6.8.0-51-generic-x86_64-with-glibc2.39", "Packages": {"pytest": "8.3.4", "pluggy": "1.5.0"}, "Plugins": {"html": "4.1.1", "anyio": "4.8.0", "metadata": "3.1.1"}}, "tests": {"src/tests/test_main.py::test_turckheim": [{"extras": [], "result": "Passed", "testId": "src/tests/test_main.py::test_turckheim", "resultsTableRow": ["<td class=\"col-result\">Passed</td>", "<td class=\"col-testId\">src/tests/test_main.py::test_turckheim</td>", "<td>start (0 | 0) - 3 - Mairie du 2e arrondissement (78 | 5) - 1 - Basilique Saint-Martin d'Ainay (406 | 5) - 3 - Chapelle Paul Couturier (109 | 5) - 1 - finish (0 | 0) - 0</td>", "<td>23 min</td>", "<td>20 min</td>", "<td class=\"col-duration\">97 ms</td>", "<td class=\"col-links\"></td>"], "log": "------------------------------ Captured log call -------------------------------\nDEBUG    asyncio:selector_events.py:64 Using selector: EpollSelector\nINFO     src.main:main.py:67 No end coordinates provided. Using start=end.\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:76 Starting to fetch landmarks...\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:88 Fetching sightseeing landmarks...\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:204 Fetched 16 landmarks of type sightseeing in (45.74643460077641, 4.819803369760263, 45.756693361321055, 4.834505559895031)\nINFO     src.utils.landmarks_manager:landmarks_manager.py:91 Found 16 sightseeing landmarks\nINFO     src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO     src.utils.cluster_manager:cluster_manager.py:145 Found 0 sightseeing clusters.\nINFO     src.main:main.py:104 Fetched 12 landmarks in  \t: 0.011 seconds\nDEBUG    src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO     src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 593\nDEBUG    src.optimization.refiner:refiner.py:345 Using 2 minor landmarks around the predicted path\nDEBUG    src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO     src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 593\nDEBUG    src.main:main.py:130 First stage optimization\t: 0.045 seconds\nDEBUG    src.main:main.py:131 Second stage optimization\t: 0.025 seconds\nINFO     src.main:main.py:132 Total computation time\t: 0.071 seconds\nINFO     src.main:main.py:137 Generated a trip of 23 minutes with 5 landmarks in 0.082 seconds.\nINFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/trip/new &quot;HTTP/1.1 200 OK&quot;\n\n"}]}, "renderCollapsed": ["passed"], "initialSort": "result", "title": "Backend Testing Report"}"></div> | ||||||
|     <script> |     <script> | ||||||
|       (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ |       (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||||||
| const { getCollapsedCategory, setCollapsedIds } = require('./storage.js') | const { getCollapsedCategory, setCollapsedIds } = require('./storage.js') | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| import os | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import List, Literal | from typing import List, Literal, Tuple | ||||||
|  |  | ||||||
|  |  | ||||||
| LOCATION_PREFIX = Path('src') | LOCATION_PREFIX = Path('src') | ||||||
| @@ -16,6 +16,7 @@ cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache') | |||||||
| OSM_CACHE_DIR = Path(cache_dir_string) | OSM_CACHE_DIR = Path(cache_dir_string) | ||||||
|  |  | ||||||
| OSM_TYPES = List[Literal['way', 'node', 'relation']] | OSM_TYPES = List[Literal['way', 'node', 'relation']] | ||||||
|  | BBOX = Tuple[float, float, float, float] | ||||||
|  |  | ||||||
| MEMCACHED_HOST_PATH = os.getenv('MEMCACHED_HOST_PATH', None) | MEMCACHED_HOST_PATH = os.getenv('MEMCACHED_HOST_PATH', None) | ||||||
| if MEMCACHED_HOST_PATH == "none": | if MEMCACHED_HOST_PATH == "none": | ||||||
|   | |||||||
| @@ -54,7 +54,8 @@ class JSONCache(CachingStrategyBase): | |||||||
|                 # Open and parse the cached JSON data |                 # Open and parse the cached JSON data | ||||||
|                 with open(filename, 'r', encoding='utf-8') as file: |                 with open(filename, 'r', encoding='utf-8') as file: | ||||||
|                     data = json.load(file) |                     data = json.load(file) | ||||||
|                 return data  # Return the parsed JSON data |                 # Return the data as a list of dicts. | ||||||
|  |                 return data | ||||||
|             except json.JSONDecodeError: |             except json.JSONDecodeError: | ||||||
|                 return None  # Return None if parsing fails |                 return None  # Return None if parsing fails | ||||||
|         return None |         return None | ||||||
|   | |||||||
| @@ -4,12 +4,14 @@ import urllib | |||||||
| import math | import math | ||||||
| import logging | import logging | ||||||
| import json | import json | ||||||
|  | from typing import List, Tuple | ||||||
|  |  | ||||||
| from .caching_strategy import get_cache_key, CachingStrategy | from .caching_strategy import get_cache_key, CachingStrategy | ||||||
| from ..constants import OSM_CACHE_DIR, OSM_TYPES | from ..constants import OSM_CACHE_DIR, OSM_TYPES, BBOX | ||||||
|  |  | ||||||
|  |  | ||||||
| RESOLUTION = 0.05 | RESOLUTION = 0.05 | ||||||
|  | CELL = Tuple[int, int] | ||||||
|  |  | ||||||
|  |  | ||||||
| class Overpass : | class Overpass : | ||||||
| @@ -29,8 +31,8 @@ class Overpass : | |||||||
|         self.caching_strategy = CachingStrategy.use(caching_strategy, cache_dir=cache_dir) |         self.caching_strategy = CachingStrategy.use(caching_strategy, cache_dir=cache_dir) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def send_query(self, bbox: tuple, osm_types: OSM_TYPES, |     def send_query(self, bbox: BBOX, osm_types: OSM_TYPES, | ||||||
|                     selector: str, conditions: list=None, out='center'): |                     selector: str, conditions: list=None, out='center') -> List[dict]: | ||||||
|         """ |         """ | ||||||
|         Sends the Overpass QL query to the Overpass API and returns the parsed json response. |         Sends the Overpass QL query to the Overpass API and returns the parsed json response. | ||||||
|  |  | ||||||
| @@ -42,28 +44,35 @@ class Overpass : | |||||||
|             out (str): Output format ('center', 'body', etc.). Defaults to 'center'. |             out (str): Output format ('center', 'body', etc.). Defaults to 'center'. | ||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             dict:    Parsed json response from the Overpass API, or cached data if available. |             list:   Parsed json response from the Overpass API, or cached data if available. | ||||||
|         """ |         """ | ||||||
|         # Determine which grid cells overlap with this bounding box. |         # Determine which grid cells overlap with this bounding box. | ||||||
|         overlapping_cells = Overpass._get_overlapping_cells(bbox) |         overlapping_cells = Overpass._get_overlapping_cells(bbox) | ||||||
|  |  | ||||||
|         # Retrieve cached data and identify missing cache entries |         # Retrieve cached data and identify missing cache entries | ||||||
|         cached_responses, hollow_cache_keys = self._retrieve_cached_data(overlapping_cells, osm_types, selector, conditions, out) |         cached_responses, non_cached_cells = self._retrieve_cached_data(overlapping_cells, osm_types, selector, conditions, out) | ||||||
|  |  | ||||||
|         # If there is no missing data, return the cached responses |         self.logger.info(f'Cache hit for {len(overlapping_cells)-len(non_cached_cells)}/{len(overlapping_cells)} quadrants.') | ||||||
|         if not hollow_cache_keys : |  | ||||||
|             self.logger.info(f'Cache hit for {len(cached_responses)} quadrants.') |  | ||||||
|             return self._combine_cached_data(cached_responses) |  | ||||||
|  |  | ||||||
|         # TODO If there is SOME missing data : hybrid stuff with partial cache |         # If there is no missing data, return the cached responses after filtering. | ||||||
|         self.logger.info(f'Cache miss for {len(hollow_cache_keys)} quadrants.') |         if not non_cached_cells : | ||||||
|  |             return Overpass._filter_landmarks(cached_responses, bbox) | ||||||
|  |  | ||||||
|         # Missing data: Make a query to Overpass API |         # If there is no cached data, fetch all from Overpass. | ||||||
|  |         elif not cached_responses : | ||||||
|             query_str = Overpass.build_query(bbox, osm_types, selector, conditions, out) |             query_str = Overpass.build_query(bbox, osm_types, selector, conditions, out) | ||||||
|             return self.fetch_data_from_api(query_str) |             return self.fetch_data_from_api(query_str) | ||||||
|  |  | ||||||
|  |         # Hybrid cache: some data from Overpass, some data from cache. | ||||||
|  |         else : | ||||||
|  |             # Resize the bbox for smaller search area and build new query string. | ||||||
|  |             non_cached_bbox = Overpass._get_non_cached_bbox(non_cached_cells, bbox) | ||||||
|  |             query_str = Overpass.build_query(non_cached_bbox, osm_types, selector, conditions, out) | ||||||
|  |             non_cached_responses = self.fetch_data_from_api(query_str) | ||||||
|  |             return Overpass._filter_landmarks(cached_responses, bbox) + non_cached_responses | ||||||
|  |  | ||||||
|     def fetch_data_from_api(self, query_str: str) -> list: |  | ||||||
|  |     def fetch_data_from_api(self, query_str: str) -> List[dict]: | ||||||
|         """ |         """ | ||||||
|         Fetch data from the Overpass API and return the json data. |         Fetch data from the Overpass API and return the json data. | ||||||
|  |  | ||||||
| @@ -117,7 +126,7 @@ class Overpass : | |||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def build_query(bbox: tuple, osm_types: OSM_TYPES, |     def build_query(bbox: BBOX, osm_types: OSM_TYPES, | ||||||
|                     selector: str, conditions: list=None, out='center') -> str: |                     selector: str, conditions: list=None, out='center') -> str: | ||||||
|         """ |         """ | ||||||
|         Constructs a query string for the Overpass API to retrieve OpenStreetMap (OSM) data. |         Constructs a query string for the Overpass API to retrieve OpenStreetMap (OSM) data. | ||||||
| @@ -160,9 +169,10 @@ class Overpass : | |||||||
|         return query |         return query | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _retrieve_cached_data(self, overlapping_cells: list, osm_types: OSM_TYPES, selector: str, conditions: list, out: str): |     def _retrieve_cached_data(self, overlapping_cells: CELL, osm_types: OSM_TYPES, | ||||||
|  |                               selector: str, conditions: list, out: str) -> Tuple[List[dict], list[CELL]]: | ||||||
|         """ |         """ | ||||||
|         Retrieve cached data and identify missing cache entries. |         Retrieve cached data and identify missing cache quadrants. | ||||||
|  |  | ||||||
|         Args: |         Args: | ||||||
|             overlapping_cells (list): Cells to check for cached data. |             overlapping_cells (list): Cells to check for cached data. | ||||||
| @@ -174,7 +184,7 @@ class Overpass : | |||||||
|         Returns: |         Returns: | ||||||
|             tuple: A tuple containing: |             tuple: A tuple containing: | ||||||
|                 - cached_responses (list): List of cached data found. |                 - cached_responses (list): List of cached data found. | ||||||
|                 - hollow_cache_keys (list): List of keys with missing data. |                 - non_cached_cells (list(tuple)): List of cells with missing data. | ||||||
|         """ |         """ | ||||||
|         cell_key_dict = {} |         cell_key_dict = {} | ||||||
|         for cell in overlapping_cells : |         for cell in overlapping_cells : | ||||||
| @@ -184,29 +194,29 @@ class Overpass : | |||||||
|             cell_key_dict[cell] = get_cache_key(key_str) |             cell_key_dict[cell] = get_cache_key(key_str) | ||||||
|  |  | ||||||
|         cached_responses = [] |         cached_responses = [] | ||||||
|         hollow_cache_keys = [] |         non_cached_cells = [] | ||||||
|  |  | ||||||
|         # Retrieve the cached data and mark the missing entries as hollow |         # Retrieve the cached data and mark the missing entries as hollow | ||||||
|         for cell, key in cell_key_dict.items(): |         for cell, key in cell_key_dict.items(): | ||||||
|             cached_data = self.caching_strategy.get(key) |             cached_data = self.caching_strategy.get(key) | ||||||
|             if cached_data is not None : |             if cached_data is not None : | ||||||
|                 cached_responses.append(cached_data) |                 cached_responses += cached_data | ||||||
|             else: |             else: | ||||||
|                 self.caching_strategy.set_hollow(key, cell, osm_types, selector, conditions, out) |                 self.caching_strategy.set_hollow(key, cell, osm_types, selector, conditions, out) | ||||||
|                 hollow_cache_keys.append(key) |                 non_cached_cells.append(cell) | ||||||
|  |  | ||||||
|         return cached_responses, hollow_cache_keys |         return cached_responses, non_cached_cells | ||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _build_query_from_hollow(json_data: dict): |     def _build_query_from_hollow(json_data: dict) -> Tuple[str, str]: | ||||||
|         """ |         """ | ||||||
|         Build query string using information from a hollow cache entry. |         Build query string using information from a hollow cache entry. | ||||||
|         """ |         """ | ||||||
|         # Extract values from the JSON object |         # Extract values from the JSON object | ||||||
|         key = json_data.get('key') |         key = json_data.get('key') | ||||||
|         cell = tuple(json_data.get('cell')) |         cell = tuple(json_data.get('cell')) | ||||||
|         bbox = Overpass._get_bbox_from_grid_cell(cell[0], cell[1]) |         bbox = Overpass._get_bbox_from_grid_cell(cell) | ||||||
|         osm_types = json_data.get('osm_types') |         osm_types = json_data.get('osm_types') | ||||||
|         selector = json_data.get('selector') |         selector = json_data.get('selector') | ||||||
|         conditions = json_data.get('conditions') |         conditions = json_data.get('conditions') | ||||||
| @@ -218,7 +228,7 @@ class Overpass : | |||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _get_overlapping_cells(query_bbox: tuple): |     def _get_overlapping_cells(query_bbox: tuple) -> List[CELL]: | ||||||
|         """ |         """ | ||||||
|         Returns a set of all grid cells that overlap with the given bounding box. |         Returns a set of all grid cells that overlap with the given bounding box. | ||||||
|         """ |         """ | ||||||
| @@ -237,7 +247,7 @@ class Overpass : | |||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _get_grid_cell(lat: float, lon: float): |     def _get_grid_cell(lat: float, lon: float) -> CELL: | ||||||
|         """ |         """ | ||||||
|         Returns the grid cell coordinates for a given latitude and longitude. |         Returns the grid cell coordinates for a given latitude and longitude. | ||||||
|         Each grid cell is 0.05°lat x 0.05°lon resolution in size. |         Each grid cell is 0.05°lat x 0.05°lon resolution in size. | ||||||
| @@ -248,7 +258,7 @@ class Overpass : | |||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _get_bbox_from_grid_cell(lat_index: int, lon_index: int): |     def _get_bbox_from_grid_cell(cell: CELL) -> BBOX: | ||||||
|         """ |         """ | ||||||
|         Returns the bounding box for a given grid cell index. |         Returns the bounding box for a given grid cell index. | ||||||
|         Each grid cell is resolution x resolution in size. |         Each grid cell is resolution x resolution in size. | ||||||
| @@ -256,26 +266,84 @@ class Overpass : | |||||||
|         The bounding box is returned as (min_lat, min_lon, max_lat, max_lon). |         The bounding box is returned as (min_lat, min_lon, max_lat, max_lon). | ||||||
|         """ |         """ | ||||||
|         # Calculate the southwest (min_lat, min_lon) corner of the bounding box |         # Calculate the southwest (min_lat, min_lon) corner of the bounding box | ||||||
|         min_lat = round(lat_index * RESOLUTION, 2) |         min_lat = round(cell[0] * RESOLUTION, 2) | ||||||
|         min_lon = round(lon_index * RESOLUTION, 2) |         min_lon = round(cell[1] * RESOLUTION, 2) | ||||||
|  |  | ||||||
|         # Calculate the northeast (max_lat, max_lon) corner of the bounding box |         # Calculate the northeast (max_lat, max_lon) corner of the bounding box | ||||||
|         max_lat = round((lat_index + 1) * RESOLUTION, 2) |         max_lat = round((cell[0] + 1) * RESOLUTION, 2) | ||||||
|         max_lon = round((lon_index + 1) * RESOLUTION, 2) |         max_lon = round((cell[1] + 1) * RESOLUTION, 2) | ||||||
|  |  | ||||||
|         return (min_lat, min_lon, max_lat, max_lon) |         return (min_lat, min_lon, max_lat, max_lon) | ||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _combine_cached_data(cached_data_list): |     def _get_non_cached_bbox(non_cached_cells: List[CELL], original_bbox: BBOX): | ||||||
|         """ |         """ | ||||||
|         Combines data from multiple cached responses into a single result. |         Calculate the non-cached bounding box by excluding cached cells. | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             non_cached_cells (list): The list of cells that were not found in the cache. | ||||||
|  |             original_bbox (tuple): The original bounding box (min_lat, min_lon, max_lat, max_lon). | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             tuple: The new bounding box that excludes cached cells, or None if all cells are cached. | ||||||
|         """ |         """ | ||||||
|         combined_data = [] |         if not non_cached_cells: | ||||||
|         for cached_data in cached_data_list: |             return None  # All cells were cached | ||||||
|             for element in cached_data: |  | ||||||
|                 combined_data.append(element) |         # Initialize the non-cached bounding box with extreme values | ||||||
|         return combined_data |         min_lat, min_lon, max_lat, max_lon = float('inf'), float('inf'), float('-inf'), float('-inf') | ||||||
|  |  | ||||||
|  |         # Iterate over non-cached cells to find the new bounding box | ||||||
|  |         for cell in non_cached_cells: | ||||||
|  |             cell_min_lat, cell_min_lon, cell_max_lat, cell_max_lon = Overpass._get_bbox_from_grid_cell(cell) | ||||||
|  |  | ||||||
|  |             min_lat = min(min_lat, cell_min_lat) | ||||||
|  |             min_lon = min(min_lon, cell_min_lon) | ||||||
|  |             max_lat = max(max_lat, cell_max_lat) | ||||||
|  |             max_lon = max(max_lon, cell_max_lon) | ||||||
|  |  | ||||||
|  |         # If no update to bounding box, return the original | ||||||
|  |         if min_lat == float('inf') or min_lon == float('inf'): | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |         return (max(min_lat, original_bbox[0]),  | ||||||
|  |                 max(min_lon, original_bbox[1]),  | ||||||
|  |                 min(max_lat, original_bbox[2]),  | ||||||
|  |                 min(max_lon, original_bbox[3])) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _filter_landmarks(elements: List[dict], bbox: BBOX) -> List[dict]: | ||||||
|  |         """ | ||||||
|  |         Filters elements based on whether their coordinates are inside the given bbox. | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |         - elements (list of dict): List of elements containing coordinates. | ||||||
|  |         - bbox (tuple): A bounding box defined as (min_lat, min_lon, max_lat, max_lon). | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         - list: A list of elements whose coordinates are inside the bounding box. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         filtered_elements = [] | ||||||
|  |         min_lat, min_lon, max_lat, max_lon = bbox | ||||||
|  |  | ||||||
|  |         for elem in elements: | ||||||
|  |             # Extract coordinates based on the 'type' of element | ||||||
|  |             if elem.get('type') != 'node': | ||||||
|  |                 center = elem.get('center', {}) | ||||||
|  |                 lat = float(center.get('lat', 0)) | ||||||
|  |                 lon = float(center.get('lon', 0)) | ||||||
|  |             else: | ||||||
|  |                 lat = float(elem.get('lat', 0)) | ||||||
|  |                 lon = float(elem.get('lon', 0)) | ||||||
|  |  | ||||||
|  |             # Check if the coordinates fall within the given bounding box | ||||||
|  |             if min_lat <= lat <= max_lat and min_lon <= lon <= max_lon: | ||||||
|  |                 filtered_elements.append(elem) | ||||||
|  |  | ||||||
|  |         return filtered_elements | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_base_info(elem: dict, osm_type: OSM_TYPES, with_name=False) : | def get_base_info(elem: dict, osm_type: OSM_TYPES, with_name=False) : | ||||||
|   | |||||||
| @@ -27,11 +27,13 @@ def test_turckheim(client, request):    # pylint: disable=redefined-outer-name | |||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|             "nature": {"type": "nature", "score": 5}, |             "nature": {"type": "nature", "score": 0}, | ||||||
|             "shopping": {"type": "shopping", "score": 0}, |             "shopping": {"type": "shopping", "score": 0}, | ||||||
|             "max_time_minute": duration_minutes, |             "max_time_minute": duration_minutes, | ||||||
|             "detour_tolerance_minute": 0}, |             "detour_tolerance_minute": 0}, | ||||||
|             "start": [48.084588, 7.280405] |             # "start": [48.084588, 7.280405] | ||||||
|  |             # "start": [45.74445023349939, 4.8222687890538865] | ||||||
|  |             "start": [45.75156398104873, 4.827154464827647] | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
| @@ -56,7 +58,7 @@ def test_turckheim(client, request):    # pylint: disable=redefined-outer-name | |||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" | ||||||
|     # assert 2!= 3 |     # assert 2!= 3 | ||||||
|  |  | ||||||
|  | ''' | ||||||
| def test_bellecour(client, request) :   # pylint: disable=redefined-outer-name | def test_bellecour(client, request) :   # pylint: disable=redefined-outer-name | ||||||
|     """ |     """ | ||||||
|     Test n°2 : Custom test in Lyon centre to ensure proper decision making in crowded area. |     Test n°2 : Custom test in Lyon centre to ensure proper decision making in crowded area. | ||||||
| @@ -342,3 +344,4 @@ def test_shopping(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" |     assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds" | ||||||
|     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" |     assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}" | ||||||
|     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" |     assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}" | ||||||
|  | ''' | ||||||
		Reference in New Issue
	
	Block a user