Skip to content

Commit 87cfa46

Browse files
authored
Merge pull request #4354 from hove-io/add_bike_type_and_use_ebike_in_geovelo
add bike type
2 parents f804eda + e0ada45 commit 87cfa46

File tree

4 files changed

+112
-56
lines changed

4 files changed

+112
-56
lines changed

source/jormungandr/jormungandr/interfaces/v1/journey_common.py

+12
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,18 @@ def __init__(self, output_type_serializer):
869869
help='A journey containing a waiting section between TC and Zonal ODT with a duration greater to max_waiting_duration_odt '
870870
'will be discarded. Units : seconds. Must be > 0. Default value : 30 minutes',
871871
)
872+
parser_get.add_argument(
873+
"bike_type",
874+
# wordings are from https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dbicycle_rental#Types_of_bicycles_and_accessories
875+
type=OptionValue(
876+
[
877+
'city_bike',
878+
'ebike',
879+
]
880+
),
881+
default='city_bike',
882+
help="only available for Geovelo so far: whether to use electric bike.",
883+
)
872884

873885
def parse_args(self, region=None, uri=None):
874886
args = self.parsers['get'].parse_args()

source/jormungandr/jormungandr/street_network/geovelo.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,14 @@ def _pt_object_summary_isochrone(cls, pt_object):
141141
return [coord.lat, coord.lon, None]
142142

143143
@classmethod
144-
def _make_request_arguments_bike_details(cls, bike_speed_mps):
144+
def _make_request_arguments_bike_details(cls, bike_speed_mps, use_ebike):
145145
bike_speed = mps_to_kmph(bike_speed_mps)
146146

147147
return {
148148
'profile': 'MEDIAN', # can be BEGINNER, EXPERT
149149
'bikeType': 'TRADITIONAL', # can be 'BSS'
150150
'averageSpeed': bike_speed, # in km/h, BEGINNER sets it to 13
151+
'eBike': use_ebike, # whether to use an electric bike or not
151152
}
152153

153154
def sort_places_by_physical_mode_and_distance(self, points):
@@ -161,18 +162,18 @@ def priority_by_mode(point):
161162
return sorted(points, key=lambda point: (priority_by_mode(point), point.distance))
162163

163164
@classmethod
164-
def _make_request_arguments_isochrone(cls, origins, destinations, bike_speed_mps=3.33):
165+
def _make_request_arguments_isochrone(cls, origins, destinations, bike_speed_mps=3.33, use_ebike=False):
165166
origins_coord = [cls._pt_object_summary_isochrone(o) for o in origins]
166167
destinations_coord = [cls._pt_object_summary_isochrone(o) for o in destinations]
167168
return {
168169
'starts': [o for o in origins_coord],
169170
'ends': [o for o in destinations_coord],
170-
'bikeDetails': cls._make_request_arguments_bike_details(bike_speed_mps),
171+
'bikeDetails': cls._make_request_arguments_bike_details(bike_speed_mps, use_ebike),
171172
'transportMode': 'BIKE',
172173
}
173174

174175
@classmethod
175-
def _make_request_arguments_direct_path(cls, origin, destination, bike_speed_mps=3.33):
176+
def _make_request_arguments_direct_path(cls, origin, destination, bike_speed_mps=3.33, use_ebike=False):
176177
coord_orig = get_pt_object_coord(origin)
177178
coord_dest = get_pt_object_coord(destination)
178179
return {
@@ -181,7 +182,7 @@ def _make_request_arguments_direct_path(cls, origin, destination, bike_speed_mps
181182
{'latitude': coord_dest.lat, 'longitude': coord_dest.lon},
182183
],
183184
'transportModes': ['BIKE'],
184-
'bikeDetails': cls._make_request_arguments_bike_details(bike_speed_mps),
185+
'bikeDetails': cls._make_request_arguments_bike_details(bike_speed_mps, use_ebike),
185186
}
186187

187188
def _call_geovelo(self, url, method=requests.post, data=None):
@@ -271,6 +272,10 @@ def inside_zone(self, point):
271272
shapely_point = Point(coord.lon, coord.lat)
272273
return self.polygon_zone.contains(shapely_point)
273274

275+
@staticmethod
276+
def use_ebike(request):
277+
return request.get('bike_type') == 'ebike'
278+
274279
def use_this_service_for_sn_matrix(self, origins, destinations):
275280
# Use if shape is absent
276281
if not self.polygon_zone:
@@ -306,7 +311,9 @@ def _get_street_network_routing_matrix(
306311
)
307312
)
308313

309-
data = self._make_request_arguments_isochrone(origins, destinations, request['bike_speed'])
314+
data = self._make_request_arguments_isochrone(
315+
origins, destinations, request['bike_speed'], self.use_ebike(request)
316+
)
310317
r = self._call_geovelo(
311318
'{}/{}'.format(self.service_url, 'api/v2/routes_m2m'), requests.post, ujson.dumps(data)
312319
)
@@ -466,7 +473,7 @@ def _direct_path(
466473
raise InvalidArguments('Geovelo, mode {} not implemented'.format(mode))
467474

468475
data = self._make_request_arguments_direct_path(
469-
pt_object_origin, pt_object_destination, request['bike_speed']
476+
pt_object_origin, pt_object_destination, request['bike_speed'], self.use_ebike(request)
470477
)
471478
single_result = True
472479
if (

source/jormungandr/jormungandr/street_network/tests/geovelo_test.py

+85-48
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@
5050
}
5151

5252

53-
def direct_path_response_valid():
53+
def direct_path_response_valid(ebike):
5454
"""
5555
A mock of a valid response from geovelo.
5656
Reply to POST of {"starts":[[48.803064,2.443385, "refStart1"]],
5757
"ends":[[48.802049,2.426482, "refEnd1"]]}
5858
Modify with caution as it will affect every tests using these start and end uris.
5959
"""
60+
duration = 2822 if ebike else 3155
6061
return [
6162
{
6263
"distances": {
@@ -65,7 +66,7 @@ def direct_path_response_valid():
6566
"recommendedRoads": 7759.0,
6667
"total": 11393.0,
6768
},
68-
"duration": 3155,
69+
"duration": duration,
6970
"estimatedDatetimeOfArrival": "2017-02-24T16:52:08.711",
7071
"estimatedDatetimeOfDeparture": "2017-02-24T15:59:33.711",
7172
"id": "bG9jPTQ4Ljg4Nzk0LDIuMzE0MzM4JmxvYz00OC44Mjk5MjcsMi4zNzY3NDcjQkVHSU5ORVIjRmFsc2UjQkVHSU5ORVIjMTMjRmFsc2UjRmFsc2UjMjAxNy0wMi0yNCAxNTo1OTozMy43MTEwNjgjVFJBRElUSU9OQUwjMCMwI1JFQ09NTUVOREVEI0ZhbHNl",
@@ -116,7 +117,7 @@ def direct_path_response_valid():
116117
"profile": "BEGINNER",
117118
"verticalGain": 51,
118119
},
119-
"duration": 3155,
120+
"duration": duration,
120121
"estimatedDatetimeOfArrival": "2017-02-24T16:52:08.711",
121122
"estimatedDatetimeOfDeparture": "2017-02-24T15:59:33.711",
122123
"geometry": "_yzf|AszglClL`ShClEzCrHj@nNnBfD~AoHfDeD`nBmeC|AqBvAuAhWyWjVqUbJsJdd@uf@uPwh@sBgG{JoYmEkMeEwLxv@iu@}Hwj@k@}DcCqQ{Ims@m@yEbBcDbCyEhEiIhSm`@rPy\\bI}OfBwD|H}CaNehA}MwiAzA_LiBsPa@sDoBaSeAoKiCcHqBQmG{Rqk@meAa@oHRwSDuDv^yfChAmAnAkHrFqZnA{Gb@wDxc@m{Btd@a|Bz@cErHum@xLobBfBqMd@aHAkGmA{LaAkCaUwoBo@sFiR{aB_@wJuFoe@u@sDm@_CyTonB`@yDy@gIdA{EyBsTNoG_OqzBe@uDuAaLjD~DzAmAzNoLxCcBtDqEj_A}y@`_@i\\vFcF~c@w^~GuFbJgJ`JaHnCmDri@kd@nQcOxCiC~\\wYfCuBlC_CpB_BtCeCxDoEdx@}q@pG_Fpi@yd@lDuBhO_NjD}CxKyJ~IcIlIsHp^{\\dRwN~DuDjBqBxEkEzh@md@xHwG~AqAhGgFzMeLrM_Lp{@_t@lEeDfKsJvQ_PxDuCnCoCzO_NlIcHhSyQzFcF~CoCdsA{hAfDoCtSqStDyCjAmEn@uJ`Ywk@xEeJxHePwWabAaB{GkKqc@wL}n@sAuG`@qFxB}A~vBc|AfUkPjE}C|b@uZ`w@oj@~j@ca@tl@mb@zMgIzIoEvNiIlHaJjIcFfNwFhH_ChMuBpMoAlK_@rLH~Lp@|OjCfFjAtD|FfCz@lBp@nHe@xkCl~@vaAf]b{@pZzMhFnCb@nFpBtGkWlBuBbBcC|NgTzWu_@fB_CtCcC|@_Cvb@qgAtw@usBdRnMtJnGb\\vTy@hEvCtAfFdC`Bv@nEtBtExBhW`Q|HtE|ChBrAjAdL_Nb^qc@zUkYxCqDlCmDxRkTrAwAlEyDdH{GhZmm@lCgFfDkIvA|@lv@xe@pCfBpDlBpC_I|DuKr[gy@xAwDhKgX`FyMzBkFdCsG|JoWdk@c{AdLsZzEcObFgMbMa\\fR}f@nBzF`c@d`BjAdF`Mra@hGfSzw@f}BxF~LfCzDzA@lCzPnZdu@p^x{@rFzNzCfIpLl[fAvCvxA}`BPaDhFkGnHkJvF_IrKoIfCiDrz@nkBvKzFdEoDfGkEbO{MbFsErDzAz^_\\t_@e]lE_Gpc@ql@r_@wg@fk@gv@fa@{h@rNcRvLvWrWrk@fB`ElD~H`Pv]pEdKhAbCbN|[fBnElBtE~AlF`Mlu@rZoXjE{Dt^e\\|AnDzAhDzJwK]eAg@CTpBrGeBzBkBk@eBHa@J_AeAY}@}ClCiCw@wJ[cAeB?gScp@YtCxBjH~DvM",
@@ -238,7 +239,15 @@ def make_data_test():
238239
'''{
239240
"starts": [[48.2, 2.0, null]], "ends": [[48.3, 3.0, null], [48.4, 4.0, null]],
240241
"transportMode": "BIKE",
241-
"bikeDetails": {"profile": "MEDIAN", "averageSpeed": 12, "bikeType": "TRADITIONAL"}}'''
242+
"bikeDetails": {"profile": "MEDIAN", "averageSpeed": 12, "bikeType": "TRADITIONAL", "eBike": false}}'''
243+
)
244+
245+
data = Geovelo._make_request_arguments_isochrone(origins, destinations, use_ebike=True)
246+
assert ujson.loads(ujson.dumps(data)) == ujson.loads(
247+
'''{
248+
"starts": [[48.2, 2.0, null]], "ends": [[48.3, 3.0, null], [48.4, 4.0, null]],
249+
"transportMode": "BIKE",
250+
"bikeDetails": {"profile": "MEDIAN", "averageSpeed": 12, "bikeType": "TRADITIONAL", "eBike": true}}'''
242251
)
243252

244253

@@ -272,54 +281,76 @@ def get_matrix_test():
272281
def direct_path_geovelo_test():
273282
instance = MagicMock()
274283
geovelo = Geovelo(instance=instance, service_url=MOCKED_SERVICE_URL)
275-
resp_json = direct_path_response_valid()
276284

277285
origin = make_pt_object(type_pb2.ADDRESS, lon=2, lat=48.2, uri='refStart1')
278286
destination = make_pt_object(type_pb2.ADDRESS, lon=3, lat=48.3, uri='refEnd1')
279287
fallback_extremity = PeriodExtremity(str_to_time_stamp('20161010T152000'), False)
280288
with requests_mock.Mocker() as req:
289+
290+
def json_matcher(request, _):
291+
req_data = request.json()
292+
return direct_path_response_valid(req_data.get("bikeDetails", {}).get("eBike"))
293+
281294
req.post(
282295
'{}/api/v2/computedroutes?instructions=true&elevations=true&geometry=true'
283296
'&single_result=true&bike_stations=false&objects_as_ids=true&'.format(MOCKED_SERVICE_URL),
284-
json=resp_json,
285-
)
286-
geovelo_resp = geovelo.direct_path_with_fp(
287-
instance, 'bike', origin, destination, fallback_extremity, MOCKED_REQUEST, None, None
297+
json=json_matcher,
288298
)
289-
assert geovelo_resp.status_code == 200
290-
assert geovelo_resp.response_type == response_pb2.ITINERARY_FOUND
291-
assert len(geovelo_resp.journeys) == 1
292-
assert geovelo_resp.journeys[0].duration == 3155 # 52min35s
293-
assert geovelo_resp.journeys[0].requested_date_time == 0 # parameter datetime absent in MOCKED_REQUEST
294-
assert len(geovelo_resp.journeys[0].sections) == 1
295-
assert geovelo_resp.journeys[0].arrival_date_time == str_to_time_stamp('20161010T152000')
296-
assert geovelo_resp.journeys[0].departure_date_time == str_to_time_stamp('20161010T142725')
297-
assert geovelo_resp.journeys[0].sections[0].type == response_pb2.STREET_NETWORK
298-
assert geovelo_resp.journeys[0].sections[0].type == response_pb2.STREET_NETWORK
299-
assert geovelo_resp.journeys[0].sections[0].duration == 3155
300-
assert geovelo_resp.journeys[0].sections[0].length == 11393
301-
assert geovelo_resp.journeys[0].sections[0].street_network.coordinates[2].lon == 2.314258
302-
assert geovelo_resp.journeys[0].sections[0].street_network.coordinates[2].lat == 48.887428
303-
assert geovelo_resp.journeys[0].sections[0].origin == origin
304-
assert geovelo_resp.journeys[0].sections[0].destination == destination
305-
assert geovelo_resp.journeys[0].sections[0].street_network.path_items[1].name == "Rue Jouffroy d'Abbans"
306-
assert geovelo_resp.journeys[0].sections[0].street_network.path_items[1].direction == 0
307-
assert geovelo_resp.journeys[0].sections[0].street_network.path_items[1].length == 40
308-
assert geovelo_resp.journeys[0].sections[0].street_network.path_items[1].duration == 11
309-
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[0].distance_from_start == 0
310-
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[0].elevation == 45.5
311-
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[1].distance_from_start == 128
312-
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[1].elevation == 44
313-
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[2].distance_from_start == 274
314-
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[2].elevation == 50
315-
assert geovelo_resp.journeys[0].sections[0].cycle_lane_length == 98
316-
assert len(geovelo_resp.journeys[0].sections[0].street_network.street_information) == 3
317-
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[0].cycle_path_type == 2
318-
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[0].length == 58.0
319-
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[1].cycle_path_type == 2
320-
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[1].length == 40.0
321-
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[2].cycle_path_type == 2
322-
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[2].length == 0.0
299+
300+
def _test(request):
301+
302+
use_ebike = Geovelo.use_ebike(request)
303+
304+
geovelo_resp = geovelo.direct_path_with_fp(
305+
instance, 'bike', origin, destination, fallback_extremity, request, None, None
306+
)
307+
assert geovelo_resp.status_code == 200
308+
assert geovelo_resp.response_type == response_pb2.ITINERARY_FOUND
309+
assert len(geovelo_resp.journeys) == 1
310+
assert geovelo_resp.journeys[0].duration == 2822 if use_ebike else 3155
311+
assert geovelo_resp.journeys[0].arrival_date_time == str_to_time_stamp('20161010T152000')
312+
assert geovelo_resp.journeys[0].departure_date_time == str_to_time_stamp(
313+
'20161010T143258' if use_ebike else '20161010T142725'
314+
)
315+
assert (
316+
geovelo_resp.journeys[0].requested_date_time == 0
317+
) # parameter datetime absent in MOCKED_REQUEST
318+
assert len(geovelo_resp.journeys[0].sections) == 1
319+
assert geovelo_resp.journeys[0].sections[0].type == response_pb2.STREET_NETWORK
320+
assert geovelo_resp.journeys[0].sections[0].type == response_pb2.STREET_NETWORK
321+
assert geovelo_resp.journeys[0].sections[0].duration == 2822 if use_ebike else 3155
322+
assert geovelo_resp.journeys[0].sections[0].length == 11393
323+
assert geovelo_resp.journeys[0].sections[0].street_network.coordinates[2].lon == 2.314258
324+
assert geovelo_resp.journeys[0].sections[0].street_network.coordinates[2].lat == 48.887428
325+
assert geovelo_resp.journeys[0].sections[0].origin == origin
326+
assert geovelo_resp.journeys[0].sections[0].destination == destination
327+
assert (
328+
geovelo_resp.journeys[0].sections[0].street_network.path_items[1].name == "Rue Jouffroy d'Abbans"
329+
)
330+
assert geovelo_resp.journeys[0].sections[0].street_network.path_items[1].direction == 0
331+
assert geovelo_resp.journeys[0].sections[0].street_network.path_items[1].length == 40
332+
assert (
333+
geovelo_resp.journeys[0].sections[0].street_network.path_items[1].duration == 10
334+
if use_ebike
335+
else 11
336+
)
337+
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[0].distance_from_start == 0
338+
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[0].elevation == 45.5
339+
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[1].distance_from_start == 128
340+
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[1].elevation == 44
341+
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[2].distance_from_start == 274
342+
assert geovelo_resp.journeys[0].sections[0].street_network.elevations[2].elevation == 50
343+
assert geovelo_resp.journeys[0].sections[0].cycle_lane_length == 98
344+
assert len(geovelo_resp.journeys[0].sections[0].street_network.street_information) == 3
345+
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[0].cycle_path_type == 2
346+
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[0].length == 58.0
347+
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[1].cycle_path_type == 2
348+
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[1].length == 40.0
349+
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[2].cycle_path_type == 2
350+
assert geovelo_resp.journeys[0].sections[0].street_network.street_information[2].length == 0.0
351+
352+
_test(MOCKED_REQUEST)
353+
_test(dict({"bike_type": "ebike"}, **MOCKED_REQUEST))
323354

324355

325356
def direct_path_geovelo_zero_test():
@@ -392,7 +423,7 @@ def distances_durations_test():
392423
"""
393424
instance = MagicMock()
394425
geovelo = Geovelo(instance=instance, service_url=MOCKED_SERVICE_URL)
395-
resp_json = direct_path_response_valid()
426+
resp_json = direct_path_response_valid(False)
396427

397428
origin = make_pt_object(type_pb2.ADDRESS, lon=2, lat=48.2, uri='refStart1')
398429
destination = make_pt_object(type_pb2.ADDRESS, lon=3, lat=48.3, uri='refEnd1')
@@ -417,16 +448,22 @@ def make_request_arguments_bike_details_test():
417448
"""
418449
instance = MagicMock()
419450
geovelo = Geovelo(instance=instance, service_url=MOCKED_SERVICE_URL)
420-
data = geovelo._make_request_arguments_bike_details(bike_speed_mps=3.33)
451+
data = geovelo._make_request_arguments_bike_details(bike_speed_mps=3.33, use_ebike=False)
421452
assert ujson.loads(ujson.dumps(data)) == ujson.loads(
422453
'''{"profile": "MEDIAN", "averageSpeed": 12,
423-
"bikeType": "TRADITIONAL"}'''
454+
"bikeType": "TRADITIONAL","eBike": false}'''
455+
)
456+
457+
data = geovelo._make_request_arguments_bike_details(bike_speed_mps=4.1, use_ebike=False)
458+
assert ujson.loads(ujson.dumps(data)) == ujson.loads(
459+
'''{"profile": "MEDIAN", "averageSpeed": 15,
460+
"bikeType": "TRADITIONAL","eBike": false}'''
424461
)
425462

426-
data = geovelo._make_request_arguments_bike_details(bike_speed_mps=4.1)
463+
data = geovelo._make_request_arguments_bike_details(bike_speed_mps=4.1, use_ebike=True)
427464
assert ujson.loads(ujson.dumps(data)) == ujson.loads(
428465
'''{"profile": "MEDIAN", "averageSpeed": 15,
429-
"bikeType": "TRADITIONAL"}'''
466+
"bikeType": "TRADITIONAL","eBike": true}'''
430467
)
431468

432469

source/jormungandr/requirements_dev.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pytest==8.3.3 ; python_version >= "3.9"
1313
pytest-mock==3.14.0 ; python_version >= "3.9"
1414
pytest-cov==4.1.0 ; python_version >= "3.9"
1515

16-
requests-mock==1.0.0
16+
requests-mock==1.12.1
1717
flex==6.10.0
1818
jsonschema==2.6.0
1919
pytest-timeout==1.3.3

0 commit comments

Comments
 (0)