From cf0e511352eb3d0e880da45b3c06f65d3bd33c3a Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Fri, 6 Aug 2021 01:28:56 +0530 Subject: [PATCH 01/10] migrate isoline routing api --- here_location_services/isoline_routing_api.py | 34 +++++--- here_location_services/ls.py | 44 ++++++---- here_location_services/utils.py | 2 + tests/test_ls.py | 86 +++++++++++++------ tests/test_ls_apis.py | 7 +- tests/test_utils.py | 1 + 6 files changed, 112 insertions(+), 62 deletions(-) diff --git a/here_location_services/isoline_routing_api.py b/here_location_services/isoline_routing_api.py index c7917dc..fc12236 100644 --- a/here_location_services/isoline_routing_api.py +++ b/here_location_services/isoline_routing_api.py @@ -25,17 +25,17 @@ def __init__( country: str = "row", ): super().__init__(api_key, auth=auth, proxies=proxies, country=country) - self._base_url = f"https://isoline.route.ls.{self._get_url_string()}" + self._base_url = f"https://isoline.router.{self._get_url_string()}" def get_isoline_routing( self, - mode: str, + transportMode: str, range: str, range_type: str, - start: Optional[List[float]] = None, + origin: Optional[List[float]] = None, destination: Optional[List[float]] = None, arrival: Optional[str] = None, - departure: Optional[str] = None, + departureTime: Optional[str] = None, ) -> requests.Response: """Get isoline routing. @@ -69,21 +69,27 @@ def get_isoline_routing( :return: :class:`requests.Response` object. :raises ApiError: If ``status_code`` of API response is not 200. """ - path = "routing/7.2/calculateisoline.json" + path = "v8/isolines" url = f"{self._base_url}/{path}" params: Dict[str, str] = { - "range": range, - "rangetype": range_type, - "mode": mode, + # "range": range, + # "rangetype": range_type, + # "range": { + # "type": range_type, + # "values": range, + # } + "range[type]": range_type, + "range[values]": range, + "transportMode": transportMode, } - if start: - params["start"] = f"geo!{start[0]},{start[1]}" + if origin: + params["origin"] = f"{origin[0]},{origin[1]}" if destination: - params["destination"] = f"geo!{destination[0]},{destination[1]}" + params["destination"] = f"{destination[0]},{destination[1]}" if arrival: - params["arrival"] = arrival - if departure: - params["departure"] = departure + params["arrivalTime"] = arrival + if departureTime: + params["departureTime"] = departureTime resp = self.get(url, params=params, proxies=self.proxies) if resp.status_code == 200: return resp diff --git a/here_location_services/ls.py b/here_location_services/ls.py index c97afdb..cbbbad8 100644 --- a/here_location_services/ls.py +++ b/here_location_services/ls.py @@ -91,7 +91,9 @@ def __init__( api_key=api_key, auth=auth, proxies=proxies, country=country ) - def geocode(self, query: str, limit: int = 20, lang: str = "en-US") -> GeocoderResponse: + def geocode( + self, query: str, limit: int = 20, lang: str = "en-US" + ) -> GeocoderResponse: """Calculate coordinates as result of geocoding for the given ``query``. :param query: A string containing the input query. @@ -130,18 +132,20 @@ def reverse_geocode( if not -180 <= lng <= 180: raise ValueError("Longitude must be in range -180 to 180.") - resp = self.geo_search_api.get_reverse_geocoding(lat=lat, lng=lng, limit=limit, lang=lang) + resp = self.geo_search_api.get_reverse_geocoding( + lat=lat, lng=lng, limit=limit, lang=lang + ) return ReverseGeocoderResponse.new(resp.json()) def calculate_isoline( self, - mode: str, + transportMode: str, range: str, range_type: str, - start: Optional[List[float]] = None, + origin: Optional[List[float]] = None, destination: Optional[List[float]] = None, arrival: Optional[str] = None, - departure: Optional[str] = None, + departureTime: Optional[str] = None, ) -> IsolineResponse: """Calculate isoline routing. @@ -176,25 +180,25 @@ def calculate_isoline( :return: :class:`IsolineResponse` object. """ - if start and destination: - raise ValueError("`start` and `destination` can not be provided together.") - if start is None and destination is None: - raise ValueError("please provide either `start` or `destination`.") - if departure and start is None: - raise ValueError("`departure` must be provided with `start`") + if origin and destination: + raise ValueError("`origin` and `destination` can not be provided together.") + if origin is None and destination is None: + raise ValueError("please provide either `origin` or `destination`.") + if departureTime and origin is None: + raise ValueError("`departureTime` must be provided with `origin`") if arrival and destination is None: raise ValueError("`arrival` must be provided with `destination`") resp = self.isoline_routing_api.get_isoline_routing( - mode=mode, + transportMode=transportMode, range=range, range_type=range_type, - start=start, + origin=origin, destination=destination, arrival=arrival, - departure=departure, + departureTime=departureTime, ) - response = resp.json()["response"] + response = resp.json() return IsolineResponse.new(response) def discover( @@ -650,7 +654,11 @@ def matrix( self, origins: List[Dict], region_definition: Union[ - CircleRegion, BoundingBoxRegion, PolygonRegion, AutoCircleRegion, WorldRegion + CircleRegion, + BoundingBoxRegion, + PolygonRegion, + AutoCircleRegion, + WorldRegion, ], async_req: bool = False, destinations: Optional[List[Dict]] = None, @@ -737,7 +745,9 @@ def matrix( ) status_url = resp["statusUrl"] while True: - resp_status = self.matrix_routing_api.get_async_matrix_route_status(status_url) + resp_status = self.matrix_routing_api.get_async_matrix_route_status( + status_url + ) if resp_status.status_code == 200 and resp_status.json().get("error"): raise ApiError(resp_status) elif resp_status.status_code == 303: diff --git a/here_location_services/utils.py b/here_location_services/utils.py index bc94db9..9793d89 100644 --- a/here_location_services/utils.py +++ b/here_location_services/utils.py @@ -16,6 +16,8 @@ def get_apikey() -> str: :return: The string value of the environment variable or an empty string if no such variable could be found. """ + os.environ["LS_API_KEY"] = "TKURWbpJHcbzmWMrmTZwGAdSTLrX8WY1oddj4S4U-EM" + api_key = os.environ.get("LS_API_KEY") if api_key is None: warnings.warn("No token found in environment variable LS_API_KEY.") diff --git a/tests/test_ls.py b/tests/test_ls.py index 4b6a48f..8559306 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -23,7 +23,9 @@ Truck, WorldRegion, ) -from here_location_services.config.routing_config import AVOID_FEATURES as ROUTING_AVOID_FEATURES +from here_location_services.config.routing_config import ( + AVOID_FEATURES as ROUTING_AVOID_FEATURES, +) from here_location_services.config.routing_config import ( ROUTE_COURSE, ROUTE_MATCH_SIDEOF_STREET, @@ -103,14 +105,16 @@ def test_isonline_routing(): """Test isonline routing api.""" ls = LS(api_key=LS_API_KEY) result = ls.calculate_isoline( - start=[52.5, 13.4], - range="900", + origin=[52.5, 13.4], + range="3000", range_type="time", - mode="fastest;car;", - departure="2020-05-04T17:00:00+02", + transportMode="car", + departureTime="2021-08-05T18:53:27+00:00", ) - + print(result.as_json_string()) + print(json.loads(result.as_json_string)) coordinates = result.isoline[0]["component"][0]["shape"] + assert coordinates[0] geo_json = result.to_geojson() assert geo_json.type == "Feature" @@ -120,7 +124,7 @@ def test_isonline_routing(): destination=[52.5, 13.4], range="900", range_type="time", - mode="fastest;car;", + transportMode="car", arrival="2020-05-04T17:00:00+02", ) coordinates = result2.isoline[0]["component"][0]["shape"] @@ -128,19 +132,19 @@ def test_isonline_routing(): with pytest.raises(ValueError): ls.calculate_isoline( - start=[52.5, 13.4], + origin=[52.5, 13.4], range="900", range_type="time", - mode="fastest;car;", + transportMode="car", destination=[52.5, 13.4], ) with pytest.raises(ApiError): ls2 = LS(api_key="dummy") ls2.calculate_isoline( - start=[52.5, 13.4], + origin=[52.5, 13.4], range="900", range_type="time", - mode="fastest;car;", + transportMode="car", ) @@ -152,22 +156,22 @@ def test_isonline_routing_exception(): ls.calculate_isoline( range="900", range_type="time", - mode="fastest;car;", + transportMode="car", ) with pytest.raises(ValueError): ls.calculate_isoline( range="900", range_type="time", - mode="fastest;car;", + transportMode="car", arrival="2020-05-04T17:00:00+02", - start=[52.5, 13.4], + origin=[52.5, 13.4], ) with pytest.raises(ValueError): ls.calculate_isoline( range="900", range_type="time", - mode="fastest;car;", - departure="2020-05-04T17:00:00+02", + transportMode="car", + departureTime="2020-05-04T17:00:00+02", destination=[52.5, 13.4], ) @@ -175,7 +179,9 @@ def test_isonline_routing_exception(): @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_ls_discover(): ls = LS(api_key=LS_API_KEY) - result = ls.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en") + result = ls.discover( + query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en" + ) assert len(result.items) == 20 result2 = ls.discover( @@ -201,7 +207,9 @@ def test_ls_discover(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10) + ls2.discover( + query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10 + ) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") @@ -268,14 +276,18 @@ def test_ls_lookup(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.lookup(location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2") + ls2.lookup( + location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2" + ) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_car_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] + avoid_areas = [ + AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + ] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via1 = Via(lat=52.52426, lng=13.43000) via2 = Via(lat=52.52624, lng=13.44012) @@ -290,12 +302,16 @@ def test_car_route(): avoid_features=avoid_features, exclude=["IND", "NZL", "AUS"], ) - assert result.response["routes"][0]["sections"][0]["departure"]["place"]["location"] == { + assert result.response["routes"][0]["sections"][0]["departure"]["place"][ + "location" + ] == { "lat": 52.5137479, "lng": 13.4246242, "elv": 76.0, } - assert result.response["routes"][0]["sections"][1]["departure"]["place"]["location"] == { + assert result.response["routes"][0]["sections"][1]["departure"]["place"][ + "location" + ] == { "lat": 52.5242323, "lng": 13.4301462, "elv": 80.0, @@ -322,7 +338,10 @@ def test_car_route_extra_options(): "minCourseDistance": 10, } via_waypoint_options = WayPointOptions(stop_duration=0, pass_through=True) - assert json.loads(via_waypoint_options.__str__()) == {"stopDuration": 0, "passThrough": True} + assert json.loads(via_waypoint_options.__str__()) == { + "stopDuration": 0, + "passThrough": True, + } dest_waypoint_options = WayPointOptions(stop_duration=10, pass_through=False) via1 = Via( lat=52.52426, @@ -367,7 +386,9 @@ def test_car_route_extra_options(): def test_bicycle_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] + avoid_areas = [ + AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + ] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.bicycle_route( @@ -397,7 +418,9 @@ def test_truck_route(): tunnel_category="B", axle_count=4, ) - avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] + avoid_areas = [ + AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + ] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.truck_route( @@ -501,7 +524,9 @@ def test_matrix_route(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + avoid_areas = AvoidBoundingBox( + 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 + ) assert json.loads(avoid_areas.__str__()) == { "type": "boundingBox", "north": 68.1766451354, @@ -563,7 +588,9 @@ def test_matrix_route_async(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + avoid_areas = AvoidBoundingBox( + 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 + ) truck = Truck( shipped_hazardous_goods=[SHIPPED_HAZARDOUS_GOODS.explosive], gross_weight=100, @@ -612,7 +639,10 @@ def test_matrix_routing_config(): } poly = PolygonRegion(outer=[1, 1, 1, 1, 1, 1]) - assert json.loads(poly.__str__()) == {"type": "polygon", "outer": [1, 1, 1, 1, 1, 1]} + assert json.loads(poly.__str__()) == { + "type": "polygon", + "outer": [1, 1, 1, 1, 1, 1], + } autocircle = AutoCircleRegion(margin=100) assert json.loads(autocircle.__str__()) == {"type": "autoCircle", "margin": 100} diff --git a/tests/test_ls_apis.py b/tests/test_ls_apis.py index 952b6dc..ceaf928 100644 --- a/tests/test_ls_apis.py +++ b/tests/test_ls_apis.py @@ -35,10 +35,10 @@ def test_reverse_geocoding(geo_search_api): def test_isonline_routing(isoline_routing_api): """Test isonline routing api.""" result = isoline_routing_api.get_isoline_routing( - start=[52.5, 13.4], range="900", range_type="time", mode="fastest;car;" + origin=[52.5, 13.4], range="3000", range_type="distance", transportMode="car" ) - coordinates = result.json()["response"]["isoline"][0]["component"][0]["shape"] + coordinates = result.json()["isolines"][0]["polygons"][0]["outer"] assert coordinates[0] @@ -46,7 +46,8 @@ def test_mock_api_error(mocker): """Mock Test for geocoding api.""" mock_response = Namespace(status_code=300) mocker.patch( - "here_location_services.matrix_routing_api.requests.post", return_value=mock_response + "here_location_services.matrix_routing_api.requests.post", + return_value=mock_response, ) origins = [ {"lat": 37.76, "lng": -122.42}, diff --git a/tests/test_utils.py b/tests/test_utils.py index 378dd17..ff00cad 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,6 +10,7 @@ def test_get_apikey(): """Test ``get_apikey`` returns empty string when environment variable ``LS_API_KEY`` is not set. """ + os.environ["LS_API_KEY"] = "TKURWbpJHcbzmWMrmTZwGAdSTLrX8WY1oddj4S4U-EM" api_key = os.environ.get("LS_API_KEY") del os.environ["LS_API_KEY"] key = get_apikey() From 45f29d37d424d9746f1e23c2b63ecb6a24d17bb3 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Sat, 7 Aug 2021 18:19:48 +0530 Subject: [PATCH 02/10] fix response type --- here_location_services/isoline_routing_api.py | 6 ---- here_location_services/responses.py | 33 +++++++++++-------- here_location_services/utils.py | 2 -- tests/test_ls.py | 18 +++++----- tests/test_utils.py | 1 - 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/here_location_services/isoline_routing_api.py b/here_location_services/isoline_routing_api.py index fc12236..187fab3 100644 --- a/here_location_services/isoline_routing_api.py +++ b/here_location_services/isoline_routing_api.py @@ -72,12 +72,6 @@ def get_isoline_routing( path = "v8/isolines" url = f"{self._base_url}/{path}" params: Dict[str, str] = { - # "range": range, - # "rangetype": range_type, - # "range": { - # "type": range_type, - # "values": range, - # } "range[type]": range_type, "range[values]": range, "transportMode": transportMode, diff --git a/here_location_services/responses.py b/here_location_services/responses.py index a5beecc..2c69f1a 100644 --- a/here_location_services/responses.py +++ b/here_location_services/responses.py @@ -24,9 +24,9 @@ def __str__(self): def as_json_string(self, encoding: str = "utf8"): """Return API response as json string.""" - json_string = json.dumps(self.response, sort_keys=True, ensure_ascii=False).encode( - encoding - ) + json_string = json.dumps( + self.response, sort_keys=True, ensure_ascii=False + ).encode(encoding) return json_string.decode() def to_geojson(self): @@ -34,7 +34,8 @@ def to_geojson(self): feature_collection = FeatureCollection([]) for item in self.response["items"]: f = Feature( - geometry=Point((item["position"]["lng"], item["position"]["lat"])), properties=item + geometry=Point((item["position"]["lng"], item["position"]["lat"])), + properties=item, ) feature_collection.features.append(f) return feature_collection @@ -73,18 +74,22 @@ class IsolineResponse(ApiResponse): def __init__(self, **kwargs): super().__init__() - self._filters = {"metaInfo": None, "center": None, "isoline": None, "start": None} + self._filters = {"departure": None, "isolines": None} for param, default in self._filters.items(): setattr(self, param, kwargs.get(param, default)) def to_geojson(self): """Return API response as GeoJSON.""" - points = [] - for latlons in self.isoline[0]["component"][0]["shape"]: - latlon = [float(i) for i in latlons.split(",")] - points.append((latlon[1], latlon[0])) - feature = Feature(geometry=Polygon([points])) - return feature + feature_collection = FeatureCollection([]) + for isoline in self.response["isolines"]: + for polygon in isoline["polygons"]: + polyline = polygon["outer"] + lstring = fp.decode(polyline) + lstring = [(coord[1], coord[0]) for coord in lstring] + f = Feature(geometry=LineString(lstring), properties=polygon) + feature_collection.features.append(f) + print(feature_collection) + return feature_collection class DiscoverResponse(ApiResponse): @@ -158,7 +163,8 @@ def to_distnaces_matrix(self): distances = self.matrix.get("distances") dest_count = self.matrix.get("numDestinations") nested_distances = [ - distances[i : i + dest_count] for i in range(0, len(distances), dest_count) + distances[i : i + dest_count] + for i in range(0, len(distances), dest_count) ] return DataFrame(nested_distances, columns=range(dest_count)) @@ -168,6 +174,7 @@ def to_travel_times_matrix(self): distances = self.matrix.get("travelTimes") dest_count = self.matrix.get("numDestinations") nested_distances = [ - distances[i : i + dest_count] for i in range(0, len(distances), dest_count) + distances[i : i + dest_count] + for i in range(0, len(distances), dest_count) ] return DataFrame(nested_distances, columns=range(dest_count)) diff --git a/here_location_services/utils.py b/here_location_services/utils.py index 9793d89..bc94db9 100644 --- a/here_location_services/utils.py +++ b/here_location_services/utils.py @@ -16,8 +16,6 @@ def get_apikey() -> str: :return: The string value of the environment variable or an empty string if no such variable could be found. """ - os.environ["LS_API_KEY"] = "TKURWbpJHcbzmWMrmTZwGAdSTLrX8WY1oddj4S4U-EM" - api_key = os.environ.get("LS_API_KEY") if api_key is None: warnings.warn("No token found in environment variable LS_API_KEY.") diff --git a/tests/test_ls.py b/tests/test_ls.py index 8559306..4b950e6 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -111,21 +111,21 @@ def test_isonline_routing(): transportMode="car", departureTime="2021-08-05T18:53:27+00:00", ) - print(result.as_json_string()) - print(json.loads(result.as_json_string)) - coordinates = result.isoline[0]["component"][0]["shape"] - assert coordinates[0] + print(result) + # print(result.isolines[0]["polygons"][0]["outer"]) + coordinates = result.isolines[0]["polygons"][0]["outer"] + + assert coordinates geo_json = result.to_geojson() - assert geo_json.type == "Feature" - assert geo_json.geometry.type == "Polygon" + assert geo_json.type == "FeatureCollection" result2 = ls.calculate_isoline( destination=[52.5, 13.4], range="900", range_type="time", transportMode="car", - arrival="2020-05-04T17:00:00+02", + arrival="2021-08-05T18:53:27+00:00", ) coordinates = result2.isoline[0]["component"][0]["shape"] assert coordinates[0] @@ -163,7 +163,7 @@ def test_isonline_routing_exception(): range="900", range_type="time", transportMode="car", - arrival="2020-05-04T17:00:00+02", + arrival="2021-08-05T18:53:27+00:00", origin=[52.5, 13.4], ) with pytest.raises(ValueError): @@ -171,7 +171,7 @@ def test_isonline_routing_exception(): range="900", range_type="time", transportMode="car", - departureTime="2020-05-04T17:00:00+02", + departureTime="2021-08-05T18:53:27+00:00", destination=[52.5, 13.4], ) diff --git a/tests/test_utils.py b/tests/test_utils.py index ff00cad..378dd17 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,7 +10,6 @@ def test_get_apikey(): """Test ``get_apikey`` returns empty string when environment variable ``LS_API_KEY`` is not set. """ - os.environ["LS_API_KEY"] = "TKURWbpJHcbzmWMrmTZwGAdSTLrX8WY1oddj4S4U-EM" api_key = os.environ.get("LS_API_KEY") del os.environ["LS_API_KEY"] key = get_apikey() From 96fd0b53ee2387ccdf32da1783e64e9974284be4 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Sat, 7 Aug 2021 18:28:51 +0530 Subject: [PATCH 03/10] fix tests --- tests/test_ls.py | 55 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/tests/test_ls.py b/tests/test_ls.py index 4b950e6..dc7d483 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -113,39 +113,40 @@ def test_isonline_routing(): ) print(result) - # print(result.isolines[0]["polygons"][0]["outer"]) + assert result.isolines + assert result.departure coordinates = result.isolines[0]["polygons"][0]["outer"] assert coordinates geo_json = result.to_geojson() assert geo_json.type == "FeatureCollection" - result2 = ls.calculate_isoline( - destination=[52.5, 13.4], - range="900", - range_type="time", - transportMode="car", - arrival="2021-08-05T18:53:27+00:00", - ) - coordinates = result2.isoline[0]["component"][0]["shape"] - assert coordinates[0] - - with pytest.raises(ValueError): - ls.calculate_isoline( - origin=[52.5, 13.4], - range="900", - range_type="time", - transportMode="car", - destination=[52.5, 13.4], - ) - with pytest.raises(ApiError): - ls2 = LS(api_key="dummy") - ls2.calculate_isoline( - origin=[52.5, 13.4], - range="900", - range_type="time", - transportMode="car", - ) + # result2 = ls.calculate_isoline( + # destination=[52.5, 13.4], + # range="900", + # range_type="time", + # transportMode="car", + # arrival="2021-08-05T18:53:27+00:00", + # ) + # coordinates = result2.isoline[0]["component"][0]["shape"] + # assert coordinates[0] + + # with pytest.raises(ValueError): + # ls.calculate_isoline( + # origin=[52.5, 13.4], + # range="900", + # range_type="time", + # transportMode="car", + # destination=[52.5, 13.4], + # ) + # with pytest.raises(ApiError): + # ls2 = LS(api_key="dummy") + # ls2.calculate_isoline( + # origin=[52.5, 13.4], + # range="900", + # range_type="time", + # transportMode="car", + # ) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") From 87a3ef83d20a368db540ae53c2aaac94252c6055 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Sun, 8 Aug 2021 02:04:40 +0530 Subject: [PATCH 04/10] fix api parameters --- here_location_services/config/base_config.py | 183 ++++++++++++++++++ .../config/isoline_routing_config.py | 70 +++++++ .../config/matrix_routing_config.py | 79 +------- .../config/routing_config.py | 107 +--------- here_location_services/isoline_routing_api.py | 76 +++++--- here_location_services/ls.py | 38 ++-- here_location_services/matrix_routing_api.py | 21 +- here_location_services/responses.py | 3 +- here_location_services/routing_api.py | 9 +- tests/test_ls.py | 77 ++++---- tests/test_ls_auth_token.py | 8 +- 11 files changed, 400 insertions(+), 271 deletions(-) create mode 100644 here_location_services/config/isoline_routing_config.py diff --git a/here_location_services/config/base_config.py b/here_location_services/config/base_config.py index a86ff6a..56e9eca 100644 --- a/here_location_services/config/base_config.py +++ b/here_location_services/config/base_config.py @@ -6,6 +6,9 @@ various APIs. """ +import json +from typing import List, Optional + class Bunch(dict): """A class for dot notation implementation of dictionary.""" @@ -13,3 +16,183 @@ class Bunch(dict): def __init__(self, **kwargs): dict.__init__(self, kwargs) self.__dict__ = self + + +class RoutingMode(Bunch): + """A Class to define constant values for Routing Modes. + + ``fast``: + Route calculation from start to destination optimized by travel time. In many cases, the route + returned by the fast mode may not be the route with the fastest possible travel time. + For example, the routing service may favor a route that remains on a highway, even + if a faster travel time can be achieved by taking a detour or shortcut through + an inconvenient side road. + + ``short``: + Route calculation from start to destination disregarding any speed information. In this mode, + the distance of the route is minimized, while keeping the route sensible. This includes, + for example, penalizing turns. Because of that, the resulting route will not necessarily be + the one with minimal distance. + """ + + +#: Use this config for routing_mode of routing API. +#: Example: for ``fast`` routing_mode use ``ROUTING_MODE.fast``. +ROUTING_MODE = RoutingMode(**{"fast": "fast", "short": "short"}) + + +class ShippedHazardousGoods(Bunch): + """A class to define the constant values for truck option ``shippedHazardousGoods``.""" + + +#: Use this config for shipped_hazardous_goods attribute of Truck options of matrix Routing API. +#: Example: for ``explosive`` shipped_hazardous_goods use ``SHIPPED_HAZARDOUS_GOODS.explosive``. +SHIPPED_HAZARDOUS_GOODS = ShippedHazardousGoods( + **{ + "explosive": "explosive", + "gas": "gas", + "flammable": "flammable", + "combustible": "combustible", + "organic": "organic", + "poison": "poison", + "radioactive": "radioactive", + "corrosive": "corrosive", + "poisonousInhalation": "poisonousInhalation", + "harmfulToWater": "harmfulToWater", + "other": "other", + } +) + + +class Truck: + """A class to define different truck options which will be used during route calculation. + Truck options should be used when transportMode is ``truck``. + """ + + def __init__( + self, + shipped_hazardous_goods: Optional[List] = None, + gross_weight: Optional[int] = None, + weight_per_axle: Optional[int] = None, + height: Optional[int] = None, + width: Optional[int] = None, + length: Optional[int] = None, + tunnel_category: Optional[str] = None, + axle_count: Optional[int] = None, + truck_type: str = "straight", + trailer_count: int = 0, + ): + """Object Initializer. + + :param shipped_hazardous_goods: List of hazardous materials in the vehicle. valid values + for hazardous materials can be used from config + :attr:`SHIPPED_HAZARDOUS_GOODS ` + :param gross_weight: Total vehicle weight, including trailers and shipped goods, in + kilograms. Should be greater than or equal to zero. + :param weight_per_axle: Vehicle weight per axle, in kilograms. Should be greater than or + equal to zero. + :param height: Vehicle height, in centimeters. Should be in range [0, 5000] + :param width: Vehicle width, in centimeters. Should be in range [0, 5000] + :param length: Vehicle length, in centimeters. Should be in range [0, 5000] + :param tunnel_category: A string for category of tunnel. Valid values are "B", "C", "D", "E". + Specifies the `cargo tunnel restriction code `_. + The route will pass only through tunnels of less restrictive categories. + :param axle_count: Total number of axles that the vehicle has. Should be in the + range [2, 255]. + :param truck_type: A string to represent the type of truck. + :param trailer_count: Number of trailers attached to the vehicle. + """ # noqa E501 + self.shippedHazardousGoods = shipped_hazardous_goods + self.grossWeight = gross_weight + self.weightPerAxle = weight_per_axle + self.height = height + self.width = width + self.length = length + self.tunnelCategory = tunnel_category + self.axleCount = axle_count + self.type = truck_type + self.trailerCount = trailer_count + + +class PlaceOptions: + """A class to define ``PlaceOptions`` for ``origin``/ ``via``/ ``destination``. + + Various options can be found here: + + `PlaceOptions `_. + """ # noqa E501 + + def __init__( + self, + course: Optional[int] = None, + sideof_street_hint: Optional[List[float]] = None, + match_sideof_street: Optional[str] = None, + namehint: Optional[str] = None, + radius: Optional[int] = None, + min_course_distance: Optional[int] = None, + ): + """Object Initializer. + + :param course: An int representing degrees clock-wise from north. + Indicating the desired direction at the place. E.g. 90 indicating ``east``. + This is defined in constant ``ROUTE_COURSE``. + :param sideof_street_hint: A list of latitude and longitude.Indicating the side of the + street that should be used. + :param match_sideof_street: Specifies how the location set by ``sideof_street_hint`` should + be handled. If this is set then sideof_street_hint should also be set. There are two + valid values for match_sideof_street: + + ``always``: + Always prefer the given side of street. + + ``onlyIfDivided``: + Only prefer using side of street set by ``sideof_street_hint`` in case the street + has dividers. This is the default behavior. + + These values are mainted as config in: + :attr:`ROUTE_MATCH_SIDEOF_STREET ` + :param namehint: A string for the router to look for the place with the most similar name. + This can e.g. include things like: North being used to differentiate between + interstates I66 North and I66 South, Downtown Avenue being used to correctly + select a residential street. + :param radius: In meters Asks the router to consider all places within the given radius as + potential candidates for route calculation. This can be either because it is not + important which place is used, or because it is unknown. Radius more than 200 meters + are not supported. + :param min_course_distance: In meters Asks the routing service to try to find a route that + avoids actions for the indicated distance. E.g. if the origin is determined by a moving + vehicle, the user might not have time to react to early actions. + """ # noqa E501 + self.course = course + self.sideOfStreetHint: Optional[str] = None + if sideof_street_hint is not None: + self.sideOfStreetHint = ",".join( + [str(point) for point in sideof_street_hint] + ) + self.matchSideOfStreet = match_sideof_street + self.namehint = namehint + self.radius = radius + self.minCourseDistance = min_course_distance + + def __repr__(self): + """Return string representation of this instance.""" + return json.dumps(self.__dict__) + + +class WayPointOptions: + """A class to define ``PlaceOptions`` for ``via``/ ``destination``. + + Various options can be found here: + + `PlaceOptions `_. + """ # noqa E501 + + def __init__( + self, stop_duration: Optional[int] = None, pass_through: Optional[bool] = None + ): + self.stopDuration = stop_duration + self.passThrough = pass_through + + def __repr__(self): + """Return string representation of this instance.""" + return json.dumps(self.__dict__) diff --git a/here_location_services/config/isoline_routing_config.py b/here_location_services/config/isoline_routing_config.py new file mode 100644 index 0000000..1d4209c --- /dev/null +++ b/here_location_services/config/isoline_routing_config.py @@ -0,0 +1,70 @@ +# Copyright (C) 2019-2021 HERE Europe B.V. +# SPDX-License-Identifier: Apache-2.0 + +"""This module defines all the configs which will be required as inputs to Isoline routing API.""" + +import json +from typing import List, Optional + +from .base_config import Bunch + + +class IsolineRoutingTransportMode(Bunch): + """A class to define constant attributes for mode of transport to be used for the + calculation of the route. + + * ``car`` + * ``truck`` + * ``pedestrian`` + """ + + +transport_mode = { + "car": "car", + "truck": "truck", + "pedestrian": "pedestrian", +} + +#: Use this config for transport_mode of isoline routing API. +#: Example: for ``car`` transportMode use ``ISOLINE_ROUTING_TRANSPORT_MODE.car``. +ISOLINE_ROUTING_TRANSPORT_MODE = IsolineRoutingTransportMode(**transport_mode) + + +class OptimisedFor(Bunch): + """A Class to define constant values for optimising calculation for Isoline Routings Api + + ``quality``: + Calculation of isoline focuses on quality, that is, the graph used for isoline calculation has higher granularity generating an isoline that is more precise. + + ``performance``: + Calculation of isoline is performance-centric, quality of isoline is reduced to provide better performance. + + ``balanced``: + Calculation of isoline takes a balanced approach averaging between quality and performance. + """ + + +#: Use this config s optimised_for of isoline routing API. +#: Example: for optimising for ``balanced`` mode use ``OPTIMISED_FOR.balanced``. +OPTIMISED_FOR = OptimisedFor( + **{"quality": "quality", "performance": "performance", "balanced": "balanced"} +) + + +class AvoidFeatures(Bunch): + """A class to define constant values for features to avoid during isoline calculation.""" + + +#: Use this config for avoid_features of isoline API. +#: Example: for ``tollRoad`` avoid_features use ``AVOID_FEATURES.tollRoad``. +AVOID_FEATURES = AvoidFeatures( + **{ + "tollRoad": "tollRoad", + "controlledAccessHighway": "controlledAccessHighway", + "ferry": "ferry", + "carShuttleTrain": "carShuttleTrain", + "tunnel": "tunnel", + "dirtRoad": "dirtRoad", + "difficultTurns": "difficultTurns", + } +) diff --git a/here_location_services/config/matrix_routing_config.py b/here_location_services/config/matrix_routing_config.py index 2b351d0..5647d20 100644 --- a/here_location_services/config/matrix_routing_config.py +++ b/here_location_services/config/matrix_routing_config.py @@ -3,7 +3,7 @@ """This module defines all the configs which will be required as inputs to matrix Routing API.""" import json -from typing import Dict, List, Optional +from typing import Dict, List from .base_config import Bunch @@ -108,7 +108,9 @@ class MatrixAttributes(Bunch): #: Use this config for matrix_attributes of matrix Routing API. #: Example: for ``travelTimes`` matrix_attributes use ``MATRIX_ATTRIBUTES.travelTimes``. -MATRIX_ATTRIBUTES = MatrixAttributes(**{"travelTimes": "travelTimes", "distances": "distances"}) +MATRIX_ATTRIBUTES = MatrixAttributes( + **{"travelTimes": "travelTimes", "distances": "distances"} +) class AvoidFeatures(Bunch): @@ -141,76 +143,3 @@ def __init__(self, north: float, south: float, west: float, east: float): def __repr__(self): """Return string representation of this instance.""" return json.dumps(self.__dict__) - - -class ShippedHazardousGoods(Bunch): - """A class to define the constant values for truck option ``shippedHazardousGoods``.""" - - -#: Use this config for shipped_hazardous_goods attribute of Truck options of matrix Routing API. -#: Example: for ``explosive`` shipped_hazardous_goods use ``SHIPPED_HAZARDOUS_GOODS.explosive``. -SHIPPED_HAZARDOUS_GOODS = ShippedHazardousGoods( - **{ - "explosive": "explosive", - "gas": "gas", - "flammable": "flammable", - "combustible": "combustible", - "organic": "organic", - "poison": "poison", - "radioactive": "radioactive", - "corrosive": "corrosive", - "poisonousInhalation": "poisonousInhalation", - "harmfulToWater": "harmfulToWater", - "other": "other", - } -) - - -class Truck: - """A class to define different truck options which will be used during route calculation. - Truck options should be used when transportMode is ``truck``. - """ - - def __init__( - self, - shipped_hazardous_goods: Optional[List] = None, - gross_weight: Optional[int] = None, - weight_per_axle: Optional[int] = None, - height: Optional[int] = None, - width: Optional[int] = None, - length: Optional[int] = None, - tunnel_category: Optional[str] = None, - axle_count: Optional[int] = None, - truck_type: str = "straight", - trailer_count: int = 0, - ): - """Object Initializer. - - :param shipped_hazardous_goods: List of hazardous materials in the vehicle. valid values - for hazardous materials can be used from config - :attr:`SHIPPED_HAZARDOUS_GOODS ` - :param gross_weight: Total vehicle weight, including trailers and shipped goods, in - kilograms. Should be greater than or equal to zero. - :param weight_per_axle: Vehicle weight per axle, in kilograms. Should be greater than or - equal to zero. - :param height: Vehicle height, in centimeters. Should be in range [0, 5000] - :param width: Vehicle width, in centimeters. Should be in range [0, 5000] - :param length: Vehicle length, in centimeters. Should be in range [0, 5000] - :param tunnel_category: A string for category of tunnel. Valid values are "B", "C", "D", "E". - Specifies the `cargo tunnel restriction code `_. - The route will pass only through tunnels of less restrictive categories. - :param axle_count: Total number of axles that the vehicle has. Should be in the - range [2, 255]. - :param truck_type: A string to represent the type of truck. - :param trailer_count: Number of trailers attached to the vehicle. - """ # noqa E501 - self.shippedHazardousGoods = shipped_hazardous_goods - self.grossWeight = gross_weight - self.weightPerAxle = weight_per_axle - self.height = height - self.width = width - self.length = length - self.tunnelCategory = tunnel_category - self.axleCount = axle_count - self.type = truck_type - self.trailerCount = trailer_count diff --git a/here_location_services/config/routing_config.py b/here_location_services/config/routing_config.py index 563681a..29011f6 100644 --- a/here_location_services/config/routing_config.py +++ b/here_location_services/config/routing_config.py @@ -4,32 +4,9 @@ """This module defines all the configs which will be required as inputs to routing APIs.""" import json -from typing import List, Optional +from typing import Optional -from .base_config import Bunch - - -class RoutingMode(Bunch): - """A Class to define constant values for Routing Modes. - - ``fast``: - Route calculation from start to destination optimized by travel time. In many cases, the route - returned by the fast mode may not be the route with the fastest possible travel time. - For example, the routing service may favor a route that remains on a highway, even - if a faster travel time can be achieved by taking a detour or shortcut through - an inconvenient side road. - - ``short``: - Route calculation from start to destination disregarding any speed information. In this mode, - the distance of the route is minimized, while keeping the route sensible. This includes, - for example, penalizing turns. Because of that, the resulting route will not necessarily be - the one with minimal distance. - """ - - -#: Use this config for routing_mode of routing API. -#: Example: for ``fast`` routing_mode use ``ROUTING_MODE.fast``. -ROUTING_MODE = RoutingMode(**{"fast": "fast", "short": "short"}) +from .base_config import Bunch, PlaceOptions, WayPointOptions class RoutingReturn(Bunch): @@ -197,86 +174,6 @@ class AvoidFeatures(Bunch): ) -class PlaceOptions: - """A class to define ``PlaceOptions`` for ``origin``/ ``via``/ ``destination``. - - Various options can be found here: - - `PlaceOptions `_. - """ # noqa E501 - - def __init__( - self, - course: Optional[int] = None, - sideof_street_hint: Optional[List[float]] = None, - match_sideof_street: Optional[str] = None, - namehint: Optional[str] = None, - radius: Optional[int] = None, - min_course_distance: Optional[int] = None, - ): - """Object Initializer. - - :param course: An int representing degrees clock-wise from north. - Indicating the desired direction at the place. E.g. 90 indicating ``east``. - This is defined in constant ``ROUTE_COURSE``. - :param sideof_street_hint: A list of latitude and longitude.Indicating the side of the - street that should be used. - :param match_sideof_street: Specifies how the location set by ``sideof_street_hint`` should - be handled. If this is set then sideof_street_hint should also be set. There are two - valid values for match_sideof_street: - - ``always``: - Always prefer the given side of street. - - ``onlyIfDivided``: - Only prefer using side of street set by ``sideof_street_hint`` in case the street - has dividers. This is the default behavior. - - These values are mainted as config in: - :attr:`ROUTE_MATCH_SIDEOF_STREET ` - :param namehint: A string for the router to look for the place with the most similar name. - This can e.g. include things like: North being used to differentiate between - interstates I66 North and I66 South, Downtown Avenue being used to correctly - select a residential street. - :param radius: In meters Asks the router to consider all places within the given radius as - potential candidates for route calculation. This can be either because it is not - important which place is used, or because it is unknown. Radius more than 200 meters - are not supported. - :param min_course_distance: In meters Asks the routing service to try to find a route that - avoids actions for the indicated distance. E.g. if the origin is determined by a moving - vehicle, the user might not have time to react to early actions. - """ # noqa E501 - self.course = course - self.sideOfStreetHint: Optional[str] = None - if sideof_street_hint is not None: - self.sideOfStreetHint = ",".join([str(point) for point in sideof_street_hint]) - self.matchSideOfStreet = match_sideof_street - self.namehint = namehint - self.radius = radius - self.minCourseDistance = min_course_distance - - def __repr__(self): - """Return string representation of this instance.""" - return json.dumps(self.__dict__) - - -class WayPointOptions: - """A class to define ``PlaceOptions`` for ``via``/ ``destination``. - - Various options can be found here: - - `PlaceOptions `_. - """ # noqa E501 - - def __init__(self, stop_duration: Optional[int] = None, pass_through: Optional[bool] = None): - self.stopDuration = stop_duration - self.passThrough = pass_through - - def __repr__(self): - """Return string representation of this instance.""" - return json.dumps(self.__dict__) - - class Scooter: """A class to define attributes specific for the scooter route. diff --git a/here_location_services/isoline_routing_api.py b/here_location_services/isoline_routing_api.py index 187fab3..3b2a250 100644 --- a/here_location_services/isoline_routing_api.py +++ b/here_location_services/isoline_routing_api.py @@ -3,12 +3,13 @@ """This module contains classes for accessing `HERE Routing API `_. """ # noqa E501 - -from typing import Dict, List, Optional +from datetime import datetime +from typing import Dict, List, Optional, Any import requests from here_location_services.platform.auth import Auth +from .config.base_config import Truck from .apis import Api from .exceptions import ApiError @@ -29,13 +30,17 @@ def __init__( def get_isoline_routing( self, - transportMode: str, range: str, range_type: str, - origin: Optional[List[float]] = None, - destination: Optional[List[float]] = None, - arrival: Optional[str] = None, - departureTime: Optional[str] = None, + transportMode: str, + origin: Optional[List] = None, + departureTime: Optional[datetime] = None, + destination: Optional[List] = None, + arrivalTime: Optional[datetime] = None, + routing_mode: Optional[str] = "fast", + optimised_for: Optional[str] = "balanced", + avoid_features: Optional[List[str]] = None, + truck: Optional[Truck] = None, ) -> requests.Response: """Get isoline routing. @@ -43,29 +48,31 @@ def get_isoline_routing( leaving from one defined center with either a specified length or specified travel time. - :param mode: A string representing how the route is calculated. - Example: ``Type;TransportModes;TrafficMode;Feature``. - ``fastest;car;traffic:disabled;motorway:-3`` :param range: A string representing a range of isoline, unit is defined by parameter range type. Example: range='1000' or range='1000,2000,3000' :param range_type: A string representing a type of ``range``. Possible values are ``distance``, ``time`` and ``consumption``. For distance the unit meters. For a - time the unit is seconds.For consumption, it is defined by the consumption + time the unit is seconds. For consumption, it is defined by the consumption model. - :param start: A list of latitude and longitude representing the center of isoline - request. Isoline will cover all the roads which can be reached from this - point within a given range. It can not be used in combination with the - ``destination`` parameter. - :param destination: A list of latitude and longitude representing the center of - isoline request. Isoline will cover all roads from which this point can be - reached within a given range. It can not be used in combination with the - ``start`` parameter. - :param arrival: A string representing the time when travel is expected to end. - It can be used only if the parameter ``destination`` is also used. - Example: arrival= '2013-07-04T17:00:00+02'. - :param departure: A string representing the time when travel is expected to - start. It can be used only if the parameter ``start`` is also used. - Example: departure= '2013-07-04T17:00:00+02' + :param transportMode: A string representing Mode of transport to be used for the calculation of the isolines. + Example: ``car``. + :param origin: Center of the isoline request. The Isoline(s) will cover the region + which can be reached from this point within given range. It cannot be used in + combination with ``destination`` parameter. + :param departureTime: Specifies the time of departure as defined by either date-time + or full-date T partial-time in RFC 3339, section 5.6 (for example, 2019-06-24T01:23:45). + The requested time is converted to the local time at origin. When the optional timezone + offset is not specified, time is assumed to be local. If neither departureTime or + arrivalTime are specified, current time at departure location will be used. All Time + values in the response are returned in the timezone of each location. + :param destination: Center of the isoline request. The Isoline(s) will cover the + region within the specified range that can reach this point. It cannot be used + in combination with ``origin`` parameter. + :param arrivalTime: Specifies the time of arrival as defined by either date-time or + full-date T partial-time in RFC 3339, section 5.6 (for example, 2019-06-24T01:23:45). + The requested time is converted to the local time at destination. When the optional + timezone offset is not specified, time is assumed to be local. All Time values in + the response are returned in the timezone of each location. :return: :class:`requests.Response` object. :raises ApiError: If ``status_code`` of API response is not 200. """ @@ -77,13 +84,22 @@ def get_isoline_routing( "transportMode": transportMode, } if origin: - params["origin"] = f"{origin[0]},{origin[1]}" + params["origin"] = (",".join([str(i) for i in origin]),) if destination: - params["destination"] = f"{destination[0]},{destination[1]}" - if arrival: - params["arrivalTime"] = arrival + params["destination"] = (",".join([str(i) for i in destination]),) + if arrivalTime: + params["arrivalTime"] = arrivalTime.isoformat(timespec="seconds") if departureTime: - params["departureTime"] = departureTime + params["departureTime"] = departureTime.isoformat(timespec="seconds") + if routing_mode: + params["routingMode"] = routing_mode + if optimised_for: + params["optimizeFor"] = optimised_for + if avoid_features: + avoid: Dict[str, Any] = {"features": avoid_features} + params["avoid"] = avoid + if truck: + params["truck"] = {k: v for k, v in vars(truck).items() if v is not None} resp = self.get(url, params=params, proxies=self.proxies) if resp.status_code == 200: return resp diff --git a/here_location_services/ls.py b/here_location_services/ls.py index cbbbad8..788a830 100644 --- a/here_location_services/ls.py +++ b/here_location_services/ls.py @@ -11,22 +11,21 @@ from typing import Dict, List, Optional, Union from here_location_services.config.routing_config import ( - PlaceOptions, Scooter, Via, - WayPointOptions, ) from here_location_services.platform.apis.aaa_oauth2_api import AAAOauth2Api from here_location_services.platform.auth import Auth from here_location_services.platform.credentials import PlatformCredentials +from .config.base_config import Truck, PlaceOptions, WayPointOptions + from .config.matrix_routing_config import ( AutoCircleRegion, AvoidBoundingBox, BoundingBoxRegion, CircleRegion, PolygonRegion, - Truck, WorldRegion, ) from .exceptions import ApiError @@ -139,13 +138,17 @@ def reverse_geocode( def calculate_isoline( self, - transportMode: str, range: str, range_type: str, - origin: Optional[List[float]] = None, - destination: Optional[List[float]] = None, - arrival: Optional[str] = None, - departureTime: Optional[str] = None, + transportMode: str, + origin: Optional[List] = None, + departureTime: Optional[datetime] = None, + destination: Optional[List] = None, + arrivalTime: Optional[datetime] = None, + routing_mode: Optional[str] = "fast", + optimised_for: Optional[str] = "balanced", + avoid_features: Optional[List[str]] = None, + truck: Optional[Truck] = None, ) -> IsolineResponse: """Calculate isoline routing. @@ -186,20 +189,27 @@ def calculate_isoline( raise ValueError("please provide either `origin` or `destination`.") if departureTime and origin is None: raise ValueError("`departureTime` must be provided with `origin`") - if arrival and destination is None: + if arrivalTime and destination is None: raise ValueError("`arrival` must be provided with `destination`") resp = self.isoline_routing_api.get_isoline_routing( - transportMode=transportMode, range=range, range_type=range_type, + transportMode=transportMode, origin=origin, - destination=destination, - arrival=arrival, departureTime=departureTime, + destination=destination, + arrivalTime=arrivalTime, + routing_mode=routing_mode, + optimised_for=optimised_for, + avoid_features=avoid_features, + truck=truck, ) - response = resp.json() - return IsolineResponse.new(response) + response = IsolineResponse.new(resp.json()) + + if response.notices: + raise ValueError("Isolines could not be calculated.") + return response def discover( self, diff --git a/here_location_services/matrix_routing_api.py b/here_location_services/matrix_routing_api.py index de7d6a8..b0e284e 100644 --- a/here_location_services/matrix_routing_api.py +++ b/here_location_services/matrix_routing_api.py @@ -9,13 +9,14 @@ from here_location_services.platform.auth import Auth from .apis import Api + +from .config.base_config import Truck from .config.matrix_routing_config import ( AutoCircleRegion, AvoidBoundingBox, BoundingBoxRegion, CircleRegion, PolygonRegion, - Truck, WorldRegion, ) from .exceptions import ApiError @@ -39,7 +40,11 @@ def __send_post_request( async_req: str, origins: List[Dict], region_definition: Union[ - CircleRegion, BoundingBoxRegion, PolygonRegion, AutoCircleRegion, WorldRegion + CircleRegion, + BoundingBoxRegion, + PolygonRegion, + AutoCircleRegion, + WorldRegion, ], destinations: Optional[List[Dict]] = None, profile: Optional[str] = None, @@ -92,7 +97,11 @@ def matrix_route( self, origins: List[Dict], region_definition: Union[ - CircleRegion, BoundingBoxRegion, PolygonRegion, AutoCircleRegion, WorldRegion + CircleRegion, + BoundingBoxRegion, + PolygonRegion, + AutoCircleRegion, + WorldRegion, ], destinations: Optional[List[Dict]] = None, profile: Optional[str] = None, @@ -156,7 +165,11 @@ def matrix_route_async( self, origins: List[Dict], region_definition: Union[ - CircleRegion, BoundingBoxRegion, PolygonRegion, AutoCircleRegion, WorldRegion + CircleRegion, + BoundingBoxRegion, + PolygonRegion, + AutoCircleRegion, + WorldRegion, ], destinations: Optional[List[Dict]] = None, profile: Optional[str] = None, diff --git a/here_location_services/responses.py b/here_location_services/responses.py index 2c69f1a..a03f0d2 100644 --- a/here_location_services/responses.py +++ b/here_location_services/responses.py @@ -74,7 +74,7 @@ class IsolineResponse(ApiResponse): def __init__(self, **kwargs): super().__init__() - self._filters = {"departure": None, "isolines": None} + self._filters = {"departure": None, "isolines": None, "notices": None} for param, default in self._filters.items(): setattr(self, param, kwargs.get(param, default)) @@ -88,7 +88,6 @@ def to_geojson(self): lstring = [(coord[1], coord[0]) for coord in lstring] f = Feature(geometry=LineString(lstring), properties=polygon) feature_collection.features.append(f) - print(feature_collection) return feature_collection diff --git a/here_location_services/routing_api.py b/here_location_services/routing_api.py index 5149763..8d6fc03 100644 --- a/here_location_services/routing_api.py +++ b/here_location_services/routing_api.py @@ -7,12 +7,15 @@ from datetime import datetime from typing import Dict, List, Optional -from here_location_services.config.matrix_routing_config import AvoidBoundingBox, Truck -from here_location_services.config.routing_config import ( +from here_location_services.config.base_config import ( + Truck, PlaceOptions, + WayPointOptions, +) +from here_location_services.config.matrix_routing_config import AvoidBoundingBox +from here_location_services.config.routing_config import ( Scooter, Via, - WayPointOptions, ) from here_location_services.platform.auth import Auth diff --git a/tests/test_ls.py b/tests/test_ls.py index dc7d483..ae0c6f8 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -10,17 +10,23 @@ from geojson import FeatureCollection from here_location_services import LS + +from here_location_services.config.base_config import ( + ROUTING_MODE, + SHIPPED_HAZARDOUS_GOODS, + Truck, + PlaceOptions, + WayPointOptions, +) from here_location_services.config.matrix_routing_config import ( AVOID_FEATURES, MATRIX_ATTRIBUTES, PROFILE, - SHIPPED_HAZARDOUS_GOODS, AutoCircleRegion, AvoidBoundingBox, BoundingBoxRegion, CircleRegion, PolygonRegion, - Truck, WorldRegion, ) from here_location_services.config.routing_config import ( @@ -29,15 +35,17 @@ from here_location_services.config.routing_config import ( ROUTE_COURSE, ROUTE_MATCH_SIDEOF_STREET, - ROUTING_MODE, ROUTING_RETURN, ROUTING_SPANS, ROUTING_TRANSPORT_MODE, - PlaceOptions, Scooter, Via, - WayPointOptions, ) +from here_location_services.config.isoline_routing_config import ( + ISOLINE_ROUTING_TRANSPORT_MODE, + OPTIMISED_FOR, +) + from here_location_services.config.search_config import PLACES_CATEGORIES from here_location_services.exceptions import ApiError from here_location_services.responses import GeocoderResponse @@ -108,11 +116,10 @@ def test_isonline_routing(): origin=[52.5, 13.4], range="3000", range_type="time", - transportMode="car", - departureTime="2021-08-05T18:53:27+00:00", + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + departureTime=datetime.now(), ) - print(result) assert result.isolines assert result.departure coordinates = result.isolines[0]["polygons"][0]["outer"] @@ -121,32 +128,30 @@ def test_isonline_routing(): geo_json = result.to_geojson() assert geo_json.type == "FeatureCollection" - # result2 = ls.calculate_isoline( - # destination=[52.5, 13.4], - # range="900", - # range_type="time", - # transportMode="car", - # arrival="2021-08-05T18:53:27+00:00", - # ) - # coordinates = result2.isoline[0]["component"][0]["shape"] - # assert coordinates[0] - - # with pytest.raises(ValueError): - # ls.calculate_isoline( - # origin=[52.5, 13.4], - # range="900", - # range_type="time", - # transportMode="car", - # destination=[52.5, 13.4], - # ) - # with pytest.raises(ApiError): - # ls2 = LS(api_key="dummy") - # ls2.calculate_isoline( - # origin=[52.5, 13.4], - # range="900", - # range_type="time", - # transportMode="car", - # ) + with pytest.raises(ValueError): + ls.calculate_isoline( + destination=[82.8628, 135.00], + range="3000", + range_type="distance", + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + arrivalTime=datetime.now(), + ) + with pytest.raises(ValueError): + ls.calculate_isoline( + origin=[52.5, 13.4], + range="900", + range_type="time", + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + destination=[52.5, 13.4], + ) + with pytest.raises(ApiError): + ls2 = LS(api_key="dummy") + ls2.calculate_isoline( + origin=[52.5, 13.4], + range="900", + range_type="time", + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + ) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") @@ -164,7 +169,7 @@ def test_isonline_routing_exception(): range="900", range_type="time", transportMode="car", - arrival="2021-08-05T18:53:27+00:00", + arrivalTime=datetime.now(), origin=[52.5, 13.4], ) with pytest.raises(ValueError): @@ -172,7 +177,7 @@ def test_isonline_routing_exception(): range="900", range_type="time", transportMode="car", - departureTime="2021-08-05T18:53:27+00:00", + departureTime=datetime.now(), destination=[52.5, 13.4], ) diff --git a/tests/test_ls_auth_token.py b/tests/test_ls_auth_token.py index c8f15af..ff79493 100644 --- a/tests/test_ls_auth_token.py +++ b/tests/test_ls_auth_token.py @@ -8,13 +8,15 @@ import pytz from here_location_services import LS +from here_location_services.config.base_config import ROUTING_MODE + from here_location_services.config.matrix_routing_config import ( AVOID_FEATURES, MATRIX_ATTRIBUTES, AvoidBoundingBox, WorldRegion, ) -from here_location_services.config.routing_config import ROUTING_MODE, ROUTING_TRANSPORT_MODE +from here_location_services.config.routing_config import ROUTING_TRANSPORT_MODE from here_location_services.platform.credentials import PlatformCredentials from here_location_services.responses import GeocoderResponse from tests.conftest import env_setup_done @@ -57,7 +59,9 @@ def test_matrix_route_auth_token(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + avoid_areas = AvoidBoundingBox( + 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 + ) result = ls.matrix( origins=origins, region_definition=region_definition, From fc3e7f8b294ee3c56116829081854fd30b0e11cc Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Tue, 10 Aug 2021 03:13:42 +0530 Subject: [PATCH 05/10] fixes --- ...services.config.isoline_routing_config.rst | 8 ++ here_location_services/config/base_config.py | 8 +- .../config/isoline_routing_config.py | 9 +- .../config/matrix_routing_config.py | 4 +- here_location_services/isoline_routing_api.py | 85 ++++++++++++--- here_location_services/ls.py | 102 +++++++++++------- here_location_services/matrix_routing_api.py | 3 +- here_location_services/responses.py | 14 ++- here_location_services/routing_api.py | 11 +- tests/test_ls.py | 61 +++-------- tests/test_ls_auth_token.py | 5 +- 11 files changed, 174 insertions(+), 136 deletions(-) create mode 100644 docs/source/here_location_services.config.isoline_routing_config.rst diff --git a/docs/source/here_location_services.config.isoline_routing_config.rst b/docs/source/here_location_services.config.isoline_routing_config.rst new file mode 100644 index 0000000..1701649 --- /dev/null +++ b/docs/source/here_location_services.config.isoline_routing_config.rst @@ -0,0 +1,8 @@ +here\_location\_services.config.isoline\_routing\_config module +=============================================================== + +.. automodule:: here_location_services.config.isoline_routing_config + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/here_location_services/config/base_config.py b/here_location_services/config/base_config.py index 56e9eca..0582d49 100644 --- a/here_location_services/config/base_config.py +++ b/here_location_services/config/base_config.py @@ -166,9 +166,7 @@ def __init__( self.course = course self.sideOfStreetHint: Optional[str] = None if sideof_street_hint is not None: - self.sideOfStreetHint = ",".join( - [str(point) for point in sideof_street_hint] - ) + self.sideOfStreetHint = ",".join([str(point) for point in sideof_street_hint]) self.matchSideOfStreet = match_sideof_street self.namehint = namehint self.radius = radius @@ -187,9 +185,7 @@ class WayPointOptions: `PlaceOptions `_. """ # noqa E501 - def __init__( - self, stop_duration: Optional[int] = None, pass_through: Optional[bool] = None - ): + def __init__(self, stop_duration: Optional[int] = None, pass_through: Optional[bool] = None): self.stopDuration = stop_duration self.passThrough = pass_through diff --git a/here_location_services/config/isoline_routing_config.py b/here_location_services/config/isoline_routing_config.py index 1d4209c..2a747ee 100644 --- a/here_location_services/config/isoline_routing_config.py +++ b/here_location_services/config/isoline_routing_config.py @@ -3,9 +3,6 @@ """This module defines all the configs which will be required as inputs to Isoline routing API.""" -import json -from typing import List, Optional - from .base_config import Bunch @@ -34,10 +31,12 @@ class OptimisedFor(Bunch): """A Class to define constant values for optimising calculation for Isoline Routings Api ``quality``: - Calculation of isoline focuses on quality, that is, the graph used for isoline calculation has higher granularity generating an isoline that is more precise. + Calculation of isoline focuses on quality, that is, the graph used for isoline calculation + has higher granularity generating an isoline that is more precise. ``performance``: - Calculation of isoline is performance-centric, quality of isoline is reduced to provide better performance. + Calculation of isoline is performance-centric, quality of isoline is reduced to provide + better performance. ``balanced``: Calculation of isoline takes a balanced approach averaging between quality and performance. diff --git a/here_location_services/config/matrix_routing_config.py b/here_location_services/config/matrix_routing_config.py index 5647d20..bec01d1 100644 --- a/here_location_services/config/matrix_routing_config.py +++ b/here_location_services/config/matrix_routing_config.py @@ -108,9 +108,7 @@ class MatrixAttributes(Bunch): #: Use this config for matrix_attributes of matrix Routing API. #: Example: for ``travelTimes`` matrix_attributes use ``MATRIX_ATTRIBUTES.travelTimes``. -MATRIX_ATTRIBUTES = MatrixAttributes( - **{"travelTimes": "travelTimes", "distances": "distances"} -) +MATRIX_ATTRIBUTES = MatrixAttributes(**{"travelTimes": "travelTimes", "distances": "distances"}) class AvoidFeatures(Bunch): diff --git a/here_location_services/isoline_routing_api.py b/here_location_services/isoline_routing_api.py index 3b2a250..144b9b3 100644 --- a/here_location_services/isoline_routing_api.py +++ b/here_location_services/isoline_routing_api.py @@ -4,14 +4,14 @@ """This module contains classes for accessing `HERE Routing API `_. """ # noqa E501 from datetime import datetime -from typing import Dict, List, Optional, Any +from typing import Any, Dict, List, Optional import requests from here_location_services.platform.auth import Auth -from .config.base_config import Truck from .apis import Api +from .config.base_config import PlaceOptions, Truck, WayPointOptions from .exceptions import ApiError @@ -34,13 +34,18 @@ def get_isoline_routing( range_type: str, transportMode: str, origin: Optional[List] = None, - departureTime: Optional[datetime] = None, + departure_time: Optional[datetime] = None, destination: Optional[List] = None, - arrivalTime: Optional[datetime] = None, + arrival_time: Optional[datetime] = None, routing_mode: Optional[str] = "fast", + shape_max_points: Optional[int] = None, optimised_for: Optional[str] = "balanced", avoid_features: Optional[List[str]] = None, truck: Optional[Truck] = None, + origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, + destination_place_options: Optional[PlaceOptions] = None, + destination_waypoint_options: Optional[WayPointOptions] = None, ) -> requests.Response: """Get isoline routing. @@ -54,25 +59,43 @@ def get_isoline_routing( ``distance``, ``time`` and ``consumption``. For distance the unit meters. For a time the unit is seconds. For consumption, it is defined by the consumption model. - :param transportMode: A string representing Mode of transport to be used for the calculation of the isolines. + :param transportMode: A string representing Mode of transport to be used for the + calculation of the isolines. Example: ``car``. :param origin: Center of the isoline request. The Isoline(s) will cover the region which can be reached from this point within given range. It cannot be used in combination with ``destination`` parameter. - :param departureTime: Specifies the time of departure as defined by either date-time - or full-date T partial-time in RFC 3339, section 5.6 (for example, 2019-06-24T01:23:45). + :param departure_time: Specifies the time of departure as defined by either date-time + or full-date partial-time in RFC 3339, section 5.6 (for example, 2019-06-24T01:23:45). The requested time is converted to the local time at origin. When the optional timezone - offset is not specified, time is assumed to be local. If neither departureTime or - arrivalTime are specified, current time at departure location will be used. All Time + offset is not specified, time is assumed to be local. If neither departure_time or + arrival_time are specified, current time at departure location will be used. All Time values in the response are returned in the timezone of each location. :param destination: Center of the isoline request. The Isoline(s) will cover the region within the specified range that can reach this point. It cannot be used in combination with ``origin`` parameter. - :param arrivalTime: Specifies the time of arrival as defined by either date-time or + :param arrival_time: Specifies the time of arrival as defined by either date-time or full-date T partial-time in RFC 3339, section 5.6 (for example, 2019-06-24T01:23:45). The requested time is converted to the local time at destination. When the optional timezone offset is not specified, time is assumed to be local. All Time values in the response are returned in the timezone of each location. + :param routing_mode: A string to represent routing mode. + :param shape_max_points: An integer to Limit the number of points in the resulting isoline + geometry. If the isoline consists of multiple components, the sum of points from all + components is considered. This parameter doesn't affect performance. + :param optimised_for: A string to specify how isoline calculation is optimized. + :param avoid_features: Avoid routes that violate these properties. Avoid features + are defined in :attr: + `AVOID_FEATURES ` + :param truck: Different truck options to use during route calculation when transportMode + = truck. use object of :class:`Truck here_location_services.config.base_config.Truck>` + :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. + :param destination_place_options: :class:`PlaceOptions` optinal place options + for ``destination``. + :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``destination``. :return: :class:`requests.Response` object. :raises ApiError: If ``status_code`` of API response is not 200. """ @@ -87,10 +110,10 @@ def get_isoline_routing( params["origin"] = (",".join([str(i) for i in origin]),) if destination: params["destination"] = (",".join([str(i) for i in destination]),) - if arrivalTime: - params["arrivalTime"] = arrivalTime.isoformat(timespec="seconds") - if departureTime: - params["departureTime"] = departureTime.isoformat(timespec="seconds") + if arrival_time: + params["arrivalTime"] = arrival_time.isoformat(timespec="seconds") + if departure_time: + params["departureTime"] = departure_time.isoformat(timespec="seconds") if routing_mode: params["routingMode"] = routing_mode if optimised_for: @@ -100,6 +123,40 @@ def get_isoline_routing( params["avoid"] = avoid if truck: params["truck"] = {k: v for k, v in vars(truck).items() if v is not None} + if shape_max_points: + params["shape[maxPoints]"] = shape_max_points + + if origin_place_options: + origin_place_opt = ";".join( + key + "=" + str(val) + for key, val in vars(origin_place_options).items() + if val is not None + ) + params["origin"] = ";".join([params["origin"], origin_place_opt]) + + if origin_waypoint_options: + origin_way_opt = "!".join( + key + "=" + str(val) + for key, val in vars(origin_waypoint_options).items() + if val is not None + ) + params["origin"] = "!".join([params["origin"], origin_way_opt]) + + if destination_place_options: + dest_place_opt = ";".join( + key + "=" + str(val) + for key, val in vars(destination_place_options).items() + if val is not None + ) + params["destination"] = ";".join([params["destination"], dest_place_opt]) + + if destination_waypoint_options: + dest_way_opt = "!".join( + key + "=" + str(val) + for key, val in vars(destination_waypoint_options).items() + if val is not None + ) + params["destination"] = "!".join([params["destination"], dest_way_opt]) resp = self.get(url, params=params, proxies=self.proxies) if resp.status_code == 200: return resp diff --git a/here_location_services/ls.py b/here_location_services/ls.py index 788a830..221296f 100644 --- a/here_location_services/ls.py +++ b/here_location_services/ls.py @@ -10,16 +10,12 @@ from time import sleep from typing import Dict, List, Optional, Union -from here_location_services.config.routing_config import ( - Scooter, - Via, -) +from here_location_services.config.routing_config import Scooter, Via from here_location_services.platform.apis.aaa_oauth2_api import AAAOauth2Api from here_location_services.platform.auth import Auth from here_location_services.platform.credentials import PlatformCredentials -from .config.base_config import Truck, PlaceOptions, WayPointOptions - +from .config.base_config import PlaceOptions, Truck, WayPointOptions from .config.matrix_routing_config import ( AutoCircleRegion, AvoidBoundingBox, @@ -90,9 +86,7 @@ def __init__( api_key=api_key, auth=auth, proxies=proxies, country=country ) - def geocode( - self, query: str, limit: int = 20, lang: str = "en-US" - ) -> GeocoderResponse: + def geocode(self, query: str, limit: int = 20, lang: str = "en-US") -> GeocoderResponse: """Calculate coordinates as result of geocoding for the given ``query``. :param query: A string containing the input query. @@ -131,9 +125,7 @@ def reverse_geocode( if not -180 <= lng <= 180: raise ValueError("Longitude must be in range -180 to 180.") - resp = self.geo_search_api.get_reverse_geocoding( - lat=lat, lng=lng, limit=limit, lang=lang - ) + resp = self.geo_search_api.get_reverse_geocoding(lat=lat, lng=lng, limit=limit, lang=lang) return ReverseGeocoderResponse.new(resp.json()) def calculate_isoline( @@ -142,13 +134,18 @@ def calculate_isoline( range_type: str, transportMode: str, origin: Optional[List] = None, - departureTime: Optional[datetime] = None, + departure_time: Optional[datetime] = None, destination: Optional[List] = None, - arrivalTime: Optional[datetime] = None, + arrival_time: Optional[datetime] = None, routing_mode: Optional[str] = "fast", + shape_max_points: Optional[int] = None, optimised_for: Optional[str] = "balanced", avoid_features: Optional[List[str]] = None, truck: Optional[Truck] = None, + origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, + destination_place_options: Optional[PlaceOptions] = None, + destination_waypoint_options: Optional[WayPointOptions] = None, ) -> IsolineResponse: """Calculate isoline routing. @@ -156,29 +153,49 @@ def calculate_isoline( leaving from one defined center with either a specified length or specified travel time. - :param mode: A string representing how the route is calculated. - Example: ``Type;TransportModes;TrafficMode;Feature``. - ``fastest;car;traffic:disabled;motorway:-3`` :param range: A string representing a range of isoline, unit is defined by parameter range type. Example: range='1000' or range='1000,2000,3000' - :param range_type: A string representing a type of `range`. Possible values are + :param range_type: A string representing a type of ``range``. Possible values are ``distance``, ``time`` and ``consumption``. For distance the unit meters. For a - time the unit is seconds.For consumption, it is defined by the consumption + time the unit is seconds. For consumption, it is defined by the consumption model. - :param start: A list of latitude and longitude representing the center of isoline - request. Isoline will cover all the roads which can be reached from this - point within a given range. It can not be used in combination with the - ``destination`` parameter. - :param destination: A list of latitude and longitude representing the center of - isoline request. Isoline will cover all roads from which this point can be - reached within a given range. It can not be used in combination with the - ``start`` parameter. - :param arrival: A string representing the time when travel is expected to end. - It can be used only if the parameter ``destination`` is also used. - Example: arrival= '2013-07-04T17:00:00+02'. - :param departure: A string representing the time when travel is expected to - start. It can be used only if the parameter ``start`` is also used. - Example: departure= '2013-07-04T17:00:00+02' + :param transportMode: A string representing Mode of transport to be used for the + calculation of the isolines. + Example: ``car``. + :param origin: Center of the isoline request. The Isoline(s) will cover the region + which can be reached from this point within given range. It cannot be used in + combination with ``destination`` parameter. + :param departure_time: Specifies the time of departure as defined by either date-time + or full-date partial-time in RFC 3339, section 5.6 (for example, 2019-06-24T01:23:45). + The requested time is converted to the local time at origin. When the optional timezone + offset is not specified, time is assumed to be local. If neither departure_time or + arrival_time are specified, current time at departure location will be used. All Time + values in the response are returned in the timezone of each location. + :param destination: Center of the isoline request. The Isoline(s) will cover the + region within the specified range that can reach this point. It cannot be used + in combination with ``origin`` parameter. + :param arrival_time: Specifies the time of arrival as defined by either date-time or + full-date T partial-time in RFC 3339, section 5.6 (for example, 2019-06-24T01:23:45). + The requested time is converted to the local time at destination. When the optional + timezone offset is not specified, time is assumed to be local. All Time values in + the response are returned in the timezone of each location. + :param routing_mode: A string to represent routing mode. + :param shape_max_points: An integer to Limit the number of points in the resulting isoline + geometry. If the isoline consists of multiple components, the sum of points from all + components is considered. This parameter doesn't affect performance. + :param optimised_for: A string to specify how isoline calculation is optimized. + :param avoid_features: Avoid routes that violate these properties. Avoid features + are defined in :attr: + `AVOID_FEATURES ` + :param truck: Different truck options to use during route calculation when transportMode + = truck. use object of :class:`Truck here_location_services.config.base_config.Truck>` + :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. + :param destination_place_options: :class:`PlaceOptions` optinal place options + for ``destination``. + :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``destination``. :raises ValueError: If ``start`` and ``destination`` are provided togrther. :return: :class:`IsolineResponse` object. """ @@ -187,9 +204,9 @@ def calculate_isoline( raise ValueError("`origin` and `destination` can not be provided together.") if origin is None and destination is None: raise ValueError("please provide either `origin` or `destination`.") - if departureTime and origin is None: - raise ValueError("`departureTime` must be provided with `origin`") - if arrivalTime and destination is None: + if departure_time and origin is None: + raise ValueError("`departure_time` must be provided with `origin`") + if arrival_time and destination is None: raise ValueError("`arrival` must be provided with `destination`") resp = self.isoline_routing_api.get_isoline_routing( @@ -197,13 +214,18 @@ def calculate_isoline( range_type=range_type, transportMode=transportMode, origin=origin, - departureTime=departureTime, + departure_time=departure_time, destination=destination, - arrivalTime=arrivalTime, + arrival_time=arrival_time, routing_mode=routing_mode, + shape_max_points=shape_max_points, optimised_for=optimised_for, avoid_features=avoid_features, truck=truck, + origin_place_options=origin_place_options, + origin_waypoint_options=origin_waypoint_options, + destination_place_options=destination_place_options, + destination_waypoint_options=destination_waypoint_options, ) response = IsolineResponse.new(resp.json()) @@ -755,9 +777,7 @@ def matrix( ) status_url = resp["statusUrl"] while True: - resp_status = self.matrix_routing_api.get_async_matrix_route_status( - status_url - ) + resp_status = self.matrix_routing_api.get_async_matrix_route_status(status_url) if resp_status.status_code == 200 and resp_status.json().get("error"): raise ApiError(resp_status) elif resp_status.status_code == 303: diff --git a/here_location_services/matrix_routing_api.py b/here_location_services/matrix_routing_api.py index b0e284e..642880c 100644 --- a/here_location_services/matrix_routing_api.py +++ b/here_location_services/matrix_routing_api.py @@ -9,7 +9,6 @@ from here_location_services.platform.auth import Auth from .apis import Api - from .config.base_config import Truck from .config.matrix_routing_config import ( AutoCircleRegion, @@ -140,7 +139,7 @@ def matrix_route( :param avoid_areas: A list of areas to avoid during route calculation. To define avoid area use object of :class:`AvoidBoundingBox here_location_services.config.matrix_routing_config.AvoidBoundingBox>` :param truck: Different truck options to use during route calculation when - transportMode = truck. use object of :class:`Truck here_location_services.config.matrix_routing_config.Truck>` + transportMode = truck. use object of :class:`Truck here_location_services.config.base_config.Truck>` :param matrix_attributes: Defines which attributes are included in the response as part of the data representation of the matrix entries summaries. Matrix attributes are defined in :attr:`MATRIX_ATTRIBUTES ` diff --git a/here_location_services/responses.py b/here_location_services/responses.py index a03f0d2..5442011 100644 --- a/here_location_services/responses.py +++ b/here_location_services/responses.py @@ -8,7 +8,7 @@ import json import flexpolyline as fp -from geojson import Feature, FeatureCollection, LineString, Point, Polygon +from geojson import Feature, FeatureCollection, LineString, Point from pandas import DataFrame @@ -24,9 +24,9 @@ def __str__(self): def as_json_string(self, encoding: str = "utf8"): """Return API response as json string.""" - json_string = json.dumps( - self.response, sort_keys=True, ensure_ascii=False - ).encode(encoding) + json_string = json.dumps(self.response, sort_keys=True, ensure_ascii=False).encode( + encoding + ) return json_string.decode() def to_geojson(self): @@ -162,8 +162,7 @@ def to_distnaces_matrix(self): distances = self.matrix.get("distances") dest_count = self.matrix.get("numDestinations") nested_distances = [ - distances[i : i + dest_count] - for i in range(0, len(distances), dest_count) + distances[i : i + dest_count] for i in range(0, len(distances), dest_count) ] return DataFrame(nested_distances, columns=range(dest_count)) @@ -173,7 +172,6 @@ def to_travel_times_matrix(self): distances = self.matrix.get("travelTimes") dest_count = self.matrix.get("numDestinations") nested_distances = [ - distances[i : i + dest_count] - for i in range(0, len(distances), dest_count) + distances[i : i + dest_count] for i in range(0, len(distances), dest_count) ] return DataFrame(nested_distances, columns=range(dest_count)) diff --git a/here_location_services/routing_api.py b/here_location_services/routing_api.py index 8d6fc03..8f826a8 100644 --- a/here_location_services/routing_api.py +++ b/here_location_services/routing_api.py @@ -7,16 +7,9 @@ from datetime import datetime from typing import Dict, List, Optional -from here_location_services.config.base_config import ( - Truck, - PlaceOptions, - WayPointOptions, -) +from here_location_services.config.base_config import PlaceOptions, Truck, WayPointOptions from here_location_services.config.matrix_routing_config import AvoidBoundingBox -from here_location_services.config.routing_config import ( - Scooter, - Via, -) +from here_location_services.config.routing_config import Scooter, Via from here_location_services.platform.auth import Auth from .apis import Api diff --git a/tests/test_ls.py b/tests/test_ls.py index ae0c6f8..81cac61 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -10,14 +10,14 @@ from geojson import FeatureCollection from here_location_services import LS - from here_location_services.config.base_config import ( ROUTING_MODE, SHIPPED_HAZARDOUS_GOODS, - Truck, PlaceOptions, + Truck, WayPointOptions, ) +from here_location_services.config.isoline_routing_config import ISOLINE_ROUTING_TRANSPORT_MODE from here_location_services.config.matrix_routing_config import ( AVOID_FEATURES, MATRIX_ATTRIBUTES, @@ -29,9 +29,7 @@ PolygonRegion, WorldRegion, ) -from here_location_services.config.routing_config import ( - AVOID_FEATURES as ROUTING_AVOID_FEATURES, -) +from here_location_services.config.routing_config import AVOID_FEATURES as ROUTING_AVOID_FEATURES from here_location_services.config.routing_config import ( ROUTE_COURSE, ROUTE_MATCH_SIDEOF_STREET, @@ -41,11 +39,6 @@ Scooter, Via, ) -from here_location_services.config.isoline_routing_config import ( - ISOLINE_ROUTING_TRANSPORT_MODE, - OPTIMISED_FOR, -) - from here_location_services.config.search_config import PLACES_CATEGORIES from here_location_services.exceptions import ApiError from here_location_services.responses import GeocoderResponse @@ -117,7 +110,7 @@ def test_isonline_routing(): range="3000", range_type="time", transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, - departureTime=datetime.now(), + departure_time=datetime.now(), ) assert result.isolines @@ -134,7 +127,7 @@ def test_isonline_routing(): range="3000", range_type="distance", transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, - arrivalTime=datetime.now(), + arrival_time=datetime.now(), ) with pytest.raises(ValueError): ls.calculate_isoline( @@ -169,7 +162,7 @@ def test_isonline_routing_exception(): range="900", range_type="time", transportMode="car", - arrivalTime=datetime.now(), + arrival_time=datetime.now(), origin=[52.5, 13.4], ) with pytest.raises(ValueError): @@ -177,7 +170,7 @@ def test_isonline_routing_exception(): range="900", range_type="time", transportMode="car", - departureTime=datetime.now(), + departure_time=datetime.now(), destination=[52.5, 13.4], ) @@ -185,9 +178,7 @@ def test_isonline_routing_exception(): @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_ls_discover(): ls = LS(api_key=LS_API_KEY) - result = ls.discover( - query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en" - ) + result = ls.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en") assert len(result.items) == 20 result2 = ls.discover( @@ -213,9 +204,7 @@ def test_ls_discover(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.discover( - query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10 - ) + ls2.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") @@ -282,18 +271,14 @@ def test_ls_lookup(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.lookup( - location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2" - ) + ls2.lookup(location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2") @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_car_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [ - AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) - ] + avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via1 = Via(lat=52.52426, lng=13.43000) via2 = Via(lat=52.52624, lng=13.44012) @@ -308,16 +293,12 @@ def test_car_route(): avoid_features=avoid_features, exclude=["IND", "NZL", "AUS"], ) - assert result.response["routes"][0]["sections"][0]["departure"]["place"][ - "location" - ] == { + assert result.response["routes"][0]["sections"][0]["departure"]["place"]["location"] == { "lat": 52.5137479, "lng": 13.4246242, "elv": 76.0, } - assert result.response["routes"][0]["sections"][1]["departure"]["place"][ - "location" - ] == { + assert result.response["routes"][0]["sections"][1]["departure"]["place"]["location"] == { "lat": 52.5242323, "lng": 13.4301462, "elv": 80.0, @@ -392,9 +373,7 @@ def test_car_route_extra_options(): def test_bicycle_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [ - AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) - ] + avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.bicycle_route( @@ -424,9 +403,7 @@ def test_truck_route(): tunnel_category="B", axle_count=4, ) - avoid_areas = [ - AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) - ] + avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.truck_route( @@ -530,9 +507,7 @@ def test_matrix_route(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox( - 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 - ) + avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) assert json.loads(avoid_areas.__str__()) == { "type": "boundingBox", "north": 68.1766451354, @@ -594,9 +569,7 @@ def test_matrix_route_async(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox( - 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 - ) + avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) truck = Truck( shipped_hazardous_goods=[SHIPPED_HAZARDOUS_GOODS.explosive], gross_weight=100, diff --git a/tests/test_ls_auth_token.py b/tests/test_ls_auth_token.py index ff79493..35ca13c 100644 --- a/tests/test_ls_auth_token.py +++ b/tests/test_ls_auth_token.py @@ -9,7 +9,6 @@ from here_location_services import LS from here_location_services.config.base_config import ROUTING_MODE - from here_location_services.config.matrix_routing_config import ( AVOID_FEATURES, MATRIX_ATTRIBUTES, @@ -59,9 +58,7 @@ def test_matrix_route_auth_token(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox( - 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 - ) + avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) result = ls.matrix( origins=origins, region_definition=region_definition, From 763ab3d3f06eef1fdd3501929063a7ce6d065202 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Tue, 10 Aug 2021 03:18:41 +0530 Subject: [PATCH 06/10] add origin waypoints parameter to routing api --- here_location_services/ls.py | 20 ++++++++++++++++++++ here_location_services/routing_api.py | 11 +++++++++++ 2 files changed, 31 insertions(+) diff --git a/here_location_services/ls.py b/here_location_services/ls.py index 221296f..58a36b7 100644 --- a/here_location_services/ls.py +++ b/here_location_services/ls.py @@ -340,6 +340,7 @@ def car_route( destination: List, via: Optional[List[Via]] = None, origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, destination_place_options: Optional[PlaceOptions] = None, destination_waypoint_options: Optional[WayPointOptions] = None, departure_time: Optional[datetime] = None, @@ -359,6 +360,8 @@ def car_route( :param destination: A list of ``latitude`` and ``longitude`` of destination point of route. :param via: A list of :class:`Via` objects. :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. :param destination_place_options: :class:`PlaceOptions` optinal place options for ``destination``. :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -388,6 +391,7 @@ def car_route( destination=destination, via=via, origin_place_options=origin_place_options, + origin_waypoint_options=origin_waypoint_options, destination_place_options=destination_place_options, destination_waypoint_options=destination_waypoint_options, departure_time=departure_time, @@ -409,6 +413,7 @@ def bicycle_route( destination: List, via: Optional[List[Via]] = None, origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, destination_place_options: Optional[PlaceOptions] = None, destination_waypoint_options: Optional[WayPointOptions] = None, departure_time: Optional[datetime] = None, @@ -428,6 +433,8 @@ def bicycle_route( :param destination: A list of ``latitude`` and ``longitude`` of destination point of route. :param via: A list of :class:`Via` objects. :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. :param destination_place_options: :class:`PlaceOptions` optinal place options for ``destination``. :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -456,6 +463,7 @@ def bicycle_route( destination=destination, via=via, origin_place_options=origin_place_options, + origin_waypoint_options=origin_waypoint_options, destination_place_options=destination_place_options, destination_waypoint_options=destination_waypoint_options, departure_time=departure_time, @@ -477,6 +485,7 @@ def truck_route( destination: List, via: Optional[List[Via]] = None, origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, destination_place_options: Optional[PlaceOptions] = None, destination_waypoint_options: Optional[WayPointOptions] = None, departure_time: Optional[datetime] = None, @@ -497,6 +506,8 @@ def truck_route( :param destination: A list of ``latitude`` and ``longitude`` of destination point of route. :param via: A list of :class:`Via` objects. :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. :param destination_place_options: :class:`PlaceOptions` optinal place options for ``destination``. :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -527,6 +538,7 @@ def truck_route( destination=destination, via=via, origin_place_options=origin_place_options, + origin_waypoint_options=origin_waypoint_options, destination_place_options=destination_place_options, destination_waypoint_options=destination_waypoint_options, departure_time=departure_time, @@ -549,6 +561,7 @@ def scooter_route( destination: List, via: Optional[List[Via]] = None, origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, destination_place_options: Optional[PlaceOptions] = None, destination_waypoint_options: Optional[WayPointOptions] = None, scooter: Optional[Scooter] = None, @@ -569,6 +582,8 @@ def scooter_route( :param destination: A list of ``latitude`` and ``longitude`` of destination point of route. :param via: A list of :class:`Via` objects. :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. :param destination_place_options: :class:`PlaceOptions` optinal place options for ``destination``. :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -598,6 +613,7 @@ def scooter_route( destination=destination, via=via, origin_place_options=origin_place_options, + origin_waypoint_options=origin_waypoint_options, destination_place_options=destination_place_options, destination_waypoint_options=destination_waypoint_options, scooter=scooter, @@ -620,6 +636,7 @@ def pedestrian_route( destination: List, via: Optional[List[Via]] = None, origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, destination_place_options: Optional[PlaceOptions] = None, destination_waypoint_options: Optional[WayPointOptions] = None, departure_time: Optional[datetime] = None, @@ -639,6 +656,8 @@ def pedestrian_route( :param destination: A list of ``latitude`` and ``longitude`` of destination point of route. :param via: A list of :class:`Via` objects. :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. :param destination_place_options: :class:`PlaceOptions` optinal place options for ``destination``. :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -667,6 +686,7 @@ def pedestrian_route( destination=destination, via=via, origin_place_options=origin_place_options, + origin_waypoint_options=origin_waypoint_options, destination_place_options=destination_place_options, destination_waypoint_options=destination_waypoint_options, departure_time=departure_time, diff --git a/here_location_services/routing_api.py b/here_location_services/routing_api.py index 8f826a8..1233b1d 100644 --- a/here_location_services/routing_api.py +++ b/here_location_services/routing_api.py @@ -36,6 +36,7 @@ def route( destination: List, via: Optional[List[Via]] = None, origin_place_options: Optional[PlaceOptions] = None, + origin_waypoint_options: Optional[WayPointOptions] = None, destination_place_options: Optional[PlaceOptions] = None, destination_waypoint_options: Optional[WayPointOptions] = None, scooter: Optional[Scooter] = None, @@ -60,6 +61,8 @@ def route( :param destination: A list of ``latitude`` and ``longitude`` of destination point of route. :param via: A list of tuples of ``latitude`` and ``longitude`` of via points. :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. + :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options + for ``origin``. :param destination_place_options: :class:`PlaceOptions` optinal place options for ``destination``. :param destination_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -133,6 +136,14 @@ def route( ) params["origin"] = ";".join([params["origin"], origin_place_opt]) + if origin_waypoint_options: + origin_way_opt = "!".join( + key + "=" + str(val) + for key, val in vars(origin_waypoint_options).items() + if val is not None + ) + params["origin"] = "!".join([params["origin"], origin_way_opt]) + if destination_place_options: dest_place_opt = ";".join( key + "=" + str(val) From 3b6cfde20437f82bee0afd41e5d1cc7e049d6d71 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Tue, 10 Aug 2021 03:31:51 +0530 Subject: [PATCH 07/10] fixes --- .../config/isoline_routing_config.py | 21 ++++++ tests/test_ls.py | 69 +++++++++++++------ tests/test_ls_apis.py | 9 ++- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/here_location_services/config/isoline_routing_config.py b/here_location_services/config/isoline_routing_config.py index 2a747ee..ee22905 100644 --- a/here_location_services/config/isoline_routing_config.py +++ b/here_location_services/config/isoline_routing_config.py @@ -27,6 +27,27 @@ class IsolineRoutingTransportMode(Bunch): ISOLINE_ROUTING_TRANSPORT_MODE = IsolineRoutingTransportMode(**transport_mode) +class RangeType(Bunch): + """A Class to define constant values for specifying the type of range for Isoline Routings Api + + ``distance``: + Units in meters + + ``time``: + Units in seconds + + ``consumption``: + Units in Wh + """ + + +#: Use this config s optimised_for of isoline routing API. +#: Example: for optimising for ``balanced`` mode use ``OPTIMISED_FOR.balanced``. +RANGE_TYPE = RangeType( + **{"distance": "distance", "time": "time", "consumption": "consumption"} +) + + class OptimisedFor(Bunch): """A Class to define constant values for optimising calculation for Isoline Routings Api diff --git a/tests/test_ls.py b/tests/test_ls.py index 81cac61..49ec006 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -17,7 +17,10 @@ Truck, WayPointOptions, ) -from here_location_services.config.isoline_routing_config import ISOLINE_ROUTING_TRANSPORT_MODE +from here_location_services.config.isoline_routing_config import ( + ISOLINE_ROUTING_TRANSPORT_MODE, + RANGE_TYPE, +) from here_location_services.config.matrix_routing_config import ( AVOID_FEATURES, MATRIX_ATTRIBUTES, @@ -29,7 +32,9 @@ PolygonRegion, WorldRegion, ) -from here_location_services.config.routing_config import AVOID_FEATURES as ROUTING_AVOID_FEATURES +from here_location_services.config.routing_config import ( + AVOID_FEATURES as ROUTING_AVOID_FEATURES, +) from here_location_services.config.routing_config import ( ROUTE_COURSE, ROUTE_MATCH_SIDEOF_STREET, @@ -108,7 +113,7 @@ def test_isonline_routing(): result = ls.calculate_isoline( origin=[52.5, 13.4], range="3000", - range_type="time", + range_type=RANGE_TYPE.time, transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, departure_time=datetime.now(), ) @@ -125,7 +130,7 @@ def test_isonline_routing(): ls.calculate_isoline( destination=[82.8628, 135.00], range="3000", - range_type="distance", + range_type=RANGE_TYPE.distance, transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, arrival_time=datetime.now(), ) @@ -133,7 +138,7 @@ def test_isonline_routing(): ls.calculate_isoline( origin=[52.5, 13.4], range="900", - range_type="time", + range_type=RANGE_TYPE.time, transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, destination=[52.5, 13.4], ) @@ -142,7 +147,7 @@ def test_isonline_routing(): ls2.calculate_isoline( origin=[52.5, 13.4], range="900", - range_type="time", + range_type=RANGE_TYPE.time, transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, ) @@ -154,22 +159,22 @@ def test_isonline_routing_exception(): with pytest.raises(ValueError): ls.calculate_isoline( range="900", - range_type="time", - transportMode="car", + range_type=RANGE_TYPE.time, + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, ) with pytest.raises(ValueError): ls.calculate_isoline( range="900", - range_type="time", - transportMode="car", + range_type=RANGE_TYPE.time, + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, arrival_time=datetime.now(), origin=[52.5, 13.4], ) with pytest.raises(ValueError): ls.calculate_isoline( range="900", - range_type="time", - transportMode="car", + range_type=RANGE_TYPE.time, + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, departure_time=datetime.now(), destination=[52.5, 13.4], ) @@ -178,7 +183,9 @@ def test_isonline_routing_exception(): @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_ls_discover(): ls = LS(api_key=LS_API_KEY) - result = ls.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en") + result = ls.discover( + query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en" + ) assert len(result.items) == 20 result2 = ls.discover( @@ -204,7 +211,9 @@ def test_ls_discover(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10) + ls2.discover( + query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10 + ) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") @@ -271,14 +280,18 @@ def test_ls_lookup(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.lookup(location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2") + ls2.lookup( + location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2" + ) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_car_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] + avoid_areas = [ + AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + ] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via1 = Via(lat=52.52426, lng=13.43000) via2 = Via(lat=52.52624, lng=13.44012) @@ -293,12 +306,16 @@ def test_car_route(): avoid_features=avoid_features, exclude=["IND", "NZL", "AUS"], ) - assert result.response["routes"][0]["sections"][0]["departure"]["place"]["location"] == { + assert result.response["routes"][0]["sections"][0]["departure"]["place"][ + "location" + ] == { "lat": 52.5137479, "lng": 13.4246242, "elv": 76.0, } - assert result.response["routes"][0]["sections"][1]["departure"]["place"]["location"] == { + assert result.response["routes"][0]["sections"][1]["departure"]["place"][ + "location" + ] == { "lat": 52.5242323, "lng": 13.4301462, "elv": 80.0, @@ -373,7 +390,9 @@ def test_car_route_extra_options(): def test_bicycle_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] + avoid_areas = [ + AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + ] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.bicycle_route( @@ -403,7 +422,9 @@ def test_truck_route(): tunnel_category="B", axle_count=4, ) - avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] + avoid_areas = [ + AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + ] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.truck_route( @@ -507,7 +528,9 @@ def test_matrix_route(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + avoid_areas = AvoidBoundingBox( + 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 + ) assert json.loads(avoid_areas.__str__()) == { "type": "boundingBox", "north": 68.1766451354, @@ -569,7 +592,9 @@ def test_matrix_route_async(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) + avoid_areas = AvoidBoundingBox( + 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 + ) truck = Truck( shipped_hazardous_goods=[SHIPPED_HAZARDOUS_GOODS.explosive], gross_weight=100, diff --git a/tests/test_ls_apis.py b/tests/test_ls_apis.py index ceaf928..de5eb05 100644 --- a/tests/test_ls_apis.py +++ b/tests/test_ls_apis.py @@ -6,6 +6,10 @@ import pytest import requests +from here_location_services.config.isoline_routing_config import ( + RANGE_TYPE, + ISOLINE_ROUTING_TRANSPORT_MODE, +) from here_location_services.config.matrix_routing_config import WorldRegion from here_location_services.exceptions import ApiError from here_location_services.matrix_routing_api import MatrixRoutingApi @@ -35,7 +39,10 @@ def test_reverse_geocoding(geo_search_api): def test_isonline_routing(isoline_routing_api): """Test isonline routing api.""" result = isoline_routing_api.get_isoline_routing( - origin=[52.5, 13.4], range="3000", range_type="distance", transportMode="car" + origin=[52.5, 13.4], + range="3000", + range_type=RANGE_TYPE.distance, + transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, ) coordinates = result.json()["isolines"][0]["polygons"][0]["outer"] From 76b8bc92cbb83fc27e6912dd42882f48369cda57 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Tue, 10 Aug 2021 17:52:39 +0530 Subject: [PATCH 08/10] fix linting error --- .../config/isoline_routing_config.py | 2 +- here_location_services/ls.py | 20 ++++++++++++------- tests/test_ls_apis.py | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/here_location_services/config/isoline_routing_config.py b/here_location_services/config/isoline_routing_config.py index ee22905..acc2202 100644 --- a/here_location_services/config/isoline_routing_config.py +++ b/here_location_services/config/isoline_routing_config.py @@ -23,7 +23,7 @@ class IsolineRoutingTransportMode(Bunch): } #: Use this config for transport_mode of isoline routing API. -#: Example: for ``car`` transportMode use ``ISOLINE_ROUTING_TRANSPORT_MODE.car``. +#: Example: for ``car`` transport_mode use ``ISOLINE_ROUTING_TRANSPORT_MODE.car``. ISOLINE_ROUTING_TRANSPORT_MODE = IsolineRoutingTransportMode(**transport_mode) diff --git a/here_location_services/ls.py b/here_location_services/ls.py index 58a36b7..b625c74 100644 --- a/here_location_services/ls.py +++ b/here_location_services/ls.py @@ -86,7 +86,9 @@ def __init__( api_key=api_key, auth=auth, proxies=proxies, country=country ) - def geocode(self, query: str, limit: int = 20, lang: str = "en-US") -> GeocoderResponse: + def geocode( + self, query: str, limit: int = 20, lang: str = "en-US" + ) -> GeocoderResponse: """Calculate coordinates as result of geocoding for the given ``query``. :param query: A string containing the input query. @@ -125,14 +127,16 @@ def reverse_geocode( if not -180 <= lng <= 180: raise ValueError("Longitude must be in range -180 to 180.") - resp = self.geo_search_api.get_reverse_geocoding(lat=lat, lng=lng, limit=limit, lang=lang) + resp = self.geo_search_api.get_reverse_geocoding( + lat=lat, lng=lng, limit=limit, lang=lang + ) return ReverseGeocoderResponse.new(resp.json()) def calculate_isoline( self, range: str, range_type: str, - transportMode: str, + transport_mode: str, origin: Optional[List] = None, departure_time: Optional[datetime] = None, destination: Optional[List] = None, @@ -159,7 +163,7 @@ def calculate_isoline( ``distance``, ``time`` and ``consumption``. For distance the unit meters. For a time the unit is seconds. For consumption, it is defined by the consumption model. - :param transportMode: A string representing Mode of transport to be used for the + :param transport_mode: A string representing Mode of transport to be used for the calculation of the isolines. Example: ``car``. :param origin: Center of the isoline request. The Isoline(s) will cover the region @@ -187,7 +191,7 @@ def calculate_isoline( :param avoid_features: Avoid routes that violate these properties. Avoid features are defined in :attr: `AVOID_FEATURES ` - :param truck: Different truck options to use during route calculation when transportMode + :param truck: Different truck options to use during route calculation when transport_mode = truck. use object of :class:`Truck here_location_services.config.base_config.Truck>` :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -212,7 +216,7 @@ def calculate_isoline( resp = self.isoline_routing_api.get_isoline_routing( range=range, range_type=range_type, - transportMode=transportMode, + transport_mode=transport_mode, origin=origin, departure_time=departure_time, destination=destination, @@ -797,7 +801,9 @@ def matrix( ) status_url = resp["statusUrl"] while True: - resp_status = self.matrix_routing_api.get_async_matrix_route_status(status_url) + resp_status = self.matrix_routing_api.get_async_matrix_route_status( + status_url + ) if resp_status.status_code == 200 and resp_status.json().get("error"): raise ApiError(resp_status) elif resp_status.status_code == 303: diff --git a/tests/test_ls_apis.py b/tests/test_ls_apis.py index de5eb05..120c4da 100644 --- a/tests/test_ls_apis.py +++ b/tests/test_ls_apis.py @@ -42,7 +42,7 @@ def test_isonline_routing(isoline_routing_api): origin=[52.5, 13.4], range="3000", range_type=RANGE_TYPE.distance, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, ) coordinates = result.json()["isolines"][0]["polygons"][0]["outer"] From d730d413f44c89e56c6eba2eeb98fa20eb4eb537 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Tue, 10 Aug 2021 17:53:39 +0530 Subject: [PATCH 09/10] fixes --- here_location_services/config/base_config.py | 2 +- .../config/isoline_routing_config.py | 4 +- here_location_services/isoline_routing_api.py | 8 +-- here_location_services/ls.py | 12 +--- tests/test_ls.py | 58 ++++++------------- tests/test_ls_apis.py | 2 +- 6 files changed, 28 insertions(+), 58 deletions(-) diff --git a/here_location_services/config/base_config.py b/here_location_services/config/base_config.py index 0582d49..e9d254d 100644 --- a/here_location_services/config/base_config.py +++ b/here_location_services/config/base_config.py @@ -66,7 +66,7 @@ class ShippedHazardousGoods(Bunch): class Truck: """A class to define different truck options which will be used during route calculation. - Truck options should be used when transportMode is ``truck``. + Truck options should be used when transport_mode is ``truck``. """ def __init__( diff --git a/here_location_services/config/isoline_routing_config.py b/here_location_services/config/isoline_routing_config.py index acc2202..c2c2cac 100644 --- a/here_location_services/config/isoline_routing_config.py +++ b/here_location_services/config/isoline_routing_config.py @@ -43,9 +43,7 @@ class RangeType(Bunch): #: Use this config s optimised_for of isoline routing API. #: Example: for optimising for ``balanced`` mode use ``OPTIMISED_FOR.balanced``. -RANGE_TYPE = RangeType( - **{"distance": "distance", "time": "time", "consumption": "consumption"} -) +RANGE_TYPE = RangeType(**{"distance": "distance", "time": "time", "consumption": "consumption"}) class OptimisedFor(Bunch): diff --git a/here_location_services/isoline_routing_api.py b/here_location_services/isoline_routing_api.py index 144b9b3..3cf760f 100644 --- a/here_location_services/isoline_routing_api.py +++ b/here_location_services/isoline_routing_api.py @@ -32,7 +32,7 @@ def get_isoline_routing( self, range: str, range_type: str, - transportMode: str, + transport_mode: str, origin: Optional[List] = None, departure_time: Optional[datetime] = None, destination: Optional[List] = None, @@ -59,7 +59,7 @@ def get_isoline_routing( ``distance``, ``time`` and ``consumption``. For distance the unit meters. For a time the unit is seconds. For consumption, it is defined by the consumption model. - :param transportMode: A string representing Mode of transport to be used for the + :param transport_mode: A string representing Mode of transport to be used for the calculation of the isolines. Example: ``car``. :param origin: Center of the isoline request. The Isoline(s) will cover the region @@ -87,7 +87,7 @@ def get_isoline_routing( :param avoid_features: Avoid routes that violate these properties. Avoid features are defined in :attr: `AVOID_FEATURES ` - :param truck: Different truck options to use during route calculation when transportMode + :param truck: Different truck options to use during route calculation when transport_mode = truck. use object of :class:`Truck here_location_services.config.base_config.Truck>` :param origin_place_options: :class:`PlaceOptions` optinal place options for ``origin``. :param origin_waypoint_options: :class:`WayPointOptions` optional waypoint options @@ -104,7 +104,7 @@ def get_isoline_routing( params: Dict[str, str] = { "range[type]": range_type, "range[values]": range, - "transportMode": transportMode, + "transportMode": transport_mode, } if origin: params["origin"] = (",".join([str(i) for i in origin]),) diff --git a/here_location_services/ls.py b/here_location_services/ls.py index b625c74..dd0b59c 100644 --- a/here_location_services/ls.py +++ b/here_location_services/ls.py @@ -86,9 +86,7 @@ def __init__( api_key=api_key, auth=auth, proxies=proxies, country=country ) - def geocode( - self, query: str, limit: int = 20, lang: str = "en-US" - ) -> GeocoderResponse: + def geocode(self, query: str, limit: int = 20, lang: str = "en-US") -> GeocoderResponse: """Calculate coordinates as result of geocoding for the given ``query``. :param query: A string containing the input query. @@ -127,9 +125,7 @@ def reverse_geocode( if not -180 <= lng <= 180: raise ValueError("Longitude must be in range -180 to 180.") - resp = self.geo_search_api.get_reverse_geocoding( - lat=lat, lng=lng, limit=limit, lang=lang - ) + resp = self.geo_search_api.get_reverse_geocoding(lat=lat, lng=lng, limit=limit, lang=lang) return ReverseGeocoderResponse.new(resp.json()) def calculate_isoline( @@ -801,9 +797,7 @@ def matrix( ) status_url = resp["statusUrl"] while True: - resp_status = self.matrix_routing_api.get_async_matrix_route_status( - status_url - ) + resp_status = self.matrix_routing_api.get_async_matrix_route_status(status_url) if resp_status.status_code == 200 and resp_status.json().get("error"): raise ApiError(resp_status) elif resp_status.status_code == 303: diff --git a/tests/test_ls.py b/tests/test_ls.py index 49ec006..ff62dad 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -32,9 +32,7 @@ PolygonRegion, WorldRegion, ) -from here_location_services.config.routing_config import ( - AVOID_FEATURES as ROUTING_AVOID_FEATURES, -) +from here_location_services.config.routing_config import AVOID_FEATURES as ROUTING_AVOID_FEATURES from here_location_services.config.routing_config import ( ROUTE_COURSE, ROUTE_MATCH_SIDEOF_STREET, @@ -114,7 +112,7 @@ def test_isonline_routing(): origin=[52.5, 13.4], range="3000", range_type=RANGE_TYPE.time, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, departure_time=datetime.now(), ) @@ -131,7 +129,7 @@ def test_isonline_routing(): destination=[82.8628, 135.00], range="3000", range_type=RANGE_TYPE.distance, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, arrival_time=datetime.now(), ) with pytest.raises(ValueError): @@ -139,7 +137,7 @@ def test_isonline_routing(): origin=[52.5, 13.4], range="900", range_type=RANGE_TYPE.time, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, destination=[52.5, 13.4], ) with pytest.raises(ApiError): @@ -148,7 +146,7 @@ def test_isonline_routing(): origin=[52.5, 13.4], range="900", range_type=RANGE_TYPE.time, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, ) @@ -160,13 +158,13 @@ def test_isonline_routing_exception(): ls.calculate_isoline( range="900", range_type=RANGE_TYPE.time, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, ) with pytest.raises(ValueError): ls.calculate_isoline( range="900", range_type=RANGE_TYPE.time, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, arrival_time=datetime.now(), origin=[52.5, 13.4], ) @@ -174,7 +172,7 @@ def test_isonline_routing_exception(): ls.calculate_isoline( range="900", range_type=RANGE_TYPE.time, - transportMode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, departure_time=datetime.now(), destination=[52.5, 13.4], ) @@ -183,9 +181,7 @@ def test_isonline_routing_exception(): @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_ls_discover(): ls = LS(api_key=LS_API_KEY) - result = ls.discover( - query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en" - ) + result = ls.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, lang="en") assert len(result.items) == 20 result2 = ls.discover( @@ -211,9 +207,7 @@ def test_ls_discover(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.discover( - query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10 - ) + ls2.discover(query="starbucks", center=[19.1663, 72.8526], radius=10000, limit=10) @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") @@ -280,18 +274,14 @@ def test_ls_lookup(): with pytest.raises(ApiError): ls2 = LS(api_key="dummy") - ls2.lookup( - location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2" - ) + ls2.lookup(location_id="here:pds:place:276u0vhj-b0bace6448ae4b0fbc1d5e323998a7d2") @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_car_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [ - AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) - ] + avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via1 = Via(lat=52.52426, lng=13.43000) via2 = Via(lat=52.52624, lng=13.44012) @@ -306,16 +296,12 @@ def test_car_route(): avoid_features=avoid_features, exclude=["IND", "NZL", "AUS"], ) - assert result.response["routes"][0]["sections"][0]["departure"]["place"][ - "location" - ] == { + assert result.response["routes"][0]["sections"][0]["departure"]["place"]["location"] == { "lat": 52.5137479, "lng": 13.4246242, "elv": 76.0, } - assert result.response["routes"][0]["sections"][1]["departure"]["place"][ - "location" - ] == { + assert result.response["routes"][0]["sections"][1]["departure"]["place"]["location"] == { "lat": 52.5242323, "lng": 13.4301462, "elv": 80.0, @@ -390,9 +376,7 @@ def test_car_route_extra_options(): def test_bicycle_route(): """Test routing API for car route.""" ls = LS(api_key=LS_API_KEY) - avoid_areas = [ - AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) - ] + avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.bicycle_route( @@ -422,9 +406,7 @@ def test_truck_route(): tunnel_category="B", axle_count=4, ) - avoid_areas = [ - AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) - ] + avoid_areas = [AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)] avoid_features = [ROUTING_AVOID_FEATURES.tollRoad] via = Via(lat=52.52426, lng=13.43000) _ = ls.truck_route( @@ -528,9 +510,7 @@ def test_matrix_route(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox( - 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 - ) + avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) assert json.loads(avoid_areas.__str__()) == { "type": "boundingBox", "north": 68.1766451354, @@ -592,9 +572,7 @@ def test_matrix_route_async(): ] region_definition = WorldRegion() matrix_attributes = [MATRIX_ATTRIBUTES.distances, MATRIX_ATTRIBUTES.travelTimes] - avoid_areas = AvoidBoundingBox( - 68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078 - ) + avoid_areas = AvoidBoundingBox(68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078) truck = Truck( shipped_hazardous_goods=[SHIPPED_HAZARDOUS_GOODS.explosive], gross_weight=100, diff --git a/tests/test_ls_apis.py b/tests/test_ls_apis.py index 120c4da..6dbc22c 100644 --- a/tests/test_ls_apis.py +++ b/tests/test_ls_apis.py @@ -7,8 +7,8 @@ import requests from here_location_services.config.isoline_routing_config import ( - RANGE_TYPE, ISOLINE_ROUTING_TRANSPORT_MODE, + RANGE_TYPE, ) from here_location_services.config.matrix_routing_config import WorldRegion from here_location_services.exceptions import ApiError From 40279ef2d7b85428188fc398ed4b954fe4c50274 Mon Sep 17 00:00:00 2001 From: Aayush Jain Date: Tue, 10 Aug 2021 19:53:52 +0530 Subject: [PATCH 10/10] fixes --- .../config/isoline_routing_config.py | 6 +-- here_location_services/isoline_routing_api.py | 6 +-- here_location_services/responses.py | 7 ++- tests/test_ls.py | 47 ++++++++++++++++++- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/here_location_services/config/isoline_routing_config.py b/here_location_services/config/isoline_routing_config.py index c2c2cac..9816ffe 100644 --- a/here_location_services/config/isoline_routing_config.py +++ b/here_location_services/config/isoline_routing_config.py @@ -69,13 +69,13 @@ class OptimisedFor(Bunch): ) -class AvoidFeatures(Bunch): +class IsolineRoutingAvoidFeatures(Bunch): """A class to define constant values for features to avoid during isoline calculation.""" #: Use this config for avoid_features of isoline API. -#: Example: for ``tollRoad`` avoid_features use ``AVOID_FEATURES.tollRoad``. -AVOID_FEATURES = AvoidFeatures( +#: Example: for ``tollRoad`` avoid_features use ``ISOLINE_ROUTING_AVOID_FEATURES.tollRoad``. +ISOLINE_ROUTING_AVOID_FEATURES = IsolineRoutingAvoidFeatures( **{ "tollRoad": "tollRoad", "controlledAccessHighway": "controlledAccessHighway", diff --git a/here_location_services/isoline_routing_api.py b/here_location_services/isoline_routing_api.py index 3cf760f..d8d1e5b 100644 --- a/here_location_services/isoline_routing_api.py +++ b/here_location_services/isoline_routing_api.py @@ -101,15 +101,15 @@ def get_isoline_routing( """ path = "v8/isolines" url = f"{self._base_url}/{path}" - params: Dict[str, str] = { + params: Dict[str, Any] = { "range[type]": range_type, "range[values]": range, "transportMode": transport_mode, } if origin: - params["origin"] = (",".join([str(i) for i in origin]),) + params["origin"] = ",".join([str(i) for i in origin]) if destination: - params["destination"] = (",".join([str(i) for i in destination]),) + params["destination"] = ",".join([str(i) for i in destination]) if arrival_time: params["arrivalTime"] = arrival_time.isoformat(timespec="seconds") if departure_time: diff --git a/here_location_services/responses.py b/here_location_services/responses.py index 5442011..215c06c 100644 --- a/here_location_services/responses.py +++ b/here_location_services/responses.py @@ -74,7 +74,12 @@ class IsolineResponse(ApiResponse): def __init__(self, **kwargs): super().__init__() - self._filters = {"departure": None, "isolines": None, "notices": None} + self._filters = { + "departure": None, + "arrival": None, + "isolines": None, + "notices": None, + } for param, default in self._filters.items(): setattr(self, param, kwargs.get(param, default)) diff --git a/tests/test_ls.py b/tests/test_ls.py index ff62dad..547c192 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -18,6 +18,7 @@ WayPointOptions, ) from here_location_services.config.isoline_routing_config import ( + ISOLINE_ROUTING_AVOID_FEATURES, ISOLINE_ROUTING_TRANSPORT_MODE, RANGE_TYPE, ) @@ -108,22 +109,64 @@ def test_ls_reverse_geocoding_exception(): def test_isonline_routing(): """Test isonline routing api.""" ls = LS(api_key=LS_API_KEY) + place_options = PlaceOptions( + course=ROUTE_COURSE.west, + sideof_street_hint=[52.512149, 13.304076], + match_sideof_street=ROUTE_MATCH_SIDEOF_STREET.always, + radius=10, + min_course_distance=10, + ) + assert json.loads(place_options.__str__()) == { + "course": 270, + "sideOfStreetHint": "52.512149,13.304076", + "matchSideOfStreet": "always", + "namehint": None, + "radius": 10, + "minCourseDistance": 10, + } + origin_waypoint_options = WayPointOptions(stop_duration=0) + result = ls.calculate_isoline( origin=[52.5, 13.4], - range="3000", + range="1000,3000", range_type=RANGE_TYPE.time, transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, departure_time=datetime.now(), + truck=Truck( + shipped_hazardous_goods=[SHIPPED_HAZARDOUS_GOODS.explosive], + gross_weight=100, + weight_per_axle=10, + height=10, + width=10, + length=10, + tunnel_category="B", + axle_count=4, + ), + shape_max_points=100, + avoid_features=[ISOLINE_ROUTING_AVOID_FEATURES.tollRoad], + origin_place_options=place_options, + origin_waypoint_options=origin_waypoint_options, ) assert result.isolines assert result.departure coordinates = result.isolines[0]["polygons"][0]["outer"] - assert coordinates geo_json = result.to_geojson() assert geo_json.type == "FeatureCollection" + destination_waypoint_options = WayPointOptions(stop_duration=0) + result2 = ls.calculate_isoline( + destination=[52.51578, 13.37749], + range="600", + range_type=RANGE_TYPE.time, + transport_mode=ISOLINE_ROUTING_TRANSPORT_MODE.car, + destination_place_options=place_options, + destination_waypoint_options=destination_waypoint_options, + ) + assert result2.isolines + assert result2.arrival + with pytest.raises(ValueError): ls.calculate_isoline( destination=[82.8628, 135.00],