Last active
March 2, 2020 22:10
-
-
Save mstevens83/2642601bfee1d37e78b17328fa64a332 to your computer and use it in GitHub Desktop.
Generates report about track cycling data posted at MyLaps/Sporthive
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### (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