diff --git a/fittrackee_api/fittrackee_api/activities/utils.py b/fittrackee_api/fittrackee_api/activities/utils.py index 729f9e65..6ebdbab6 100644 --- a/fittrackee_api/fittrackee_api/activities/utils.py +++ b/fittrackee_api/fittrackee_api/activities/utils.py @@ -149,11 +149,11 @@ def edit_activity(activity, activity_data, auth_user_id): return activity -def get_gpx_data(parsed_gpx, max_speed, start): +def get_gpx_data(parsed_gpx, max_speed, start, stopped_time_btwn_seg): gpx_data = {'max_speed': (max_speed / 1000) * 3600, 'start': start} duration = parsed_gpx.get_duration() - gpx_data['duration'] = timedelta(seconds=duration) + gpx_data['duration'] = timedelta(seconds=duration) + stopped_time_btwn_seg ele = parsed_gpx.get_elevation_extremes() gpx_data['elevation_max'] = ele.maximum @@ -165,7 +165,8 @@ def get_gpx_data(parsed_gpx, max_speed, start): mv = parsed_gpx.get_moving_data() gpx_data['moving_time'] = timedelta(seconds=mv.moving_time) - gpx_data['stop_time'] = timedelta(seconds=mv.stopped_time) + gpx_data['stop_time'] = (timedelta(seconds=mv.stopped_time) + + stopped_time_btwn_seg) distance = mv.moving_distance + mv.stopped_distance gpx_data['distance'] = distance / 1000 @@ -196,16 +197,33 @@ def get_gpx_info(gpx_file): start = 0 map_data = [] weather_data = [] + segments_nb = len(gpx.tracks[0].segments) + prev_seg_last_point = None + no_stopped_time = timedelta(seconds=0) + stopped_time_btwn_seg = no_stopped_time for segment_idx, segment in enumerate(gpx.tracks[0].segments): segment_start = 0 + segment_points_nb = len(segment.points) for point_idx, point in enumerate(segment.points): - if point_idx == 0 and start == 0: - start = point.time - weather_data.append(get_weather(point)) - if (point_idx == (len(segment.points) - 1) and - segment_idx == (len(gpx.tracks[0].segments) - 1)): - weather_data.append(get_weather(point)) + if point_idx == 0: + # first gpx point => get weather + if start == 0: + start = point.time + weather_data.append(get_weather(point)) + + # if a previous segment exists, calculate stopped time between + # the two segments + if prev_seg_last_point: + stopped_time_btwn_seg = point.time - prev_seg_last_point + + # last segment point + if point_idx == (segment_points_nb - 1): + prev_seg_last_point = point.time + + # last gpx point => get weather + if segment_idx == (segments_nb - 1): + weather_data.append(get_weather(point)) map_data.append([ point.longitude, point.latitude ]) @@ -217,12 +235,12 @@ def get_gpx_info(gpx_file): max_speed = segment_max_speed segment_data = get_gpx_data( - segment, segment_max_speed, segment_start + segment, segment_max_speed, segment_start, no_stopped_time ) segment_data['idx'] = segment_idx gpx_data['segments'].append(segment_data) - full_gpx_data = get_gpx_data(gpx, max_speed, start) + full_gpx_data = get_gpx_data(gpx, max_speed, start, stopped_time_btwn_seg) gpx_data = {**gpx_data, **full_gpx_data} bounds = gpx.get_bounds() gpx_data['bounds'] = [ @@ -345,7 +363,7 @@ def process_one_gpx_file(params, filename): except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e: raise ActivityException('error', 'Error during gpx file parsing.', e) except Exception as e: - raise ActivityException('error', 'Error during activity file save.', e) + raise ActivityException('error', 'Error during gpx processing.', e) try: new_activity = create_activity( diff --git a/fittrackee_api/fittrackee_api/tests/conftest.py b/fittrackee_api/fittrackee_api/tests/conftest.py index f3083e89..177e2dc1 100644 --- a/fittrackee_api/fittrackee_api/tests/conftest.py +++ b/fittrackee_api/fittrackee_api/tests/conftest.py @@ -468,3 +468,120 @@ def gpx_file_invalid_xml(): '' # noqa ' ' ) + + +@pytest.fixture() +def gpx_file_with_segments(): + return ( + '' + '' # noqa + ' ' + ' ' + ' just an activity' + ' ' + ' ' + ' 998' + ' ' + ' ' + ' ' + ' 998' + ' ' + ' ' + ' ' + ' 994' + ' ' + ' ' + ' ' + ' 994' + ' ' + ' ' + ' ' + ' 994' + ' ' + ' ' + ' ' + ' 993' + ' ' + ' ' + ' ' + ' 992' + ' ' + ' ' + ' ' + ' 992' + ' ' + ' ' + ' ' + ' 987' + ' ' + ' ' + ' ' + ' ' + ' ' + ' 987' + ' ' + ' ' + ' ' + ' 987' + ' ' + ' ' + ' ' + ' 987' + ' ' + ' ' + ' ' + ' 986' + ' ' + ' ' + ' ' + ' 986' + ' ' + ' ' + ' ' + ' 986' + ' ' + ' ' + ' ' + ' 985' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 979' + ' ' + ' ' + ' ' + ' 981' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 979' + ' ' + ' ' + ' ' + ' 979' + ' ' + ' ' + ' ' + ' 975' + ' ' + ' ' + ' ' + ' ' + '' + ) diff --git a/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py b/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py index 98a1bbc0..92150fd0 100644 --- a/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py +++ b/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py @@ -66,6 +66,75 @@ def assert_activity_data_with_gpx(data): assert records[3]['value'] == 4.61 +def assert_activity_data_with_gpx_segments(data): + assert 'creation_date' in data['data']['activities'][0] + assert 'Tue, 13 Mar 2018 12:44:45 GMT' == data['data']['activities'][0]['activity_date'] # noqa + assert 1 == data['data']['activities'][0]['user_id'] + assert 1 == data['data']['activities'][0]['sport_id'] + assert '0:04:10' == data['data']['activities'][0]['duration'] + assert data['data']['activities'][0]['ascent'] == 0.4 + assert data['data']['activities'][0]['ave_speed'] == 4.59 + assert data['data']['activities'][0]['descent'] == 23.4 + assert data['data']['activities'][0]['distance'] == 0.3 + assert data['data']['activities'][0]['max_alt'] == 998.0 + assert data['data']['activities'][0]['max_speed'] is None # not enough points # noqa + assert data['data']['activities'][0]['min_alt'] == 975.0 + assert data['data']['activities'][0]['moving'] == '0:03:55' + assert data['data']['activities'][0]['pauses'] == '0:00:15' + assert data['data']['activities'][0]['with_gpx'] is True + assert data['data']['activities'][0]['map'] is not None + assert data['data']['activities'][0]['weather_start'] is None + assert data['data']['activities'][0]['weather_end'] is None + assert data['data']['activities'][0]['notes'] is None + assert len(data['data']['activities'][0]['segments']) == 2 + + segment = data['data']['activities'][0]['segments'][0] + assert segment['activity_id'] == 1 + assert segment['segment_id'] == 0 + assert segment['duration'] == '0:01:30' + assert segment['ascent'] is None + assert segment['ave_speed'] == 4.53 + assert segment['descent'] == 11.0 + assert segment['distance'] == 0.113 + assert segment['max_alt'] == 998.0 + assert segment['max_speed'] is None + assert segment['min_alt'] == 987.0 + assert segment['moving'] == '0:01:30' + assert segment['pauses'] is None + + segment = data['data']['activities'][0]['segments'][1] + assert segment['activity_id'] == 1 + assert segment['segment_id'] == 1 + assert segment['duration'] == '0:02:25' + assert segment['ascent'] == 0.4 + assert segment['ave_speed'] == 4.62 + assert segment['descent'] == 12.4 + assert segment['distance'] == 0.186 + assert segment['max_alt'] == 987.0 + assert segment['max_speed'] is None + assert segment['min_alt'] == 975.0 + assert segment['moving'] == '0:02:25' + assert segment['pauses'] is None + + records = data['data']['activities'][0]['records'] + assert len(records) == 3 + assert records[0]['sport_id'] == 1 + assert records[0]['activity_id'] == 1 + assert records[0]['record_type'] == 'LD' + assert records[0]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT' + assert records[0]['value'] == '0:03:55' + assert records[1]['sport_id'] == 1 + assert records[1]['activity_id'] == 1 + assert records[1]['record_type'] == 'FD' + assert records[1]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT' + assert records[1]['value'] == 0.3 + assert records[2]['sport_id'] == 1 + assert records[2]['activity_id'] == 1 + assert records[2]['record_type'] == 'AS' + assert records[2]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT' + assert records[2]['value'] == 4.59 + + def assert_activity_data_wo_gpx(data): assert 'creation_date' in data['data']['activities'][0] assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' # noqa @@ -236,6 +305,97 @@ def test_get_an_activity_with_gpx(app, user_1, sport_1_cycling, gpx_file): assert data['message'] == 'internal error.' +def test_get_an_activity_with_gpx_segments( + app, user_1, sport_1_cycling, gpx_file_with_segments): + client = app.test_client() + resp_login = client.post( + '/api/auth/login', + data=json.dumps(dict( + email='test@test.com', + password='12345678' + )), + content_type='application/json' + ) + client.post( + '/api/activities', + data=dict( + file=(BytesIO(str.encode(gpx_file_with_segments)), 'example.gpx'), + data='{"sport_id": 1}' + ), + headers=dict( + content_type='multipart/form-data', + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + response = client.get( + '/api/activities/1', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert len(data['data']['activities']) == 1 + assert 'just an activity' == data['data']['activities'][0]['title'] + assert_activity_data_with_gpx_segments(data) + + map_id = data['data']['activities'][0]['map'] + + response = client.get( + '/api/activities/1/gpx', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert '' in data['message'] + assert len(data['data']['gpx']) != '' + + response = client.get( + f'/api/activities/map/{map_id}', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + assert response.status_code == 200 + + # error case in the same test to avoid generate a new map file + activity = Activity.query.filter_by(id=1).first() + activity.map = 'incorrect path' + + assert response.status_code == 200 + assert 'success' in data['status'] + assert '' in data['message'] + assert len(data['data']['gpx']) != '' + + response = client.get( + f'/api/activities/map/{map_id}', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 500 + assert data['status'] == 'error' + assert data['message'] == 'internal error.' + + def test_get_an_activity_with_gpx_different_user( app, user_1, user_2, sport_1_cycling, gpx_file): client = app.test_client()