Compare commits

..

1 Commits

Author SHA1 Message Date
Thomas Klaehn
7d7ca2b48f bicycle-statistics: add accumulated distances view 2019-06-16 20:49:12 +02:00

View File

@ -1,255 +1,115 @@
#!/usr/bin/env python
import argparse
import datetime import datetime
import glob import glob
import sys
import gpx_parser
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy
import os import os
import gpxpy
import gpxpy.gpx
from geopy import distance
from geopy import Point
import pandas as pd import pandas as pd
import collections
from gpx_parser import Tracks
MONTH_LABELS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] class Segment(object):
start_time = None
end_time = None
distance = 0.0 # [m]
class Track(object):
start_time = None
end_time = None
distance = 0.0 # [m]
avg_speed = 0.0 # [km/h]
duration = None
def plot_bar_chart(labels, ticklabels, values, title, xlabel, ylabel, filename, xtick_rotation=0): def __init__(self, raw_track):
fig = plt.figure() for segment in raw_track.segments:
ax1 = fig.add_subplot(111) seg = Segment()
ax1.grid(zorder=0) for i in range(1, len(segment.points)):
ax1.spines["top"].set_visible(False) if self.start_time is None:
ax1.spines["bottom"].set_visible(False) self.start_time = segment.points[i - 1].time
ax1.spines["left"].set_visible(False) if seg.start_time is None:
ax1.spines["right"].set_visible(False) seg.start_time = segment.points[i - 1].time
seg.end_time = segment.points[i - 1].time
point1 = Point(str(segment.points[i - 1].latitude) + \
' ' + str(segment.points[i - 1].longitude))
point2 = Point(str(segment.points[i].latitude) + \
' ' + str(segment.points[i].longitude))
seg.distance += distance.distance(point1, point2).meters
plt.title(title) try:
plt.xlabel(xlabel) if self.duration is None:
plt.ylabel(ylabel) self.duration = seg.end_time - seg.start_time
width = 1.0 / len(values) - 0.03
x_base = numpy.arange(len(ticklabels))
x_pos = list()
for i in range(len(values)):
x_pos.append([x + (width / 2) + i * width for x in range(len(x_base))])
plt.bar(x_pos[i], values[i], width=width, label=labels[i], zorder=2)
plt.xticks(x_base, ticklabels, rotation=xtick_rotation)
# Tweak spacing to prevent clipping of tick-labels
plt.subplots_adjust(bottom=0.2)
plt.legend()
plt.savefig(filename)
plt.close('all')
def plot_line_chart(values, ticklabels, title, xlabel, ylabel, filename, xtick_rotation=0):
'''Plot a line chart.
Args:
values (dict): key: line name
value (list): line values
ticklabels (list): Names for the tick labels (must be same length as value list).
title (str): Title of the chart.
'''
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.grid(zorder=0)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["right"].set_visible(False)
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
for key in values.keys():
if len(ticklabels) == len(values[key]):
plt.plot(ticklabels, values[key], label=key)
else: else:
short_ticklabels = list() self.duration += seg.end_time - seg.start_time
for i in range(0, len(values[key])): except Exception:
short_ticklabels.append(ticklabels[i]) # TODO: Add logging mechanism.
plt.plot(short_ticklabels, values[key], label=key) pass
self.end_time = seg.end_time
x_base = numpy.arange(len(ticklabels)) self.distance += seg.distance
plt.xticks(x_base, ticklabels, rotation=xtick_rotation) self.avg_speed = self.distance / self.duration.total_seconds() * 3.6
plt.legend()
plt.savefig(filename)
plt.close('all')
class Gpx2Html(object): class Tracks(object):
def __init__(self, infolder, outfolder, logger): __distance = dict()
__duration = dict()
__avg_speed = dict()
__tracks = list()
__files = list()
def __init__(self, logger):
self.logger = logger self.logger = logger
self.infolder = infolder
self.outfolder = os.path.abspath(outfolder)
if not os.path.exists(self.outfolder): def add(self, filename):
os.makedirs(self.outfolder) if filename not in self.__files:
self.logger.info("Adding file %s.", filename)
with open(filename, 'r') as f:
self.__files.append(filename)
gpx = gpxpy.parse(f)
for raw in gpx.tracks:
track = Track(raw)
self.__tracks.append(track)
trk_month = track.start_time.month
trk_year = track.start_time.year
self.tracks = Tracks(logger) if trk_year not in self.__distance:
self.update() self.__distance[trk_year] = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0}
self.__distance[trk_year][trk_month] += track.distance / 1000
def update(self): if trk_year not in self.__duration:
infiles = glob.glob(os.path.join(self.infolder, '*.gpx')) self.__duration[trk_year] = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0}
for filename in infiles: self.__duration[trk_year][trk_month] += track.duration.total_seconds()
self.tracks.add(filename)
self.logger.info("Begin update of png's/html...") if trk_year not in self.__avg_speed:
distances = list() self.__avg_speed[trk_year] = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0}
avg_speeds = list() self.__avg_speed[trk_year][trk_month] = self.__distance[trk_year][trk_month] / (self.__duration[trk_year][trk_month] / 3600)
distances_dict = dict() self.logger.info("Adding done.")
for year in self.tracks.years():
distances.append(self.tracks.distances(year))
distances_dict[year] = self.tracks.distances(year)
avg_speeds.append(self.tracks.avg_speeds(year))
self.logger.info("{}: {}".format(year, self.tracks.distances))
plot_bar_chart(self.tracks.years(), MONTH_LABELS, distances, def years(self):
'Distance', 'Month', 'km', ret = None
os.path.join(self.outfolder, 'distance.png')) try:
ret = sorted(self.__distance.keys())
except Exception:
pass
return ret
plot_bar_chart(self.tracks.years(), MONTH_LABELS, avg_speeds, def distances(self, year):
'Average Speed', 'Month', 'km/h', ret = 0
os.path.join(self.outfolder, 'avg_spd.png')) try:
ret = self.__distance[year].values()
except Exception:
pass
return ret
# Accumulated distance: def avg_speeds(self, year):
accumulated_distances = dict() ret = None
for year in distances_dict.keys(): try:
accumulated_distance = list() ret = self.__avg_speed[year].values()
accumulated_distance.append(0) except Exception:
for i in range(0, len(distances_dict[year])): pass
accumulated_distance.append(accumulated_distance[i] + distances_dict[year][i]) return ret
accumulated_distances[year] = accumulated_distance
current_year = datetime.datetime.today().year def tracks(self, start_date, end_date):
current_month = datetime.datetime.today().month tracks = list()
current_year_distance = list()
for i in range(0, current_month):
current_year_distance.append(accumulated_distances[current_year][i])
accumulated_distances[current_year] = current_year_distance
plot_line_chart(accumulated_distances, [""] + MONTH_LABELS,
"accumulated distance", 'Month', 'km',
os.path.join(self.outfolder, 'acc_dist.png'))
end_date = datetime.datetime.today()
start_date = end_date - datetime.timedelta(days=14)
last_n_tracks = self.tracks.tracks(start_date, end_date)
last_n_distances = dict()
last_n_durations = dict()
dates = pd.date_range(start_date.date(), end_date.date()) dates = pd.date_range(start_date.date(), end_date.date())
for date in dates: for track in self.__tracks:
for track in last_n_tracks: if track.start_time.date() in dates:
if date.date() == track.start_time.date(): tracks.append(track)
get = 0 return tracks
try:
get = last_n_distances[date.date()]
except KeyError:
pass
if get == 0:
last_n_distances[date.date()] = track.distance / 1000
else:
last_n_distances[date.date()] += track.distance / 1000
try:
get = last_n_durations[date.date()]
except KeyError:
pass
if get == 0:
last_n_durations[date.date()] = track.duration.total_seconds()
else:
last_n_durations[date.date()] += track.duration.total_seconds()
else:
try:
get = last_n_distances[date.date()]
except KeyError:
last_n_distances[date.date()] = 0
try:
get = last_n_durations[date.date()]
except KeyError:
last_n_durations[date.date()] = 0
last_n_dist = list()
last_n_dur = list()
last_n_avg = list()
last_n_dates = list()
for date in dates:
try:
last_n_dist.append(last_n_distances[date.date()])
except KeyError:
last_n_dist.append(0)
try:
last_n_dur.append(last_n_durations[date.date()])
except KeyError:
last_n_dur.append(0)
date_str = "{0:04d}-{1:02d}-{2:02d}".format(date.year, date.month, date.day)
last_n_dates.append(date_str)
try:
if last_n_durations[date.date()] == 0:
last_n_avg.append(0)
else:
last_n_avg.append(last_n_distances[date.date()] /
(last_n_durations[date.date()] / 3600))
except KeyError:
last_n_avg.append(0)
plot_bar_chart(["Distance", "Average speed"], last_n_dates,
[last_n_dist, last_n_avg],
'Last 14 days', 'Date', 'km, km/h',
os.path.join(self.outfolder, 'last_14_days.png'), 90)
self.__write_html_file()
self.logger.info("End update of png's/html...")
def __write_html_file(self):
with open(os.path.join(self.outfolder, 'index.html'), 'w') as handle:
handle.write('<!DOCTYPE html>\n')
handle.write('<html>\n')
handle.write('<head>\n')
handle.write('<style>\n')
handle.write('table {\n')
handle.write(' border-collapse: separate;\n')
handle.write(' border-spacing: 20px 0;\n')
handle.write('}\n')
handle.write('th {\n')
handle.write(' text-align: left;\n')
handle.write('}\n')
handle.write('</style>\n')
handle.write('<title> Bicycle </title>\n')
handle.write('</head>\n')
handle.write('<body>\n')
handle.write('<center>\n')
handle.write('<h1> Bicycle </h1>\n')
handle.write('<p>\n')
handle.write('<table>\n')
handle.write('<tr>\n')
for year in self.tracks.years():
handle.write('<th>{}</th>\n'.format(year))
handle.write('</tr>\n')
handle.write('<tr>\n')
for year in self.tracks.years():
handle.write('<td>{} km</td>\n'.format(round(sum(self.tracks.distances(year)), 1)))
handle.write('</tr>\n')
handle.write('</table>\n')
handle.write('</p>\n')
handle.write('<p>\n')
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format('distance.png'))
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format('acc_dist.png'))
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format('avg_spd.png'))
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format('last_14_days.png'))
handle.write('</p>\n')
handle.write('</body>\n')
handle.write('<center>\n')
handle.write('</html>\n')