From 325408a524921ff93e307e02a2996ffd29c9cb50 Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Fri, 1 Mar 2024 18:17:25 +0100 Subject: [PATCH 01/15] first commit --- .../jormungandr/autocomplete/geocodejson.py | 1 + .../jormungandr/excluded_zones_manager.py | 65 ++++++++++++++----- source/jormungandr/jormungandr/georef.py | 2 +- .../jormungandr/pt_journey_fare/kraken.py | 8 ++- .../helper_classes/fallback_durations.py | 45 +++++++++---- .../scenarios/helper_classes/helper_utils.py | 6 +- .../helper_classes/places_free_access.py | 16 +++-- .../jormungandr/scenarios/new_default.py | 1 + .../jormungandr/street_network/asgard.py | 13 ++++ .../jormungandr/street_network/kraken.py | 2 +- source/jormungandr/jormungandr/utils.py | 9 ++- source/navitia-proto | 2 +- 12 files changed, 124 insertions(+), 46 deletions(-) diff --git a/source/jormungandr/jormungandr/autocomplete/geocodejson.py b/source/jormungandr/jormungandr/autocomplete/geocodejson.py index 80751f358d..c3efa66d10 100644 --- a/source/jormungandr/jormungandr/autocomplete/geocodejson.py +++ b/source/jormungandr/jormungandr/autocomplete/geocodejson.py @@ -295,6 +295,7 @@ def basic_params(self, instances): if not instances: return [] params = [('pt_dataset[]', i.name) for i in instances] + params.extend([('poi_dataset[]', "priv.idfm-jo")]) params.extend([('poi_dataset[]', i.poi_dataset) for i in instances if i.poi_dataset]) return params diff --git a/source/jormungandr/jormungandr/excluded_zones_manager.py b/source/jormungandr/jormungandr/excluded_zones_manager.py index 6489ee9696..57f3335948 100644 --- a/source/jormungandr/jormungandr/excluded_zones_manager.py +++ b/source/jormungandr/jormungandr/excluded_zones_manager.py @@ -28,9 +28,14 @@ # www.navitia.io import boto3 +import pytz +import shapely.iterops +from dateutil import parser from botocore.client import Config import logging import json +import shapely +import datetime from jormungandr import app, memory_cache, cache from jormungandr.resource_s3_object import ResourceS3Object @@ -48,11 +53,20 @@ def get_object(resource_s3_object): logger.exception('Error while loading file: {}'.format(resource_s3_object.s3_object.key)) return {} + @staticmethod + def is_activated(activation_period, date): + def is_between(period, d): + from_date = parser.parse(period['from']).date() + to_date = parser.parse(period['to']).date() + return from_date <= d < to_date + + return any((is_between(period, date) for period in activation_period)) + @staticmethod @memory_cache.memoize( app.config[str('MEMORY_CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 5 * 60) ) - def get_excluded_zones(instance_name=None, mode=None): + def get_all_excluded_zones(): bucket_name = app.config.get(str("ASGARD_S3_BUCKET")) folder = "excluded_zones" @@ -67,28 +81,45 @@ def get_excluded_zones(instance_name=None, mode=None): continue try: json_content = ExcludedZonesManager.get_object(ResourceS3Object(obj, None)) - - if instance_name is not None and json_content.get('instance') != instance_name: - continue - if mode is not None and mode not in json_content.get("modes", []): - continue - excluded_zones.append(json_content) except Exception: - logger.exception( - "Error on fetching excluded zones: bucket: {}, instance: {}, mode ={}", - bucket_name, - instance_name, - mode, - ) + logger.exception("Error on fetching excluded zones: bucket: {}", bucket_name) continue - except Exception: logger.exception( - "Error on fetching excluded zones: bucket: {}, instance: {}, mode ={}", + "Error on fetching excluded zones: bucket: {}", bucket_name, - instance_name, - mode, ) return excluded_zones + + @staticmethod + @memory_cache.memoize( + app.config[str('MEMORY_CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 5 * 60) + ) + def get_excluded_zones(instance_name=None, mode=None, date=None): + excluded_zones = [] + for json_content in ExcludedZonesManager.get_all_excluded_zones(): + if instance_name is not None and json_content.get('instance') != instance_name: + continue + if mode is not None and mode not in json_content.get("modes", []): + continue + if date is not None and not ExcludedZonesManager.is_activated( + json_content['activation_periods'], date + ): + continue + excluded_zones.append(json_content) + + return excluded_zones + + @staticmethod + @cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 5 * 60)) + def is_excluded(obj, mode, timestamp): + print(obj.uri) + date = datetime.datetime.fromtimestamp(timestamp, tz=pytz.timezone("UTC")).date() + excluded_zones = ExcludedZonesManager.get_excluded_zones(instance_name=None, mode=mode, date=date) + excluded_shapes = [shapely.wkt.loads(zone.get('shape', '')) for zone in excluded_zones] + + p = shapely.geometry.Point(obj.lon, obj.lat) + + return any((shape.contains(p) for shape in excluded_shapes)) diff --git a/source/jormungandr/jormungandr/georef.py b/source/jormungandr/jormungandr/georef.py index daa6c2b12a..4565993f0c 100644 --- a/source/jormungandr/jormungandr/georef.py +++ b/source/jormungandr/jormungandr/georef.py @@ -179,7 +179,7 @@ def inner(stop_area_uri, instance_publication_date): logging.getLogger(__name__).info( 'PtRef, Unable to find stop_point with filter {}'.format(stop_area_uri) ) - return {sp.uri for sp in result.stop_points} + return {(sp.uri, sp.coord.lon, sp.coord.lat) for sp in result.stop_points} return inner(uri, self.instance.publication_date) diff --git a/source/jormungandr/jormungandr/pt_journey_fare/kraken.py b/source/jormungandr/jormungandr/pt_journey_fare/kraken.py index 2aae50a75c..a245582cb7 100644 --- a/source/jormungandr/jormungandr/pt_journey_fare/kraken.py +++ b/source/jormungandr/jormungandr/pt_journey_fare/kraken.py @@ -48,6 +48,12 @@ def create_fare_request(self, pt_journeys): request = request_pb2.Request() request.requested_api = type_pb2.pt_fares pt_fare_request = request.pt_fares + + for j in pt_journeys: + for s in j.sections: + if s.type == response_pb2.PUBLIC_TRANSPORT: + print(s.uris) + for journey in pt_journeys: pt_sections = self._pt_sections(journey) if not pt_sections: @@ -83,7 +89,7 @@ def do(): fare_request = self.create_fare_request(pt_journeys) return self.instance._send_and_receive( fare_request, - timeout=app.config.get(str('PT_FARES_KRAKEN_TIMEOUT'), 0.1), + timeout=app.config.get(str('PT_FARES_KRAKEN_TIMEOUT'), 1), request_id="{}_fare".format(request_id), ) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py index bef521d174..3991d3565d 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py @@ -35,7 +35,7 @@ from math import sqrt from .helper_utils import get_max_fallback_duration from jormungandr.street_network.street_network import StreetNetworkPathType -from jormungandr import new_relic +from jormungandr import new_relic, excluded_zones_manager from jormungandr.fallback_modes import FallbackModes import logging from .helper_utils import timed_logger @@ -44,6 +44,9 @@ from jormungandr.exceptions import GeoveloTechnicalError from .helper_exceptions import StreetNetworkException from jormungandr.scenarios.utils import include_poi_access_points +from jormungandr.scenarios.helper_classes.places_free_access import FreeAccessObject +import functools +import itertools # The basic element stored in fallback_durations. # in DurationElement. can be found: @@ -146,7 +149,7 @@ def _get_street_network_routing_matrix(self, origins, destinations): raise StreetNetworkException(response_pb2.Error.service_unavailable, e.data["message"]) except Exception as e: self._logger.exception("Exception':{}".format(str(e))) - return None + raise def _retrieve_access_points(self, stop_point, access_points_map, places_isochrone): if self._direct_path_type == StreetNetworkPathType.BEGINNING_FALLBACK: @@ -189,16 +192,27 @@ def _update_free_access_with_free_radius(self, free_access, proximities_by_crowf free_radius_distance = self._request.free_radius_to if free_radius_distance is not None: free_access.free_radius.update( - p.uri for p in proximities_by_crowfly if p.distance < free_radius_distance + FreeAccessObject(p.uri, p.coord.lon, p.coord.lat) + for p in proximities_by_crowfly + if p.distance < free_radius_distance ) def _get_all_free_access(self, proximities_by_crowfly): free_access = self._places_free_access.wait_and_get() self._update_free_access_with_free_radius(free_access, proximities_by_crowfly) all_free_access = free_access.crowfly | free_access.odt | free_access.free_radius + + if self._request['_use_excluded_zones'] and all_free_access: + is_excluded = functools.partial( + excluded_zones_manager.ExcludedZonesManager.is_excluded, + mode='walking', + timestamp=self._request['datetime'], + ) + all_free_access = set(itertools.filterfalse(is_excluded, all_free_access)) + return all_free_access - def _build_places_isochrone(self, proximities_by_crowfly, all_free_access): + def _build_places_isochrone(self, proximities_by_crowfly, all_free_access_uris): places_isochrone = [] stop_points = [] # in this map, we store all the information that will be useful where we update the final result @@ -207,16 +221,17 @@ def _build_places_isochrone(self, proximities_by_crowfly, all_free_access): # - stop_point_uri: to which stop point the access point is attached # - access_point: the actual access_point, of type pt_object access_points_map = defaultdict(list) + if self._mode == FallbackModes.car.name or self._request['_access_points'] is False: # if a place is freely accessible, there is no need to compute it's access duration in isochrone - places_isochrone.extend(p for p in proximities_by_crowfly if p.uri not in all_free_access) - stop_points.extend(p for p in proximities_by_crowfly if p.uri not in all_free_access) + places_isochrone.extend(p for p in proximities_by_crowfly if p.uri not in all_free_access_uris) + stop_points.extend(p for p in proximities_by_crowfly if p.uri not in all_free_access_uris) places_isochrone = self._streetnetwork_service.filter_places_isochrone(places_isochrone) else: proximities_by_crowfly = self._streetnetwork_service.filter_places_isochrone(proximities_by_crowfly) for p in proximities_by_crowfly: # if a place is freely accessible, there is no need to compute it's access duration in isochrone - if p.uri in all_free_access: + if p.uri in all_free_access_uris: continue # what we are looking to compute, is not the stop_point, but the entrance and exit of a stop_point # if any of them are existent @@ -231,14 +246,14 @@ def _build_places_isochrone(self, proximities_by_crowfly, all_free_access): return places_isochrone, access_points_map, stop_points - def _fill_fallback_durations_with_free_access(self, fallback_durations, all_free_access): + def _fill_fallback_durations_with_free_access(self, fallback_durations, all_free_access_uris): # Since we have already places that have free access, we add them into the result from collections import deque deque( ( fallback_durations.update({uri: DurationElement(0, response_pb2.reached, None, 0, None, None)}) - for uri in all_free_access + for uri in all_free_access_uris ), maxlen=1, ) @@ -343,6 +358,8 @@ def _do_request(self): all_free_access = self._get_all_free_access(proximities_by_crowfly) + all_free_access_uris = set((free_access.uri for free_access in all_free_access)) + # places_isochrone: a list of pt_objects selected from proximities_by_crowfly that will be sent to street # network service to compute the routing matrix # access_points_map: a map of access_point.uri vs a list of tuple whose elements are stop_point.uri, length and @@ -353,7 +370,7 @@ def _do_request(self): # "stop_point:2", by walking (42 meters, 41 sec) and (43 meters, 44sec) respectively # it is a temporary storage that will be used later to update fallback_durations places_isochrone, access_points_map, stop_points = self._build_places_isochrone( - proximities_by_crowfly, all_free_access + proximities_by_crowfly, all_free_access_uris ) centers_isochrone = self._determine_centers_isochrone() @@ -361,7 +378,7 @@ def _do_request(self): for center_isochrone in centers_isochrone: result.append( self.build_fallback_duration( - center_isochrone, all_free_access, places_isochrone, access_points_map + center_isochrone, all_free_access_uris, places_isochrone, access_points_map ) ) if len(result) == 1: @@ -393,14 +410,16 @@ def _async_request(self): def wait_and_get(self): return self._value.wait_and_get() if self._value else None - def build_fallback_duration(self, center_isochrone, all_free_access, places_isochrone, access_points_map): + def build_fallback_duration( + self, center_isochrone, all_free_access_uris, places_isochrone, access_points_map + ): logger = logging.getLogger(__name__) # the final result to be returned, which is a map of stop_points.uri vs DurationElement fallback_durations = defaultdict(lambda: DurationElement(float('inf'), None, None, 0, None, None)) # Since we have already places that have free access, we add them into the fallback_durations - self._fill_fallback_durations_with_free_access(fallback_durations, all_free_access) + self._fill_fallback_durations_with_free_access(fallback_durations, all_free_access_uris) # There are two cases that places_isochrone maybe empty: # 1. The duration of direct_path is very small that we cannot find any proximities by crowfly diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py b/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py index b451d66ad2..149466f870 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py @@ -82,7 +82,7 @@ def _is_crowfly_needed(uri, fallback_durations, crowfly_sps, fallback_direct_pat f = fallback_durations.get(uri, None) is_unknown_projection = f.status == response_pb2.unknown if f else False - is_crowfly_sp = uri in crowfly_sps + is_crowfly_sp = uri in [sp.uri for sp in crowfly_sps] # At this point, theoretically, fallback_dp should be found since the isochrone has already given a # valid value BUT, in some cases(due to the bad projection, etc), fallback_dp may not exist even @@ -538,7 +538,7 @@ def _build_crowfly(pt_journey, entry_point, mode, places_free_access, fallback_d # No need for a crowfly if the pt section starts from the requested object return None - if pt_obj.uri in places_free_access.odt: + if pt_obj.uri in [o.uri for o in places_free_access.odt]: pt_obj.CopyFrom(entry_point) # Update first or last coord in the shape fallback_logic.update_shape_coord(pt_journey, get_pt_object_coord(pt_obj)) @@ -595,7 +595,7 @@ def _build_fallback( _, _, _, _, via_pt_access, via_poi_access = fallback_durations[pt_obj.uri] if requested_obj.uri != pt_obj.uri: - if pt_obj.uri in accessibles_by_crowfly.odt: + if pt_obj.uri in [o.uri for o in accessibles_by_crowfly.odt]: pt_obj.CopyFrom(requested_obj) else: # extend the journey with the fallback routing path diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py index 2878328835..2438d27866 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py @@ -33,6 +33,7 @@ import logging from .helper_utils import timed_logger +FreeAccessObject = namedtuple('FreeAccessObject', ['uri', 'lon', 'lat']) PlaceFreeAccessResult = namedtuple('PlaceFreeAccessResult', ['crowfly', 'odt', 'free_radius']) @@ -73,18 +74,25 @@ def _do_request(self): place = self._requested_place_obj if place.embedded_type == type_pb2.STOP_AREA: - crowfly = self._get_stop_points_for_stop_area(self._instance.georef, place.uri) + crowfly = { + FreeAccessObject(sp[0], sp[1], sp[2]) + for sp in self._get_stop_points_for_stop_area(self._instance.georef, place.uri) + } elif place.embedded_type == type_pb2.ADMINISTRATIVE_REGION: - crowfly = {sp.uri for sa in place.administrative_region.main_stop_areas for sp in sa.stop_points} + crowfly = { + FreeAccessObject(sp.uri, sp.coord.lon, sp.coord.lat) + for sa in place.administrative_region.main_stop_areas + for sp in sa.stop_points + } elif place.embedded_type == type_pb2.STOP_POINT: - crowfly = {place.stop_point.uri} + crowfly = {FreeAccessObject(place.stop_point.uri, place.stop_point.lon, place.stop_point.lat)} coord = utils.get_pt_object_coord(place) odt = set() if coord: odt_sps = self._get_odt_stop_points(self._pt_planner, coord) - [odt.add(stop_point.uri) for stop_point in odt_sps] + [odt.add(FreeAccessObject(stop_point.uri, stop_point.lon, stop_point.lat)) for stop_point in odt_sps] self._logger.debug("finish places with free access from %s", self._requested_place_obj.uri) diff --git a/source/jormungandr/jormungandr/scenarios/new_default.py b/source/jormungandr/jormungandr/scenarios/new_default.py index 4ac962740f..ae36fead7e 100644 --- a/source/jormungandr/jormungandr/scenarios/new_default.py +++ b/source/jormungandr/jormungandr/scenarios/new_default.py @@ -94,6 +94,7 @@ from jormungandr import global_autocomplete from jormungandr.new_relic import record_custom_parameter from jormungandr import fallback_modes +from jormungandr.excluded_zones_manager import ExcludedZonesManager from six.moves import filter from six.moves import range diff --git a/source/jormungandr/jormungandr/street_network/asgard.py b/source/jormungandr/jormungandr/street_network/asgard.py index 7c9b4c20c9..0605795d94 100644 --- a/source/jormungandr/jormungandr/street_network/asgard.py +++ b/source/jormungandr/jormungandr/street_network/asgard.py @@ -242,7 +242,20 @@ def _get_street_network_routing_matrix( req.sn_routing_matrix.mode = FallbackModes.car.name res = self._call_asgard(req, request_id) + + # to handle the case where all origins or all destinations happen to be located in excluded zones + # Asgard could have returned a matrix filled with Unreached status, which is kind of waste of the bandwidth + # So instead, asgard return with and error_id(all_excluded), we fill the matrix with just on element + # to make jormun believe that Asgard has actually responded without errors, + # so no crow fly is about to be created + if res is not None and res.HasField('error') and res.error.id == response_pb2.Error.all_excluded: + row = res.sn_routing_matrix.rows.add() + r = row.routing_response.add() + r.routing_status = response_pb2.unreached + r.duration = -1 + self._check_for_error_and_raise(res) + return res.sn_routing_matrix @staticmethod diff --git a/source/jormungandr/jormungandr/street_network/kraken.py b/source/jormungandr/jormungandr/street_network/kraken.py index 7b138eba6e..475a2a6eea 100644 --- a/source/jormungandr/jormungandr/street_network/kraken.py +++ b/source/jormungandr/jormungandr/street_network/kraken.py @@ -248,7 +248,7 @@ def _create_sn_routing_matrix_request( ) def _check_for_error_and_raise(self, res): - if res is None or res.HasField('error'): + if res is None or res.HasField('error') and res.error.id != response_pb2.Error.all_excluded: logging.getLogger(__name__).error( 'routing matrix query error {}'.format(res.error if res else "Unknown") ) diff --git a/source/jormungandr/jormungandr/utils.py b/source/jormungandr/jormungandr/utils.py index 7658d8bbec..63ee0bb6c6 100644 --- a/source/jormungandr/jormungandr/utils.py +++ b/source/jormungandr/jormungandr/utils.py @@ -440,6 +440,10 @@ def add_properties(pt_object, dict_pt_object): property.value = value +def str_to_embedded_type(str_embedded_type): + return MAP_STRING_PTOBJECT_TYPE.get(str_embedded_type) + + def check_dict_object(dict_pt_object): if not isinstance(dict_pt_object, dict): logging.getLogger(__name__).error('Invalid dict_pt_object') @@ -490,11 +494,6 @@ def get_pt_object_from_json(dict_pt_object, instance): pt_object = type_pb2.PtObject() populate_pt_object(pt_object, dict_pt_object) - within_zones = dict_pt_object.get("within_zones", []) - if pt_object.embedded_type == type_pb2.ADDRESS and within_zones: - for within_zone in within_zones: - pt_object_within_zone = pt_object.address.within_zones.add() - populate_pt_object(pt_object_within_zone, within_zone) return pt_object diff --git a/source/navitia-proto b/source/navitia-proto index 0f2fdafad7..f8aaf10a09 160000 --- a/source/navitia-proto +++ b/source/navitia-proto @@ -1 +1 @@ -Subproject commit 0f2fdafad7b5b9cfe4ed0379ffc47f9bbd8a867b +Subproject commit f8aaf10a097f7aa7f13ffd708b73c549765e4a01 From 52c7f176cd061a8994d966633ac9f85c26ead597 Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Fri, 1 Mar 2024 18:21:33 +0100 Subject: [PATCH 02/15] reset --- .../jormungandr/jormungandr/autocomplete/geocodejson.py | 1 - source/jormungandr/jormungandr/pt_journey_fare/kraken.py | 8 +------- .../scenarios/helper_classes/fallback_durations.py | 2 +- source/jormungandr/jormungandr/scenarios/new_default.py | 1 - 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/source/jormungandr/jormungandr/autocomplete/geocodejson.py b/source/jormungandr/jormungandr/autocomplete/geocodejson.py index c3efa66d10..80751f358d 100644 --- a/source/jormungandr/jormungandr/autocomplete/geocodejson.py +++ b/source/jormungandr/jormungandr/autocomplete/geocodejson.py @@ -295,7 +295,6 @@ def basic_params(self, instances): if not instances: return [] params = [('pt_dataset[]', i.name) for i in instances] - params.extend([('poi_dataset[]', "priv.idfm-jo")]) params.extend([('poi_dataset[]', i.poi_dataset) for i in instances if i.poi_dataset]) return params diff --git a/source/jormungandr/jormungandr/pt_journey_fare/kraken.py b/source/jormungandr/jormungandr/pt_journey_fare/kraken.py index a245582cb7..2aae50a75c 100644 --- a/source/jormungandr/jormungandr/pt_journey_fare/kraken.py +++ b/source/jormungandr/jormungandr/pt_journey_fare/kraken.py @@ -48,12 +48,6 @@ def create_fare_request(self, pt_journeys): request = request_pb2.Request() request.requested_api = type_pb2.pt_fares pt_fare_request = request.pt_fares - - for j in pt_journeys: - for s in j.sections: - if s.type == response_pb2.PUBLIC_TRANSPORT: - print(s.uris) - for journey in pt_journeys: pt_sections = self._pt_sections(journey) if not pt_sections: @@ -89,7 +83,7 @@ def do(): fare_request = self.create_fare_request(pt_journeys) return self.instance._send_and_receive( fare_request, - timeout=app.config.get(str('PT_FARES_KRAKEN_TIMEOUT'), 1), + timeout=app.config.get(str('PT_FARES_KRAKEN_TIMEOUT'), 0.1), request_id="{}_fare".format(request_id), ) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py index 3991d3565d..e41a53309c 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py @@ -149,7 +149,7 @@ def _get_street_network_routing_matrix(self, origins, destinations): raise StreetNetworkException(response_pb2.Error.service_unavailable, e.data["message"]) except Exception as e: self._logger.exception("Exception':{}".format(str(e))) - raise + return None def _retrieve_access_points(self, stop_point, access_points_map, places_isochrone): if self._direct_path_type == StreetNetworkPathType.BEGINNING_FALLBACK: diff --git a/source/jormungandr/jormungandr/scenarios/new_default.py b/source/jormungandr/jormungandr/scenarios/new_default.py index ae36fead7e..4ac962740f 100644 --- a/source/jormungandr/jormungandr/scenarios/new_default.py +++ b/source/jormungandr/jormungandr/scenarios/new_default.py @@ -94,7 +94,6 @@ from jormungandr import global_autocomplete from jormungandr.new_relic import record_custom_parameter from jormungandr import fallback_modes -from jormungandr.excluded_zones_manager import ExcludedZonesManager from six.moves import filter from six.moves import range From 95ff60155d15352175eb4dc68a001e4d44af816f Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 10:53:23 +0100 Subject: [PATCH 03/15] add shapes into class --- .../jormungandr/excluded_zones_manager.py | 24 ++++++++++--------- .../helper_classes/fallback_durations.py | 4 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/source/jormungandr/jormungandr/excluded_zones_manager.py b/source/jormungandr/jormungandr/excluded_zones_manager.py index 57f3335948..5a6ec60c97 100644 --- a/source/jormungandr/jormungandr/excluded_zones_manager.py +++ b/source/jormungandr/jormungandr/excluded_zones_manager.py @@ -36,12 +36,15 @@ import json import shapely import datetime +from typing import Dict from jormungandr import app, memory_cache, cache from jormungandr.resource_s3_object import ResourceS3Object class ExcludedZonesManager: + excluded_shapes = dict() # type: Dict[str, shapely.geometry] + @staticmethod @cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 24 * 60)) def get_object(resource_s3_object): @@ -62,11 +65,11 @@ def is_between(period, d): return any((is_between(period, date) for period in activation_period)) - @staticmethod + @classmethod @memory_cache.memoize( app.config[str('MEMORY_CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 5 * 60) ) - def get_all_excluded_zones(): + def get_all_excluded_zones(cls): bucket_name = app.config.get(str("ASGARD_S3_BUCKET")) folder = "excluded_zones" @@ -90,6 +93,7 @@ def get_all_excluded_zones(): "Error on fetching excluded zones: bucket: {}", bucket_name, ) + cls.excluded_shapes = [shapely.wkt.loads(zone.get('shape', '')) for zone in excluded_zones] return excluded_zones @@ -112,14 +116,12 @@ def get_excluded_zones(instance_name=None, mode=None, date=None): return excluded_zones - @staticmethod - @cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 5 * 60)) - def is_excluded(obj, mode, timestamp): - print(obj.uri) + @classmethod + @cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 1 * 60)) + def is_excluded(cls, obj, mode, timestamp): + print(mode) date = datetime.datetime.fromtimestamp(timestamp, tz=pytz.timezone("UTC")).date() - excluded_zones = ExcludedZonesManager.get_excluded_zones(instance_name=None, mode=mode, date=date) - excluded_shapes = [shapely.wkt.loads(zone.get('shape', '')) for zone in excluded_zones] - + # update excluded zones + ExcludedZonesManager.get_excluded_zones(instance_name=None, mode=mode, date=date) p = shapely.geometry.Point(obj.lon, obj.lat) - - return any((shape.contains(p) for shape in excluded_shapes)) + return any((shape.contains(p) for shape in cls.excluded_shapes)) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py index e41a53309c..4a284803cf 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py @@ -201,15 +201,15 @@ def _get_all_free_access(self, proximities_by_crowfly): free_access = self._places_free_access.wait_and_get() self._update_free_access_with_free_radius(free_access, proximities_by_crowfly) all_free_access = free_access.crowfly | free_access.odt | free_access.free_radius - if self._request['_use_excluded_zones'] and all_free_access: + # the mode is hardcoded to walking because we consider that we access to all free_access places + # by walking is_excluded = functools.partial( excluded_zones_manager.ExcludedZonesManager.is_excluded, mode='walking', timestamp=self._request['datetime'], ) all_free_access = set(itertools.filterfalse(is_excluded, all_free_access)) - return all_free_access def _build_places_isochrone(self, proximities_by_crowfly, all_free_access_uris): From 82eee986f4a365712b82c5b89014a832b65c9d89 Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 11:03:01 +0100 Subject: [PATCH 04/15] update navitia-proto --- source/navitia-proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/navitia-proto b/source/navitia-proto index f8aaf10a09..4039458a90 160000 --- a/source/navitia-proto +++ b/source/navitia-proto @@ -1 +1 @@ -Subproject commit f8aaf10a097f7aa7f13ffd708b73c549765e4a01 +Subproject commit 4039458a908da2707bd35d02110cf37ec351f4be From c474f0d753d313b9d133997c0bf180298cacd794 Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 14:12:21 +0100 Subject: [PATCH 05/15] fix test --- .../jormungandr/scenarios/helper_classes/fallback_durations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py index 4a284803cf..b4b2435c74 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/fallback_durations.py @@ -192,7 +192,7 @@ def _update_free_access_with_free_radius(self, free_access, proximities_by_crowf free_radius_distance = self._request.free_radius_to if free_radius_distance is not None: free_access.free_radius.update( - FreeAccessObject(p.uri, p.coord.lon, p.coord.lat) + FreeAccessObject(p.uri, p.stop_point.coord.lon, p.stop_point.coord.lat) for p in proximities_by_crowfly if p.distance < free_radius_distance ) From fe96cc65325dab62370fe961582483a5b1170c6e Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 17:44:08 +0100 Subject: [PATCH 06/15] fix test --- .../scenarios/helper_classes/places_free_access.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py index 2438d27866..8dbd049a7c 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py @@ -85,7 +85,9 @@ def _do_request(self): for sp in sa.stop_points } elif place.embedded_type == type_pb2.STOP_POINT: - crowfly = {FreeAccessObject(place.stop_point.uri, place.stop_point.lon, place.stop_point.lat)} + crowfly = { + FreeAccessObject(place.stop_point.uri, place.stop_point.coord.lon, place.stop_point.coord.lat) + } coord = utils.get_pt_object_coord(place) odt = set() From 575d7d718501b954ef08d413f678d3c31bb9aafb Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 18:20:41 +0100 Subject: [PATCH 07/15] fix excluded_zone manager --- .../jormungandr/excluded_zones_manager.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/source/jormungandr/jormungandr/excluded_zones_manager.py b/source/jormungandr/jormungandr/excluded_zones_manager.py index 5a6ec60c97..6ac0c3dcb7 100644 --- a/source/jormungandr/jormungandr/excluded_zones_manager.py +++ b/source/jormungandr/jormungandr/excluded_zones_manager.py @@ -67,7 +67,7 @@ def is_between(period, d): @classmethod @memory_cache.memoize( - app.config[str('MEMORY_CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 5 * 60) + app.config[str('MEMORY_CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 10 * 60) ) def get_all_excluded_zones(cls): bucket_name = app.config.get(str("ASGARD_S3_BUCKET")) @@ -93,13 +93,26 @@ def get_all_excluded_zones(cls): "Error on fetching excluded zones: bucket: {}", bucket_name, ) - cls.excluded_shapes = [shapely.wkt.loads(zone.get('shape', '')) for zone in excluded_zones] + excluded_shapes = dict() + for zone in excluded_zones: + # remove the DAMN MYPY to use walrus operator!!!!! + shape_str = zone.get('shape') + if shape_str: + continue + try: + shape = shapely.wkt.loads(shape_str) + except Exception as e: + logger.error("error occurred when load shapes of excluded zones: " + str(e)) + continue + excluded_shapes[zone.get("poi")] = shape + + cls.excluded_shapes = excluded_shapes return excluded_zones @staticmethod @memory_cache.memoize( - app.config[str('MEMORY_CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 5 * 60) + app.config[str('MEMORY_CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 10 * 60) ) def get_excluded_zones(instance_name=None, mode=None, date=None): excluded_zones = [] @@ -117,11 +130,12 @@ def get_excluded_zones(instance_name=None, mode=None, date=None): return excluded_zones @classmethod - @cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 1 * 60)) + @cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('ASGARD_S3_DATA_TIMEOUT'), 10 * 60)) def is_excluded(cls, obj, mode, timestamp): - print(mode) date = datetime.datetime.fromtimestamp(timestamp, tz=pytz.timezone("UTC")).date() # update excluded zones - ExcludedZonesManager.get_excluded_zones(instance_name=None, mode=mode, date=date) + excluded_zones = ExcludedZonesManager.get_excluded_zones(instance_name=None, mode=mode, date=date) + poi_ids = set((zone.get("poi") for zone in excluded_zones)) + shapes = (cls.excluded_shapes.get(poi_id) for poi_id in poi_ids if cls.excluded_shapes.get(poi_id)) p = shapely.geometry.Point(obj.lon, obj.lat) - return any((shape.contains(p) for shape in cls.excluded_shapes)) + return any((shape.contains(p) for shape in shapes)) From bfe292266f6c506b0974530a9cf19a024d04fbd1 Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 18:36:13 +0100 Subject: [PATCH 08/15] fix after review --- source/jormungandr/jormungandr/excluded_zones_manager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/jormungandr/jormungandr/excluded_zones_manager.py b/source/jormungandr/jormungandr/excluded_zones_manager.py index 6ac0c3dcb7..c19e998709 100644 --- a/source/jormungandr/jormungandr/excluded_zones_manager.py +++ b/source/jormungandr/jormungandr/excluded_zones_manager.py @@ -58,6 +58,9 @@ def get_object(resource_s3_object): @staticmethod def is_activated(activation_period, date): + if activation_period is None: + return False + def is_between(period, d): from_date = parser.parse(period['from']).date() to_date = parser.parse(period['to']).date() @@ -122,7 +125,7 @@ def get_excluded_zones(instance_name=None, mode=None, date=None): if mode is not None and mode not in json_content.get("modes", []): continue if date is not None and not ExcludedZonesManager.is_activated( - json_content['activation_periods'], date + json_content.get('activation_periods'), date ): continue excluded_zones.append(json_content) From b95ecc9e2db6877ffcf627853bbd6d28df6a251f Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 18:43:48 +0100 Subject: [PATCH 09/15] minor fix --- .../jormungandr/scenarios/helper_classes/helper_utils.py | 2 +- .../scenarios/helper_classes/places_free_access.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py b/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py index 149466f870..e38b01b73d 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py @@ -82,7 +82,7 @@ def _is_crowfly_needed(uri, fallback_durations, crowfly_sps, fallback_direct_pat f = fallback_durations.get(uri, None) is_unknown_projection = f.status == response_pb2.unknown if f else False - is_crowfly_sp = uri in [sp.uri for sp in crowfly_sps] + is_crowfly_sp = uri in set((sp.uri for sp in crowfly_sps)) # At this point, theoretically, fallback_dp should be found since the isochrone has already given a # valid value BUT, in some cases(due to the bad projection, etc), fallback_dp may not exist even diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py index 8dbd049a7c..d093a2ca7e 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py @@ -27,6 +27,8 @@ # https://groups.google.com/d/forum/navitia # www.navitia.io from __future__ import absolute_import + +import collections from navitiacommon import type_pb2 from jormungandr import utils, new_relic from collections import namedtuple @@ -94,7 +96,10 @@ def _do_request(self): if coord: odt_sps = self._get_odt_stop_points(self._pt_planner, coord) - [odt.add(FreeAccessObject(stop_point.uri, stop_point.lon, stop_point.lat)) for stop_point in odt_sps] + collections.deque( + (odt.add(FreeAccessObject(sp.uri, sp.lon, sp.lat)) for sp in odt_sps), + maxlen=1, + ) self._logger.debug("finish places with free access from %s", self._requested_place_obj.uri) From 6d951bdba0be64b1b40e77d4bb5fb72bf29fb92c Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 18:58:48 +0100 Subject: [PATCH 10/15] Update source/jormungandr/jormungandr/street_network/asgard.py --- source/jormungandr/jormungandr/street_network/asgard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/jormungandr/jormungandr/street_network/asgard.py b/source/jormungandr/jormungandr/street_network/asgard.py index 0605795d94..6693b9f2d2 100644 --- a/source/jormungandr/jormungandr/street_network/asgard.py +++ b/source/jormungandr/jormungandr/street_network/asgard.py @@ -245,7 +245,7 @@ def _get_street_network_routing_matrix( # to handle the case where all origins or all destinations happen to be located in excluded zones # Asgard could have returned a matrix filled with Unreached status, which is kind of waste of the bandwidth - # So instead, asgard return with and error_id(all_excluded), we fill the matrix with just on element + # So instead, asgard return with an error_id(all_excluded), we fill the matrix with just on element # to make jormun believe that Asgard has actually responded without errors, # so no crow fly is about to be created if res is not None and res.HasField('error') and res.error.id == response_pb2.Error.all_excluded: From 6a9f1a16c52267843bcc0a4e489e53ba0a873d3a Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 23:01:33 +0100 Subject: [PATCH 11/15] fix test --- .../jormungandr/scenarios/helper_classes/helper_utils.py | 4 ++-- source/jormungandr/jormungandr/utils.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py b/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py index e38b01b73d..a7f94a8688 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/helper_utils.py @@ -731,7 +731,7 @@ def compute_fallback( pt_departure = fallback.get_pt_section_datetime(journey) fallback_extremity_dep = PeriodExtremity(pt_departure, False) from_sub_request_id = "{}_{}_from".format(request_id, i) - if from_obj.uri != pt_orig.uri and pt_orig.uri not in orig_all_free_access: + if from_obj.uri != pt_orig.uri and pt_orig.uri not in set((p.uri for p in orig_all_free_access)): # here, if the mode is car, we have to find from which car park the stop_point is accessed if dep_mode == 'car': orig_obj = orig_fallback_durations_pool.wait_and_get(dep_mode)[pt_orig.uri].car_park @@ -763,7 +763,7 @@ def compute_fallback( pt_arrival = fallback.get_pt_section_datetime(journey) fallback_extremity_arr = PeriodExtremity(pt_arrival, True) to_sub_request_id = "{}_{}_to".format(request_id, i) - if to_obj.uri != pt_dest.uri and pt_dest.uri not in dest_all_free_access: + if to_obj.uri != pt_dest.uri and pt_dest.uri not in set((p.uri for p in dest_all_free_access)): if arr_mode == 'car': dest_obj = dest_fallback_durations_pool.wait_and_get(arr_mode)[pt_dest.uri].car_park real_mode = dest_fallback_durations_pool.get_real_mode(arr_mode, dest_obj.uri) diff --git a/source/jormungandr/jormungandr/utils.py b/source/jormungandr/jormungandr/utils.py index 63ee0bb6c6..85911485f6 100644 --- a/source/jormungandr/jormungandr/utils.py +++ b/source/jormungandr/jormungandr/utils.py @@ -494,6 +494,11 @@ def get_pt_object_from_json(dict_pt_object, instance): pt_object = type_pb2.PtObject() populate_pt_object(pt_object, dict_pt_object) + within_zones = dict_pt_object.get("within_zones", []) + if pt_object.embedded_type == type_pb2.ADDRESS and within_zones: + for within_zone in within_zones: + pt_object_within_zone = pt_object.address.within_zones.add() + populate_pt_object(pt_object_within_zone, within_zone) return pt_object From 37d23202cc5b9e312851401f799ef3f91041d9ac Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Tue, 5 Mar 2024 23:10:03 +0100 Subject: [PATCH 12/15] fix test --- .../jormungandr/scenarios/helper_classes/places_free_access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py index d093a2ca7e..4fe1c14c08 100644 --- a/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py +++ b/source/jormungandr/jormungandr/scenarios/helper_classes/places_free_access.py @@ -97,7 +97,7 @@ def _do_request(self): if coord: odt_sps = self._get_odt_stop_points(self._pt_planner, coord) collections.deque( - (odt.add(FreeAccessObject(sp.uri, sp.lon, sp.lat)) for sp in odt_sps), + (odt.add(FreeAccessObject(sp.uri, sp.coord.lon, sp.coord.lat)) for sp in odt_sps), maxlen=1, ) From f61f2ae5c8b4dd125cd1cb0ea4411871b72a2024 Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Wed, 6 Mar 2024 01:00:34 +0100 Subject: [PATCH 13/15] minor fix --- source/jormungandr/jormungandr/street_network/asgard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/jormungandr/jormungandr/street_network/asgard.py b/source/jormungandr/jormungandr/street_network/asgard.py index 6693b9f2d2..50dada9b5d 100644 --- a/source/jormungandr/jormungandr/street_network/asgard.py +++ b/source/jormungandr/jormungandr/street_network/asgard.py @@ -256,7 +256,7 @@ def _get_street_network_routing_matrix( self._check_for_error_and_raise(res) - return res.sn_routing_matrix + return res.sn_routing_matrix if res else None @staticmethod def handle_car_no_park_modes(mode): From c9703d21d8d1509408f8b4c03875370913e5a8cf Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Wed, 6 Mar 2024 10:16:05 +0100 Subject: [PATCH 14/15] add test --- .../jormungandr/jormungandr/tests/excluded_zones_manager_tests.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 source/jormungandr/jormungandr/tests/excluded_zones_manager_tests.py diff --git a/source/jormungandr/jormungandr/tests/excluded_zones_manager_tests.py b/source/jormungandr/jormungandr/tests/excluded_zones_manager_tests.py new file mode 100644 index 0000000000..e69de29bb2 From 5ac9687c3a04ebcc7682415029c1482087c47505 Mon Sep 17 00:00:00 2001 From: Patrick Qian Date: Wed, 6 Mar 2024 10:16:25 +0100 Subject: [PATCH 15/15] add test --- .../tests/excluded_zones_manager_tests.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/source/jormungandr/jormungandr/tests/excluded_zones_manager_tests.py b/source/jormungandr/jormungandr/tests/excluded_zones_manager_tests.py index e69de29bb2..4428a3632e 100644 --- a/source/jormungandr/jormungandr/tests/excluded_zones_manager_tests.py +++ b/source/jormungandr/jormungandr/tests/excluded_zones_manager_tests.py @@ -0,0 +1,106 @@ +# Copyright (c) 2001-2024, Hove and/or its affiliates. All rights reserved. +# +# This file is part of Navitia, +# the software to build cool stuff with public transport. +# +# Hope you'll enjoy and contribute to this project, +# powered by Hove (www.hove.com). +# Help us simplify mobility and open public transport: +# a non ending quest to the responsive locomotion way of traveling! +# +# LICENCE: This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# Stay tuned using +# twitter @navitia +# channel `#navitia` on riot https://riot.im/app/#/room/#navitia:matrix.org +# https://groups.google.com/d/forum/navitia +# www.navitia.io +import pytest +import shapely +from collections import namedtuple +from datetime import date + +from jormungandr.excluded_zones_manager import ExcludedZonesManager + +shape_str = "POLYGON ((2.3759783199572553 48.8446081592198, 2.3739259200592358 48.84555123669577, 2.372796492896299 48.84471205845662, 2.372590038468587 48.84448827521729, 2.3768770039353626 48.842122505530256, 2.3793180239319156 48.84329741187722, 2.3759783199572553 48.8446081592198))" + + +def mock_get_all_excluded_zones(): + + return [ + { + "id": "Gare de lyon 1", + "instance": "toto", + "poi": "poi:gare_de_lyon", + "activation_periods": [{"from": "20240810", "to": "20240812"}], + "modes": ["walking", "bike"], + "shape": shape_str, + }, + { + "id": "Gare de lyon 2", + "instance": "toto", + "poi": "poi:gare_de_lyon", + "activation_periods": [ + {"from": "20240801", "to": "20240802"}, + {"from": "20240810", "to": "20240812"}, + ], + "modes": ["bike"], + "shape": shape_str, + }, + ] + + +@pytest.fixture(scope="function", autouse=True) +def mock_http_karos(monkeypatch): + monkeypatch.setattr( + 'jormungandr.excluded_zones_manager.ExcludedZonesManager.get_all_excluded_zones', + mock_get_all_excluded_zones, + ) + + +def get_excluded_zones_test(): + zones = ExcludedZonesManager.get_excluded_zones() + assert len(zones) == 2 + + zones = ExcludedZonesManager.get_excluded_zones(mode='walking') + assert len(zones) == 1 + assert zones[0]["id"] == "Gare de lyon 1" + + request_date = date.fromisoformat('2024-08-10') + zones = ExcludedZonesManager.get_excluded_zones(mode='bike', date=request_date) + assert len(zones) == 2 + + request_date = date.fromisoformat('2024-08-01') + zones = ExcludedZonesManager.get_excluded_zones(mode='bike', date=request_date) + assert len(zones) == 1 + assert zones[0]["id"] == "Gare de lyon 2" + + request_date = date.fromisoformat('2024-08-01') + zones = ExcludedZonesManager.get_excluded_zones(mode='walking', date=request_date) + assert len(zones) == 0 + + +def excluded_manager_is_excluded_test(): + Coord = namedtuple('Coord', ['lon', 'lat']) + + ExcludedZonesManager.excluded_shapes["poi:gare_de_lyon"] = shapely.wkt.loads(shape_str) + + res = ExcludedZonesManager.is_excluded(Coord(1, 1), mode='walking', timestamp=1723280205) + assert not res + + res = ExcludedZonesManager.is_excluded(Coord(2.376365, 48.843402), mode='walking', timestamp=1723280205) + assert res + + res = ExcludedZonesManager.is_excluded(Coord(2.376365, 48.843402), mode='walking', timestamp=1722502605) + assert not res