python code for parging the .tcx file created by Runtastic
see http://takatakamanbou.hatenablog.com/entry/2015/12/31/231200
python code for parging the .tcx file created by Runtastic
see http://takatakamanbou.hatenablog.com/entry/2015/12/31/231200
##### parsing the .tcx file created by Runtastic ##### | |
import xml.etree.ElementTree as ET | |
import datetime | |
import dateutil.parser | |
import pytz | |
import numpy as np | |
# namespace | |
ns = { 'TCDv2': 'http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2' } | |
def readTCD( fn ): | |
tree = ET.parse( fn ) | |
dictHeader = {} | |
# <TrainingCenterDatabase> | |
# <Activities> | |
# <Activity Sport="running"> | |
# : | |
# </Activity> | |
# </Activities> | |
# </TrainingCenterDatabase> | |
TrainingCenterDatabase = tree.getroot() | |
Activities = TrainingCenterDatabase.find( 'TCDv2:Activities', ns ) | |
Activity = Activities.find( 'TCDv2:Activity', ns ) | |
dictHeader['Sport'] = Activity.attrib['Sport'] | |
# <Activity Sport="running"> | |
# <Id>2015-12-31T02:10:16.000Z</Id> | |
# <Lap StartTime="2015-12-31T02:10:16.000Z"> | |
# <TotalTimeSeconds>11191</TotalTimeSeconds> | |
# <DistanceMeters>27001</DistanceMeters> | |
# <Calories>1715</Calories> | |
# <AverageHeartRateBpm> | |
# <Value>150</Value> | |
# </AverageHeartRateBpm> | |
# <MaximumHeartRateBpm> | |
# <Value>189</Value> | |
# </MaximumHeartRateBpm> | |
# <MaximumSpeed>4.255025</MaximumSpeed> | |
# <TriggerMethod>Manual</TriggerMethod> | |
Id = Activity.find( 'TCDv2:Id', ns ) | |
Lap = Activity.find( 'TCDv2:Lap', ns ) | |
Lap_StartTime = Lap.attrib['StartTime'] | |
TotalTimeSeconds = Lap.find( 'TCDv2:TotalTimeSeconds', ns ) | |
DistanceMeters = Lap.find( 'TCDv2:DistanceMeters', ns ) | |
Calories = Lap.find( 'TCDv2:Calories', ns ) | |
startTimeUTC = dateutil.parser.parse( Lap_StartTime ) | |
startTimeJST = startTimeUTC.astimezone( pytz.timezone( 'Asia/Tokyo' ) ) | |
totaltime = float( TotalTimeSeconds.text ) | |
distance = float( DistanceMeters.text ) | |
averagePace = totaltime * 1000 / distance | |
dictHeader['Id'] = Id.text | |
dictHeader['TotalTimeSeconds'] = int( TotalTimeSeconds.text ) | |
dictHeader['DistanceMeters'] = int( DistanceMeters.text ) | |
dictHeader['Calories'] = int( Calories.text ) | |
dictHeader['StartTime'] = startTimeJST.isoformat() | |
dictHeader['AveragePace'] = averagePace | |
# <Track> | |
# <Trackpoint> | |
# ... | |
# </Trackpoint> | |
# </Track> | |
Track = Lap.find( 'TCDv2:Track', ns ) | |
Trackpoints = Track.findall( 'TCDv2:Trackpoint', ns ) | |
npoint = len( Trackpoints ) | |
pointArray = np.empty( npoint, dtype = object ) | |
time_prev = 0.0 | |
time_pause = 0.0 | |
for i, Trackpoint in enumerate( Trackpoints ): | |
# <Time>2015-12-31T05:22:32.000Z</Time> | |
Time = Trackpoint.find( 'TCDv2:Time', ns ) | |
delta = dateutil.parser.parse( Time.text ) - startTimeUTC | |
time = delta.total_seconds() | |
# <DistanceMeters>27001</DistanceMeters> | |
DistanceMeters = Trackpoint.find( 'TCDv2:DistanceMeters', ns ) | |
distance = float( DistanceMeters.text ) | |
# <AltitudeMeters>85.8173828125</AltitudeMeters> | |
AltitudeMeters = Trackpoint.find( 'TCDv2:AltitudeMeters', ns ) | |
altitude = float( AltitudeMeters.text ) | |
# <Position> | |
# <LatitudeDegrees>34.9740676879882812</LatitudeDegrees> | |
# <LongitudeDegrees>135.9050292968750000</LongitudeDegrees> | |
# </Position> | |
Position = Trackpoint.find( 'TCDv2:Position', ns ) | |
LatitudeDegrees = Position.find( 'TCDv2:LatitudeDegrees', ns ) | |
LongitudeDegrees = Position.find( 'TCDv2:LongitudeDegrees', ns ) | |
latitude = float( LatitudeDegrees.text ) | |
longitude = float( LongitudeDegrees.text ) | |
# considering the time for pausing | |
if time - time_prev > 30: | |
time_pause += time - time_prev - 3 | |
#print i, time, time_pause | |
d = { 'time':time - time_pause, 'distance':distance, 'altitude':altitude, | |
'latitude':latitude, 'longitude':longitude } | |
# <HeartRateBpm> occasionally inserted | |
# <Value>126</Value> | |
# </HeartRateBpm> | |
HeartRateBpm = Trackpoint.find( 'TCDv2:HeartRateBpm', ns ) | |
if HeartRateBpm != None: | |
hr = HeartRateBpm.find( 'TCDv2:Value', ns ).text | |
d['heartrate'] = float( hr ) | |
pointArray[i] = d | |
time_prev = time | |
return dictHeader, pointArray | |
def compData( pointArray ): | |
npoint = pointArray.shape[0] | |
pointData = np.empty( ( npoint, 5 ) ) | |
heartList = [] | |
for i, d in enumerate( pointArray ): | |
mtime = d['time'] / 60 | |
pointData[i, 0] = mtime | |
pointData[i, 1] = d['distance'] | |
pointData[i, 2] = d['altitude'] | |
pointData[i, 3] = d['latitude'] | |
pointData[i, 4] = d['longitude'] | |
if 'heartrate' in d: | |
heartList.append( [ mtime, d['distance'], d['heartrate'] ] ) | |
heartData = np.array( heartList ) | |
dt = 10 | |
time = pointData[dt:, 0] - pointData[:npoint - dt, 0] | |
dist = pointData[dt:, 1] - pointData[:npoint - dt, 1] | |
pace = time * 1000.0 / dist | |
paceData = np.vstack( ( pointData[dt:, 0], pointData[dt:, 1], pace ) ).T | |
return pointData, paceData, heartData | |
if __name__ == '__main__': | |
import matplotlib.pyplot as plt | |
fn = 'runtastic_20151231_1431.tcx' | |
dh, pt = readTCD( fn ) | |
print 'Session Type:', dh['Sport'] | |
print 'Start:', dh['StartTime'] | |
print 'Distance: %.2f km' % ( dh['DistanceMeters'] / 1000.0 ) | |
s = dh['TotalTimeSeconds'] | |
th = s / 3600 | |
tm = ( s - th * 3600 ) / 60 | |
ts = s - th * 3600 - tm * 60 | |
print 'Time: %02d:%02d:%02d' % ( th, tm, ts ) | |
s = dh['AveragePace'] | |
tm = np.floor( s / 60 ) | |
ts = np.round( s - tm * 60 ) | |
print 'Pace: %02d:%02d min/km' % ( tm, ts ) | |
print 'Calories: %d kcal' % dh['Calories'] | |
pointData, paceData, heartData = compData( pt ) | |
np.savetxt( 'point.txt', pointData ) | |
np.savetxt( 'pace.txt', paceData ) | |
np.savetxt( 'heart.txt', heartData ) | |
fig, ax1 = plt.subplots() | |
ax1.plot( pointData[:, 0], pointData[:, 2], 'b-' ) | |
ax1.plot( heartData[:, 0], heartData[:, 2], 'r-' ) | |
ax1.set_xlabel( 'time [min]' ) | |
ax1.set_ylabel( 'altitude [m] & heartrate [Bpm]' ) | |
ax1.set_xticks( np.arange( 0, np.max( pointData[:, 0] ), 30 ) ) | |
ax1.set_ylim( bottom = -100 ) | |
ax2 = ax1.twinx() | |
ax2.plot( paceData[:, 0], paceData[:, 2], 'g-' ) | |
ax2.set_ylabel( 'pace [min/km]', color = 'g' ) | |
ax2.set_yticks( [ 5, 10, 15, 20 ] ) | |
for tl in ax2.get_yticklabels(): | |
tl.set_color('g') | |
ax2.set_ylim( top = 50 ) | |
plt.show() |