#!/usr/bin/env python import argparse import datetime import sys import gpx_parser import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import numpy import os import pandas as pd import collections 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) 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) class Gpx2Html(object): MONTH_LABELS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] def __init__(self, infolder, outfolder): self.infolder = infolder self.outfolder = os.path.abspath(outfolder) self.distance_diag_file = 'distance.png' self.distance_diag_abs = os.path.join(self.outfolder, self.distance_diag_file) self.avg_spd_diag_file = 'avg_spd.png' self.avg_spd_diag_abs = os.path.join(self.outfolder, self.avg_spd_diag_file) self.last_n_days_diag_file = None self.html_file = os.path.join(self.outfolder, 'index.html') self.years_distance = list() self.years_avg_spd = list() self.years = list() def plot_distance_diagram(self): plot_bar_chart(self.years, self.MONTH_LABELS, self.years_distance, 'Distance', 'Month', 'km', self.distance_diag_abs) def plot_avg_spd_diagram(self): plot_bar_chart(self.years, self.MONTH_LABELS, self.years_avg_spd, 'Average Speed', 'Month', 'km/h', self.avg_spd_diag_abs) def plot_last_n_days(self, day_count): plot_bar_chart(["Distance", "Average speed"], self.date_distance.keys(), [self.date_distance.values(), self.date_avg_spd.values()], 'Last {} days'.format(day_count), 'Date', 'km, km/h', os.path.join(self.outfolder, self.last_n_days_diag_file), 90) def write_html_file(self): with open(self.html_file, 'w') as handle: handle.write('\n') handle.write('\n') handle.write('\n') handle.write('\n') handle.write(' Bicycle \n') handle.write('\n') handle.write('\n') handle.write('
\n') handle.write('

Bicycle

\n') handle.write('

\n') handle.write('\n') handle.write('\n') for year in self.years: handle.write('\n'.format(year)) handle.write('\n') handle.write('\n') for i in range(len(self.years_distance)): handle.write('\n'.format(round(sum(self.years_distance[i]), 1))) handle.write('\n') handle.write('
{}
{} km
\n') handle.write('

\n') handle.write('

\n') handle.write('Distance\n'.format(self.distance_diag_file)) handle.write('Distance\n'.format(self.avg_spd_diag_file)) handle.write('Distance\n'.format(self.last_n_days_diag_file)) handle.write('

\n') handle.write('\n') handle.write('
\n') handle.write('\n') def process(self): self.years[:] = [] self.years_avg_spd[:] = [] self.years_distance[:] = [] self.tracks = gpx_parser.Tracks(self.infolder) for year in range(2017, datetime.datetime.now().year + 1): month_avg_spd = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0} month_distance = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0} month_duration = {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 month in range(1, 13): if month == 12: max_day = (datetime.date(year + 1, 1, 1) - datetime.timedelta(days=1)).day else: max_day = (datetime.date(year, month + 1, 1) - datetime.timedelta(days=1)).day for day in range(1, max_day + 1): date_tracks = self.tracks.get(year, month, day) for track in date_tracks: month_distance[month] += (track.distance / 1000) # km month_duration[month] += track.duration.total_seconds() / 3600 # h for i in range(1, 13): if month_duration[i] > 0: month_avg_spd[i] = month_distance[i] / month_duration[i] self.years_distance.append(month_distance.values()) self.years_avg_spd.append(month_avg_spd.values()) self.years.append(str(year)) self.plot_distance_diagram() self.plot_avg_spd_diagram() # last n days n = 14 end_date = datetime.date.today() start_date = end_date - datetime.timedelta(days=n) dates = pd.date_range(start_date, end_date) self.date_distance = dict() date_duration = dict() self.date_avg_spd = dict() for date in dates: date_str = "{0:04d}-{1:02d}-{2:02d}".format(date.year, date.month, date.day) date_tracks = self.tracks.get(date.year, date.month, date.day) for track in date_tracks: try: current_dist = self.date_distance[date_str] current_duration = date_duration[date_str] except KeyError: current_dist = 0 current_duration = 0 current_dist += track.distance / 1000 self.date_distance.update({date_str:current_dist}) current_duration += track.duration.total_seconds() / 3600 date_duration.update({date_str:current_duration}) # check for empty dates try: current_dist = self.date_distance[date_str] current_duration = date_duration[date_str] except KeyError: self.date_distance.update({date_str:0}) date_duration.update({date_str:0}) date_duration = collections.OrderedDict(sorted(date_duration.items())) for key, value in date_duration.items(): if value == 0: self.date_avg_spd.update({key:0}) else: avg_spd = self.date_distance[key] / value self.date_avg_spd.update({key:avg_spd}) self.date_avg_spd = collections.OrderedDict(sorted(self.date_avg_spd.items())) self.date_distance = collections.OrderedDict(sorted(self.date_distance.items())) self.last_n_days_diag_file = "distance_last_{}_days.png".format(n) self.plot_last_n_days(n) self.write_html_file()