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 glob
import sys
import gpx_parser
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy
import os
import gpxpy
import gpxpy.gpx
from geopy import distance
from geopy import Point
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 __init__(self, raw_track):
for segment in raw_track.segments:
seg = Segment()
for i in range(1, len(segment.points)):
if self.start_time is None:
self.start_time = segment.points[i - 1].time
if seg.start_time is None:
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
try:
if self.duration is None:
self.duration = seg.end_time - seg.start_time
else:
self.duration += seg.end_time - seg.start_time
except Exception:
# TODO: Add logging mechanism.
pass
self.end_time = seg.end_time
self.distance += seg.distance
self.avg_speed = self.distance / self.duration.total_seconds() * 3.6
def plot_bar_chart(labels, ticklabels, values, title, xlabel, ylabel, filename, xtick_rotation=0):
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)
class Tracks(object):
__distance = dict()
__duration = dict()
__avg_speed = dict()
__tracks = list()
__files = list()
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
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:
short_ticklabels = list()
for i in range(0, len(values[key])):
short_ticklabels.append(ticklabels[i])
plt.plot(short_ticklabels, values[key], label=key)
x_base = numpy.arange(len(ticklabels))
plt.xticks(x_base, ticklabels, rotation=xtick_rotation)
plt.legend()
plt.savefig(filename)
plt.close('all')
class Gpx2Html(object):
def __init__(self, infolder, outfolder, logger):
def __init__(self, logger):
self.logger = logger
self.infolder = infolder
self.outfolder = os.path.abspath(outfolder)
if not os.path.exists(self.outfolder):
os.makedirs(self.outfolder)
def add(self, filename):
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)
self.update()
if trk_year not in self.__distance:
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):
infiles = glob.glob(os.path.join(self.infolder, '*.gpx'))
for filename in infiles:
self.tracks.add(filename)
if trk_year not in self.__duration:
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}
self.__duration[trk_year][trk_month] += track.duration.total_seconds()
self.logger.info("Begin update of png's/html...")
distances = list()
avg_speeds = list()
distances_dict = dict()
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))
if trk_year not in self.__avg_speed:
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}
self.__avg_speed[trk_year][trk_month] = self.__distance[trk_year][trk_month] / (self.__duration[trk_year][trk_month] / 3600)
self.logger.info("Adding done.")
plot_bar_chart(self.tracks.years(), MONTH_LABELS, distances,
'Distance', 'Month', 'km',
os.path.join(self.outfolder, 'distance.png'))
def years(self):
ret = None
try:
ret = sorted(self.__distance.keys())
except Exception:
pass
return ret
plot_bar_chart(self.tracks.years(), MONTH_LABELS, avg_speeds,
'Average Speed', 'Month', 'km/h',
os.path.join(self.outfolder, 'avg_spd.png'))
def distances(self, year):
ret = 0
try:
ret = self.__distance[year].values()
except Exception:
pass
return ret
# Accumulated distance:
accumulated_distances = dict()
for year in distances_dict.keys():
accumulated_distance = list()
accumulated_distance.append(0)
for i in range(0, len(distances_dict[year])):
accumulated_distance.append(accumulated_distance[i] + distances_dict[year][i])
accumulated_distances[year] = accumulated_distance
def avg_speeds(self, year):
ret = None
try:
ret = self.__avg_speed[year].values()
except Exception:
pass
return ret
current_year = datetime.datetime.today().year
current_month = datetime.datetime.today().month
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()
def tracks(self, start_date, end_date):
tracks = list()
dates = pd.date_range(start_date.date(), end_date.date())
for date in dates:
for track in last_n_tracks:
if date.date() == track.start_time.date():
get = 0
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')
for track in self.__tracks:
if track.start_time.date() in dates:
tracks.append(track)
return tracks