From 492c67bec3b8e8f523bf489b2e12f92019b20b41 Mon Sep 17 00:00:00 2001
From: Thomas Klaehn
Date: Thu, 26 Jul 2018 09:53:44 +0200
Subject: [PATCH] bicycle-statistics: Restructure
Signed-off-by: Thomas Klaehn
---
.gitignore | 2 +-
bicycle-stat | 3 +
bicycle-stat.service | 10 ++
bicycle_statistics/__init__.py | 0
bicycle_statistics/__main__.py | 18 ++++
gpx2html | 191 ---------------------------------
gpx2html/__init__.py | 184 +++++++++++++++++++++++++++++++
setup.py | 18 +++-
8 files changed, 230 insertions(+), 196 deletions(-)
create mode 100755 bicycle-stat
create mode 100644 bicycle-stat.service
create mode 100644 bicycle_statistics/__init__.py
create mode 100644 bicycle_statistics/__main__.py
delete mode 100755 gpx2html
create mode 100755 gpx2html/__init__.py
diff --git a/.gitignore b/.gitignore
index a51e862..e68c680 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
data/
.vscode/settings.json
-gpx_parser/__init__.pyc
+*.pyc
diff --git a/bicycle-stat b/bicycle-stat
new file mode 100755
index 0000000..f8b05fe
--- /dev/null
+++ b/bicycle-stat
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+python -m bicycle_statistics $@
diff --git a/bicycle-stat.service b/bicycle-stat.service
new file mode 100644
index 0000000..7bae109
--- /dev/null
+++ b/bicycle-stat.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Bicucle statistics
+After=multi-user.target
+
+[Service]
+Type=idle
+ExecStart=/usr/local/bin/bicycle-stat
+
+[Install]
+WantedBy=multi-user.target
diff --git a/bicycle_statistics/__init__.py b/bicycle_statistics/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bicycle_statistics/__main__.py b/bicycle_statistics/__main__.py
new file mode 100644
index 0000000..44b5ca5
--- /dev/null
+++ b/bicycle_statistics/__main__.py
@@ -0,0 +1,18 @@
+import argparse
+import sys
+from gpx2html import Gpx2Html
+
+def parse_args():
+ '''Shell argument parser.'''
+ parser = argparse.ArgumentParser()
+ parser.add_argument('infolder', help='Specify the in folder.')
+ parser.add_argument('outfolder', help='Specify the out folder.')
+ return parser.parse_args()
+
+def main():
+ args = parse_args()
+ gpx2html = Gpx2Html(args.infolder, args.outfolder)
+ gpx2html.process()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/gpx2html b/gpx2html
deleted file mode 100755
index 700bcb4..0000000
--- a/gpx2html
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/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
-
-MONTH_LABELS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
-
-def parse_args():
- '''Shell argument parser.'''
- parser = argparse.ArgumentParser()
- parser.add_argument('infolder', help='Specify the in folder.')
- parser.add_argument('outfolder', help='Specify the out folder.')
- return parser.parse_args()
-
-
-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)
-
-
-def main():
- args = parse_args()
- tracks = gpx_parser.Tracks(args.infolder)
- years_distance = list()
- years_avg_spd = list()
- years = list()
- 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 = 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]
- years_distance.append(month_distance.values())
- years_avg_spd.append(month_avg_spd.values())
- years.append(str(year))
-
- out_folder = os.path.abspath(args.outfolder)
-
- dst_file_name = 'distance.png'
- plot_bar_chart(years, MONTH_LABELS, years_distance, 'Distance', 'Month', 'km', os.path.join(out_folder, dst_file_name))
-
- avg_file_name = 'avg_spd.png'
- plot_bar_chart(years, MONTH_LABELS, years_avg_spd, 'Average Speed', 'Month', 'km/h', os.path.join(out_folder, avg_file_name))
-
- # 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)
-
- date_distance = dict()
- date_duration = dict()
- 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 = tracks.get(date.year, date.month, date.day)
- for track in date_tracks:
- try:
- current_dist = date_distance[date_str]
- current_duration = date_duration[date_str]
- except KeyError:
- current_dist = 0
- current_duration = 0
- current_dist += track.distance / 1000
- 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 = date_distance[date_str]
- current_duration = date_duration[date_str]
- except KeyError:
- 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:
- date_avg_spd.update({key:0})
- else:
- avg_spd = date_distance[key] / value
- date_avg_spd.update({key:avg_spd})
-
- date_avg_spd = collections.OrderedDict(sorted(date_avg_spd.items()))
- date_distance = collections.OrderedDict(sorted(date_distance.items()))
-
- dst_n_file_name = "distance_last_{}_days.png".format(n)
- plot_bar_chart(["Distance", "Average speed"],
- date_distance.keys(),
- [date_distance.values(), date_avg_spd.values()],
- 'Last {} days'.format(n),
- 'Date',
- 'km, km/h',
- os.path.join(out_folder, dst_n_file_name),
- 90)
-
- html_file = os.path.join(out_folder, 'index.html')
- with open(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 years:
- handle.write('{} | \n'.format(year))
- handle.write('
\n')
-
- handle.write('\n')
- for i in range(len(years_distance)):
- handle.write('{} km | \n'.format(round(sum(years_distance[i]), 1)))
- handle.write('
\n')
- handle.write('
\n')
-
- handle.write('
\n')
-
- handle.write('\n')
- handle.write('\n'.format(dst_file_name))
- handle.write('\n'.format(avg_file_name))
- handle.write('\n'.format(dst_n_file_name))
- handle.write('
\n')
-
- handle.write('\n')
- handle.write('\n')
- handle.write('\n')
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/gpx2html/__init__.py b/gpx2html/__init__.py
new file mode 100755
index 0000000..a0524f2
--- /dev/null
+++ b/gpx2html/__init__.py
@@ -0,0 +1,184 @@
+#!/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 = outfolder
+
+
+ def process(self):
+ self.tracks = gpx_parser.Tracks(self.infolder)
+ years_distance = list()
+ years_avg_spd = list()
+ years = list()
+ 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]
+ years_distance.append(month_distance.values())
+ years_avg_spd.append(month_avg_spd.values())
+ years.append(str(year))
+
+ out_folder = os.path.abspath(self.outfolder)
+
+ dst_file_name = 'distance.png'
+ plot_bar_chart(years, self.MONTH_LABELS, years_distance, 'Distance', 'Month', 'km', os.path.join(out_folder, dst_file_name))
+
+ avg_file_name = 'avg_spd.png'
+ plot_bar_chart(years, self.MONTH_LABELS, years_avg_spd, 'Average Speed', 'Month', 'km/h', os.path.join(out_folder, avg_file_name))
+
+ # 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)
+
+ date_distance = dict()
+ date_duration = dict()
+ 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 = date_distance[date_str]
+ current_duration = date_duration[date_str]
+ except KeyError:
+ current_dist = 0
+ current_duration = 0
+ current_dist += track.distance / 1000
+ 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 = date_distance[date_str]
+ current_duration = date_duration[date_str]
+ except KeyError:
+ 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:
+ date_avg_spd.update({key:0})
+ else:
+ avg_spd = date_distance[key] / value
+ date_avg_spd.update({key:avg_spd})
+
+ date_avg_spd = collections.OrderedDict(sorted(date_avg_spd.items()))
+ date_distance = collections.OrderedDict(sorted(date_distance.items()))
+
+ dst_n_file_name = "distance_last_{}_days.png".format(n)
+ plot_bar_chart(["Distance", "Average speed"],
+ date_distance.keys(),
+ [date_distance.values(), date_avg_spd.values()],
+ 'Last {} days'.format(n),
+ 'Date',
+ 'km, km/h',
+ os.path.join(out_folder, dst_n_file_name),
+ 90)
+
+ html_file = os.path.join(out_folder, 'index.html')
+ with open(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 years:
+ handle.write('{} | \n'.format(year))
+ handle.write('
\n')
+
+ handle.write('\n')
+ for i in range(len(years_distance)):
+ handle.write('{} km | \n'.format(round(sum(years_distance[i]), 1)))
+ handle.write('
\n')
+ handle.write('
\n')
+
+ handle.write('\n')
+
+ handle.write('\n')
+ handle.write('\n'.format(dst_file_name))
+ handle.write('\n'.format(avg_file_name))
+ handle.write('\n'.format(dst_n_file_name))
+ handle.write('
\n')
+
+ handle.write('\n')
+ handle.write('\n')
+ handle.write('\n')
diff --git a/setup.py b/setup.py
index ecd4b9d..fb543b7 100755
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,22 @@
#!/usr/bin/env python
from distutils.core import setup
+import sys
+import os
+import shutil
+import stat
-NAME = 'gpx_parser'
-VERSION = '0.2.1'
+NAME = 'bicycle-statistics'
+VERSION = '0.1.0'
AUTHOR = 'Thomas Klaehn'
EMAIL = 'tkl@blackfinn.de'
-PACKAGES = [NAME]
-SCRIPTS = ['example-gpx-parser', 'gpx2html']
+PACKAGES = [NAME, 'gpx_parser', 'gpx2html']
+SCRIPTS = ['example-gpx-parser', 'bicycle-stat']
+
+DAEMON_START_SCRIPT = os.path.join("/lib/systemd/system", "bicycle-stat.service")
+
+if sys.argv[1] == 'install':
+ shutil.copyfile("bicycle-stat.service", DAEMON_START_SCRIPT)
+ os.chmod(DAEMON_START_SCRIPT, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
setup(name=NAME, version=VERSION, author=AUTHOR, author_email=EMAIL, packages=PACKAGES, scripts=SCRIPTS)