bicycle-statistics/gpx2html/__init__.py
2020-08-11 11:58:51 +02:00

314 lines
11 KiB
Python
Executable File

#!/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 pandas as pd
import collections
from gpx_parser import Tracks
import pytz
import calendar
MONTH_LABELS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
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)
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.
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)
step = len(values[min(values.keys())]) / len(ticklabels)
ax1.set_xticks(numpy.arange(1, len(ticklabels) * step, step))
ax1.set_xticklabels(ticklabels, rotation=xtick_rotation)
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
for key in values.keys():
if key == 'blind':
plt.plot(values[key], linestyle='None')
else:
plt.plot(values[key], label=key)
plt.legend()
plt.savefig(filename)
plt.close('all')
def generate_date_list(start_date, end_date):
r = (end_date + datetime.timedelta(days=1) - start_date).days
return[start_date + datetime.timedelta(days=i) for i in range(r)]
def get_distance_by_date(tracks, date):
'''Return accumulated distances [km] by date.
Args:
tracks (list(Track)): List of tracks to be evaluated.
date (datetine.datetime): Date to be evaluated.
Return (float): Accumulated distance [km] for date.
'''
distance = 0
for track in tracks:
if track.start_time.year == date.year and \
track.start_time.month == date.month and \
track.start_time.day == date.day:
distance += track.distance
if distance > 0:
distance /= 1000
return distance
class Gpx2Html(object):
def __init__(self, infolder, outfolder, logger):
self.logger = logger
self.infolder = infolder
self.outfolder = os.path.abspath(outfolder)
if not os.path.exists(self.outfolder):
os.makedirs(self.outfolder)
self.tracks = Tracks(logger)
self.update()
def update(self):
for root, _, files in os.walk(self.infolder):
for name in files:
if name.endswith(".gpx"):
self.tracks.add(os.path.join(root, name))
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))
plot_bar_chart(self.tracks.years(), MONTH_LABELS, distances,
'Distance', 'Month', 'km',
os.path.join(self.outfolder, 'distance.png'))
plot_bar_chart(self.tracks.years(), MONTH_LABELS, avg_speeds,
'Average Speed', 'Month', 'km/h',
os.path.join(self.outfolder, 'avg_spd.png'))
now = datetime.datetime.now()
# Accumulated distance:
accumulated_distance = dict()
for year in self.tracks.years():
acc_year_dist = list()
start_date = datetime.datetime(year, 1, 1)
end_date = datetime.datetime(year, 12, 31)
if year == now.year:
end_date = now
tracks = self.tracks.tracks(start_date, end_date)
for date in generate_date_list(start_date, end_date):
date_distance = get_distance_by_date(tracks, date)
try:
acc_year_dist.append(date_distance + acc_year_dist[-1])
except IndexError:
acc_year_dist.append(date_distance)
accumulated_distance[year] = acc_year_dist
plot_line_chart(accumulated_distance, MONTH_LABELS,
"Accumulated Distance", 'Month', 'km/year',
os.path.join(self.outfolder, 'acc_dist.png'))
# Expected year distance:
start_date =datetime.datetime(now.year, 1, 1)
end_date = now
tracks = self.tracks.tracks(start_date, end_date)
acc_year_dist = list()
exp_year_dist = list()
blind_line = list()
for date in generate_date_list(start_date, end_date):
date_distance = get_distance_by_date(tracks, date)
blind_line.append(0)
try:
acc_year_dist.append(date_distance + acc_year_dist[-1])
except IndexError:
acc_year_dist.append(date_distance)
expexted_distance = acc_year_dist[-1] / date.timetuple().tm_yday * 365
exp_year_dist.append(expexted_distance)
today = datetime.date.today()
last_month_day = today.replace(day=calendar.monthrange(today.year, today.month)[1])
diff = last_month_day - today
for i in range(diff.days):
blind_line.append(0)
xyz = dict()
xyz['driven'] = acc_year_dist
xyz['expected @ year end'] = exp_year_dist
xyz['blind'] = blind_line
plot_line_chart(xyz, MONTH_LABELS[:now.month], "Distance", 'Month',
'km/year', os.path.join(self.outfolder, 'exp_dist.png'))
end_date = datetime.datetime.today()
start_date = end_date - datetime.timedelta(days=13) # last 14 days
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())
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('exp_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')