Message Boards Message Boards

GPS Mountainbike analysis

POSTED BY: Sander Huisman
18 Replies
POSTED BY: Sander Huisman

Nice ! I added some small teaser-animation.

POSTED BY: Vitaliy Kaurov
POSTED BY: Sander Huisman
POSTED BY: Vitaliy Kaurov

During quite some years of biking I have a big collection of GPS tracks. In order to get a quick idea of any specific track I wrote some code which generates a graphic file (.png) showing the significant info. It looks like this:

enter image description here

This file is then located in the same directory and with the same filename as the GPX file (but of course with a different extension!). Well, most comments and descriptions are in German, and certainly there is much more room for improvement (e.g. when reading in more than four GPX files at a time the kernel quits - maybe somebody finds the reason). The nice feature is that the altitude is coded in color. I am using a Garmin Oregon 600t which has access to barometric pressure; the altitude data are contained in GPX file.

Regards -- Henrik

Attachments:
POSTED BY: Henrik Schachner

Thanks for sharing your code, many similarities! great job!

POSTED BY: Sander Huisman
POSTED BY: Daniel Carvalho

In case your GPS device does not store the elevation or is bad at it (some devices don't have a pressure sensor, so it solely relies on GPS for the height, which is generally of bad quality) you can upload it to gpsies.com, and then download it again. It will add the terrain-elevation to it. (a trick i just found out this Sunday)

I'm not sure if the wolfram cloud allows for uploading files to the cloud. But if so, then it should not be too difficult right?

POSTED BY: Sander Huisman
POSTED BY: Sander Huisman
POSTED BY: Daniel Carvalho

Welcome to Lyon :-)

Thanks! You're also in Lyon?! We should meet up some time, I've always wondered how to pronounce your name :-P

POSTED BY: Sander Huisman
POSTED BY: Vitaliy Kaurov

Computing how many calories I have burned is very tricky to be reliable. Of course I could do something like:

WolframAlpha[StringTemplate["cycling `1` minutes `2` kilometer 70 kilogram male"][240, 60], {{"MetabolicProperties", 1}, "ComputableData"}]

and get the "energy expenditure" value from the array it returns. But this does not really include the elevation climb nor does it account for surface (was it muddy, sandy, stones, or clean smooth asphalt), nor the weather conditions (temperature, humidity, as well as wind).

There are roughly 3 types of power one has to provide, 1 for the friction with the road, 1 for the friction with the air, 1 for climbing.

$P(t) = \mu(t)mv(t)+C_D v(t)^3 + mgv(t)Tan[\theta(t)]$

where $\mu$ is the friction coefficient as a function of time (depending on the type of terrain), $C_D$ the drag coefficient with the area of myself and other prefactors included already, $g$ acceleration of gravity, and $m$ my mass, and $\theta$ the slope. The total power would then be the integral of that quantity over time. The you still have to include that your muscles are not very efficient (20% or so). So you'll burn 5x the energy... For me, there is too much guessing to give a reliable answer...

Note that the last term cancels out if the track is a closed contour (arrive and depart from the same point) (there is as much ascend as there is descend).

POSTED BY: Sander Huisman
POSTED BY: Henrik Schachner

Indeed that also works quite nice! Nice trick for the TimeZone, I didn't think about it! Thanks for sharing as well!

POSTED BY: Sander Huisman
POSTED BY: Sander Huisman

Here is an Import function I made for my GPX files that computes various quantities:

ClearAll[ImportGarminGPX,DuplicatePositions]
DuplicatePositions[x_List]:=Module[{nums},
    nums=Select[Tally[x],Last[#]>1&][[All,1]];
    Flatten[Rest/@(Position[x,#]&/@nums)]
]
ImportGarminGPX[fn_String,ignorepts:{n_Integer,m_Integer}:{0,0},timezone_:2]:=Module[{data,hr,trail,rawtrail,elevation,elevationunits,abstime,time,date,dups,distances,distance,speed,slen,smoothspeed,avgspeeds,avgspeed,laf,lof,trailfunction,elevationgain,maxhr,hb,avghr,runningmin,runningmax,biggestclimb,biggestdrop},
data=Import[fn,"XML"];

data=Cases[data,XMLElement["trkpt",{"lat"->lat_,"lon"->lon_},other_]:>{ToExpression/@{lat,lon},other},\[Infinity]];
data=Drop[Drop[data,n],-m];
trail=rawtrail=data[[All,1]];
trail=GeoPosition/@trail;

data=data[[All,2]];

abstime=time=Map[FirstCase[#,XMLElement["time",{},{time_}]:>AbsoluteTime[time]+timezone 3600,Missing[],\[Infinity]]&,data];
date=DateObject[First[time]];
time-=First[time];

dups=List/@DuplicatePositions[time]; (* delete duplicate times *)
If[dups=!={},
    Print["duplicate time detected removing indices:",dups];
    time=Delete[time,dups];
    abstime=Delete[abstime,dups];
    trail=Delete[trail,dups];
    rawtrail=Delete[rawtrail,dups];
    data=Delete[data,dups];
];

elevationunits=elevation=Map[FirstCase[#,XMLElement["ele",{},{ele_}]:>ToExpression[ele],Missing[],\[Infinity]]&,data];
elevationunits=Quantity[elevation,"Meters"];

runningmin=FoldList[Min,First[elevation],elevation];
runningmax=Reverse[FoldList[Max,First[Reverse[elevation]],Reverse[elevation]]];
biggestclimb=Max[runningmax-runningmin];

runningmin=Reverse[FoldList[Min,First[Reverse[elevation]],Reverse[elevation]]];
runningmax=FoldList[Max,First[elevation],elevation];
biggestdrop=Max[runningmax-runningmin];

hr=Map[FirstCase[#,XMLElement[{"http://www.garmin.com/xmlschemas/TrackPointExtension/v1","hr"},{},{hr_}]:>ToExpression[hr],Missing[],\[Infinity]]&,data];

distances=BlockMap[GeoDistance@@#&,trail,2,1];
distances=Prepend[QuantityMagnitude[distances,"Meters"],0.0];
distance=Accumulate[distances];

speed=Differences[distance]/Differences[time];
speed=Join[{speed[[1]]},MovingAverage[speed,2],{speed[[-1]]}];
slen=3;
smoothspeed=Join[Accumulate[speed[[;;slen]]]/Range[slen],MovingAverage[speed,2slen+1],Reverse[Accumulate[Reverse[speed[[-slen;;]]]]/Range[slen]]];
avgspeeds=Prepend[Rest[distance]/Rest[time],0];
avgspeed=Last[avgspeeds];


laf=Interpolation[{time,rawtrail[[All,1]]}\[Transpose],InterpolationOrder->1];
lof=Interpolation[{time,rawtrail[[All,2]]}\[Transpose],InterpolationOrder->1];
trailfunction=Function[t,GeoPosition[{laf[t],lof[t]}]];
elevationgain=Total[Select[Differences[elevation],Positive]];

If[Length[DeleteMissing[hr]]>0,
    maxhr=Max[DeleteMissing[hr]];
    hb=Interpolation[DeleteMissing[{time,hr/60.0}\[Transpose],1,2],InterpolationOrder->1];
    hb=Integrate[hb[t],{t,Min[time],Max[time]}];
    avghr=60hb/(Max[time]-Min[time]);
,
    maxhr=Missing[];
    hb=Missing[];
    avghr=Missing[];
];
<|
"AbsoluteTime"->abstime,
"AverageHeartRate"->Round[avghr,0.1],
"AverageSpeed"->avgspeed,
"AverageSpeeds"->avgspeeds,
"BiggestClimb"->biggestclimb,
"BiggestDrop"->biggestdrop,
"Date"->date,
"DateString"->DateString[date,{"Day"," ","MonthName"," ","Year"}],
"Distance"->distance,
"DistanceFunction"->Interpolation[{time,distance}\[Transpose],InterpolationOrder->1],
"Distances"->distances,
"Elevation"->elevation,
"ElevationGain"->elevationgain,
"ElevationUnits"->elevationunits,
"Heartbeats"->hb,
"HeartRate"->hr,
"InverseDistanceFunction"->Interpolation[DeleteDuplicatesBy[{distance,time}\[Transpose],First],InterpolationOrder->1],
"MaximumHeartRate"->maxhr,
"MaxTime"->Max[time],
"RawTrail"->rawtrail,
"SmoothSpeed"->smoothspeed,
"Speed"->speed,
"SpeedFunction"->Interpolation[{time,speed}\[Transpose],InterpolationOrder->1],
"Speedkmh"->3.6 speed,
"Time"->time,
"TotalDistance"->Last[distance],
"Trail"->trail,
"TrailFunction"->trailfunction
|>
]

Here it also calculates various other properties:

AbsoluteTime
AverageHeartRate
AverageSpeed
AverageSpeeds
BiggestClimb
BiggestDrop
Date
DateString
Distance
DistanceFunction
Distances
Elevation
ElevationGain
ElevationUnits
Heartbeats
HeartRate
InverseDistanceFunction
MaximumHeartRate
MaxTime
RawTrail
SmoothSpeed
Speed
SpeedFunction
Speedkmh
Time
TotalDistance
Trail
TrailFunction

Note that the GPX format has a lot of variations and extensions and I can't guarantee it works on your particular GPX file. I have several GPX files from different devices or applications (from e.g. a smart phone), and they are all structurally a little bit different. I tried using FirstCase[....Missing[]..] everywhere, and I built in some safe-guards but it not completely hooligan-proof! You might have to change the code slightly to adapt it to your files.

POSTED BY: Sander Huisman
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract