Skip to content

Instantly share code, notes, and snippets.

@mstevens83
Last active March 2, 2020 22:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mstevens83/2642601bfee1d37e78b17328fa64a332 to your computer and use it in GitHub Desktop.
Save mstevens83/2642601bfee1d37e78b17328fa64a332 to your computer and use it in GitHub Desktop.
Generates report about track cycling data posted at MyLaps/Sporthive
### (C) Matthias Stevens, 2020.
import requests #install using pip
import csv
import statistics
from operator import attrgetter
from datetime import datetime, timedelta
baseUrl = "https://sporthive.com/Practice/"
def getActivityPageUrl(activityId):
return baseUrl + "Activity/" + str(activityId)
def getActivityCsvUrl(activityId):
return baseUrl + "GetCsv?activityId=" + str(activityId)
def parseDateTimeStrToDT(dateTimeStr):
return datetime.strptime(dateTimeStr, '%d-%m-%Y %H:%M:%S')
def parseDurationStrToS(timeStr):
parts = timeStr.split(':')
return (int(parts[0]) * 60.0 * 60.0) + (int(parts[1]) * 60.0) + float(parts[2])
def formatDuration(durationS):
return datetime.utcfromtimestamp(durationS).strftime("%H:%M:%S.%f")[:-3]
class Track:
def __init__(self, name, location, lengthM=250.0):
self.name = name
self.location = location
self.length = float(lengthM)
eddy = Track("Wielercentrum Eddy Merckx", "Gent", 250.0)
kuipke = Track("Kuipke", "Gent", 166.66)
class Lap:
def __init__(self, durationS, track=eddy):
self.track = track
self.duration = durationS
def setIndex(self, index):
self.index = index
def setQTimes(self, qTimesS):
self.qTimes = qTimesS
#if(round(sum(qS for qS in self.qTimes), 3) != self.duration):
# print("Mismatch!:" + str(round(sum(qS for qS in self.qTimes), 3)) + " != " + str(self.duration))
def getSpeedMS(self):
return self.track.length / self.duration
def getSpeedKMH(self):
return self.getSpeedMS() * 3.6
def getMaxSpeedKMH(self):
if(hasattr(self, 'qTimes')):
return max(self.track.length / 4.0 / qS for qS in self.qTimes) * 3.6
else:
return self.getSpeedKMH()
def printStats(self, prefix=""):
print(prefix + str(round(self.duration, 3)) + "s (" + str(round(self.getSpeedKMH(), 2)) + "km/h" + (", #" + str(self.index) if hasattr(self, 'index') else "") + ")")
class Session:
def __init__(self, bikeCount, riderCount, track=eddy):
super().__init__()
self.bikeCount = bikeCount
self.riderCount = riderCount
self.track = track
self.laps = []
def setStartTime(self, startTimeDT):
self.startTime = startTimeDT
def getEndTime(self):
return self.startTime + timedelta(seconds=self.getDuration())
def addLap(self, durationS, qTimesS):
lap = Lap(durationS, self.track)
self.laps.append(lap)
lap.setIndex(len(self.laps))
lap.setQTimes(qTimesS)
def getDuration(self):
return sum(l.duration for l in self.laps)
def getDistance(self):
return self.track.length * len(self.laps)
def getFastestLap(self):
return min(self.laps, key=attrgetter('duration'))
def getMaxSpeedKMH(self):
return max(l.getMaxSpeedKMH() for l in self.laps)
def printStats(self):
print("Session " + str(self.riderCount))
print(" - Start time: " + self.startTime.strftime("%H:%M:%S"))
print(" - Duration: " + formatDuration(self.getDuration()) + " (moving)")
print(" - Laps: " + str(len(self.laps)) + " (" + str(self.getDistance() / 1000.0) + "km)")
avgLap = Lap(statistics.mean((l.duration for l in self.laps)))
avgLap.printStats(" - Average lap: ")
self.getFastestLap().printStats(" - Fastest lap: ")
print(" - Max quarter lap speed: " + str(round(self.getMaxSpeedKMH(), 2)) + "km/h")
def processActivityReport(activityId, riderSessions=None, track=eddy):
bikeSessionCount = 0
sessions = []
# Download, parse & process:
with requests.Session() as reqSes:
response = reqSes.get(getActivityCsvUrl(activityId))
csvFile = response.content.decode('utf-16le')
reportReader = csv.reader(csvFile.splitlines(), delimiter=',', quotechar='"')
for row in reportReader:
if(row[0] == "Transponder"): #Header detected
print("CSV downloaded & decoded, starting to parse...\n")
elif(row[0] == ""):
bikeSessionCount += 1
if (riderSessions is None or bikeSessionCount in riderSessions):
sessions.append(Session(bikeSessionCount, len(sessions) + 1, track))
#print("new session")
else:
#print("lap")
if (riderSessions is None or bikeSessionCount in riderSessions):
if(len(sessions[-1].laps) == 0):
sessions[-1].setStartTime(parseDateTimeStrToDT(row[1] + " " + row[2]))
sessions[-1].addLap(parseDurationStrToS(row[5]), [parseDurationStrToS(qStr) for qStr in row[6:10]])
if (riderSessions is None and len(sessions) > 0):
riderSessions = [i+1 for i in range(len(sessions))]
# Generate report:
totalDuration = sum(s.getDuration() for s in sessions)
totalLaps = sum(len(s.laps) for s in sessions)
print("At " + track.name + ", " + track.location + " (" + str(round(track.length)) + "m track).")
#print("")
print("――Overall stats――")
print(" * Start time: " + sessions[0].startTime.strftime("%H:%M:%S"))
print(" * Total distance: " + str(totalLaps * track.length / 1000.0) + "km (" + str(totalLaps) + " laps)")
print(" * Total duration: " + formatDuration(totalDuration) + " (moving)")
print(" * Average speed: " + str(round(totalLaps * track.length / totalDuration * 3.6, 2)) + "km/h")
print(" * Fastest lap: " + str(round(min(l.duration for l in (s.getFastestLap() for s in sessions)), 3)) + "s")
print(" * Max quarter lap speed: " + str(round(max(s.getMaxSpeedKMH() for s in sessions), 2)) + "km/h")
#print("")
print("――Details――")
for i, session in enumerate(sessions):
if(i > 0):
print("Break")
print(" - Start time: " + sessions[i-1].getEndTime().strftime("%H:%M:%S"))
print(" - Duration: " + formatDuration((session.startTime - sessions[i-1].getEndTime()).total_seconds()))
session.printStats()
#print("")
print("――Data source――")
print(getActivityPageUrl(activityId) + " (Bike session" + ("s " + str(riderSessions[0]) + "-" + str(riderSessions[-1]) if len(riderSessions) > 1 else " " + str(riderSessions[0])) + ")")
print("")
### Test:
#processActivityReport(691996884, riderSessions = [4], track=eddy)
#processActivityReport(691996884, riderSessions = [5, 6], track=eddy)
#processActivityReport(691996884, riderSessions = [4, 5, 6], track=eddy)
#processActivityReport(692084939, riderSessions = [8, 9, 10, 11])
processActivityReport(692127581)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment