pause duration calculation with segments - fix #29
This commit is contained in:
parent
f4f486606d
commit
3b42534d77
@ -149,11 +149,11 @@ def edit_activity(activity, activity_data, auth_user_id):
|
|||||||
return activity
|
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}
|
gpx_data = {'max_speed': (max_speed / 1000) * 3600, 'start': start}
|
||||||
|
|
||||||
duration = parsed_gpx.get_duration()
|
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()
|
ele = parsed_gpx.get_elevation_extremes()
|
||||||
gpx_data['elevation_max'] = ele.maximum
|
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()
|
mv = parsed_gpx.get_moving_data()
|
||||||
gpx_data['moving_time'] = timedelta(seconds=mv.moving_time)
|
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
|
distance = mv.moving_distance + mv.stopped_distance
|
||||||
gpx_data['distance'] = distance / 1000
|
gpx_data['distance'] = distance / 1000
|
||||||
|
|
||||||
@ -196,15 +197,32 @@ def get_gpx_info(gpx_file):
|
|||||||
start = 0
|
start = 0
|
||||||
map_data = []
|
map_data = []
|
||||||
weather_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):
|
for segment_idx, segment in enumerate(gpx.tracks[0].segments):
|
||||||
segment_start = 0
|
segment_start = 0
|
||||||
|
segment_points_nb = len(segment.points)
|
||||||
for point_idx, point in enumerate(segment.points):
|
for point_idx, point in enumerate(segment.points):
|
||||||
if point_idx == 0 and start == 0:
|
if point_idx == 0:
|
||||||
|
# first gpx point => get weather
|
||||||
|
if start == 0:
|
||||||
start = point.time
|
start = point.time
|
||||||
weather_data.append(get_weather(point))
|
weather_data.append(get_weather(point))
|
||||||
if (point_idx == (len(segment.points) - 1) and
|
|
||||||
segment_idx == (len(gpx.tracks[0].segments) - 1)):
|
# 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))
|
weather_data.append(get_weather(point))
|
||||||
map_data.append([
|
map_data.append([
|
||||||
point.longitude, point.latitude
|
point.longitude, point.latitude
|
||||||
@ -217,12 +235,12 @@ def get_gpx_info(gpx_file):
|
|||||||
max_speed = segment_max_speed
|
max_speed = segment_max_speed
|
||||||
|
|
||||||
segment_data = get_gpx_data(
|
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
|
segment_data['idx'] = segment_idx
|
||||||
gpx_data['segments'].append(segment_data)
|
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}
|
gpx_data = {**gpx_data, **full_gpx_data}
|
||||||
bounds = gpx.get_bounds()
|
bounds = gpx.get_bounds()
|
||||||
gpx_data['bounds'] = [
|
gpx_data['bounds'] = [
|
||||||
@ -345,7 +363,7 @@ def process_one_gpx_file(params, filename):
|
|||||||
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
|
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
|
||||||
raise ActivityException('error', 'Error during gpx file parsing.', e)
|
raise ActivityException('error', 'Error during gpx file parsing.', e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ActivityException('error', 'Error during activity file save.', e)
|
raise ActivityException('error', 'Error during gpx processing.', e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_activity = create_activity(
|
new_activity = create_activity(
|
||||||
|
@ -468,3 +468,120 @@ def gpx_file_invalid_xml():
|
|||||||
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||||
' <metadata/>'
|
' <metadata/>'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def gpx_file_with_segments():
|
||||||
|
return (
|
||||||
|
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
|
||||||
|
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||||
|
' <metadata/>'
|
||||||
|
' <trk>'
|
||||||
|
' <name>just an activity</name>'
|
||||||
|
' <trkseg>'
|
||||||
|
' <trkpt lat="44.68095" lon="6.07367">'
|
||||||
|
' <ele>998</ele>'
|
||||||
|
' <time>2018-03-13T12:44:45Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.68091" lon="6.07367">'
|
||||||
|
' <ele>998</ele>'
|
||||||
|
' <time>2018-03-13T12:44:50Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.6808" lon="6.07364">'
|
||||||
|
' <ele>994</ele>'
|
||||||
|
' <time>2018-03-13T12:45:00Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.68075" lon="6.07364">'
|
||||||
|
' <ele>994</ele>'
|
||||||
|
' <time>2018-03-13T12:45:05Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.68071" lon="6.07364">'
|
||||||
|
' <ele>994</ele>'
|
||||||
|
' <time>2018-03-13T12:45:10Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.68049" lon="6.07361">'
|
||||||
|
' <ele>993</ele>'
|
||||||
|
' <time>2018-03-13T12:45:30Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.68019" lon="6.07356">'
|
||||||
|
' <ele>992</ele>'
|
||||||
|
' <time>2018-03-13T12:45:55Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.68014" lon="6.07355">'
|
||||||
|
' <ele>992</ele>'
|
||||||
|
' <time>2018-03-13T12:46:00Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67995" lon="6.07358">'
|
||||||
|
' <ele>987</ele>'
|
||||||
|
' <time>2018-03-13T12:46:15Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' </trkseg>'
|
||||||
|
' <trkseg>'
|
||||||
|
' <trkpt lat="44.67977" lon="6.07364">'
|
||||||
|
' <ele>987</ele>'
|
||||||
|
' <time>2018-03-13T12:46:30Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67972" lon="6.07367">'
|
||||||
|
' <ele>987</ele>'
|
||||||
|
' <time>2018-03-13T12:46:35Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67966" lon="6.07368">'
|
||||||
|
' <ele>987</ele>'
|
||||||
|
' <time>2018-03-13T12:46:40Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67961" lon="6.0737">'
|
||||||
|
' <ele>986</ele>'
|
||||||
|
' <time>2018-03-13T12:46:45Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67938" lon="6.07377">'
|
||||||
|
' <ele>986</ele>'
|
||||||
|
' <time>2018-03-13T12:47:05Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67933" lon="6.07381">'
|
||||||
|
' <ele>986</ele>'
|
||||||
|
' <time>2018-03-13T12:47:10Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67922" lon="6.07385">'
|
||||||
|
' <ele>985</ele>'
|
||||||
|
' <time>2018-03-13T12:47:20Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67911" lon="6.0739">'
|
||||||
|
' <ele>980</ele>'
|
||||||
|
' <time>2018-03-13T12:47:30Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.679" lon="6.07399">'
|
||||||
|
' <ele>980</ele>'
|
||||||
|
' <time>2018-03-13T12:47:40Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67896" lon="6.07402">'
|
||||||
|
' <ele>980</ele>'
|
||||||
|
' <time>2018-03-13T12:47:45Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67884" lon="6.07408">'
|
||||||
|
' <ele>979</ele>'
|
||||||
|
' <time>2018-03-13T12:47:55Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67863" lon="6.07423">'
|
||||||
|
' <ele>981</ele>'
|
||||||
|
' <time>2018-03-13T12:48:15Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67858" lon="6.07425">'
|
||||||
|
' <ele>980</ele>'
|
||||||
|
' <time>2018-03-13T12:48:20Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67842" lon="6.07434">'
|
||||||
|
' <ele>979</ele>'
|
||||||
|
' <time>2018-03-13T12:48:35Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67837" lon="6.07435">'
|
||||||
|
' <ele>979</ele>'
|
||||||
|
' <time>2018-03-13T12:48:40Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' <trkpt lat="44.67822" lon="6.07442">'
|
||||||
|
' <ele>975</ele>'
|
||||||
|
' <time>2018-03-13T12:48:55Z</time>'
|
||||||
|
' </trkpt>'
|
||||||
|
' </trkseg>'
|
||||||
|
' </trk>'
|
||||||
|
'</gpx>'
|
||||||
|
)
|
||||||
|
@ -66,6 +66,75 @@ def assert_activity_data_with_gpx(data):
|
|||||||
assert records[3]['value'] == 4.61
|
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):
|
def assert_activity_data_wo_gpx(data):
|
||||||
assert 'creation_date' in data['data']['activities'][0]
|
assert 'creation_date' in data['data']['activities'][0]
|
||||||
assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' # noqa
|
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.'
|
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(
|
def test_get_an_activity_with_gpx_different_user(
|
||||||
app, user_1, user_2, sport_1_cycling, gpx_file):
|
app, user_1, user_2, sport_1_cycling, gpx_file):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
|
Loading…
Reference in New Issue
Block a user