Compare commits
12 Commits
0cf59eb7f1
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
e84ab19187 | ||
|
7f7c65e3fa | ||
|
7a7d6dedf3 | ||
|
320bab0188 | ||
|
5d88a026cd | ||
|
9761814f6f | ||
|
d65eeaf0fd | ||
|
ae4cfa3a6d | ||
|
8a0318b656 | ||
|
e1c68a55b3 | ||
|
2cd36b5e52 | ||
|
ddbb5f159f |
30
.devcontainer/devcontainer.json
Normal file
30
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,30 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/cpp
|
||||
{
|
||||
"name": "bicycle-statistics",
|
||||
"image": "bic-dev:latest",
|
||||
|
||||
// --network=host is necessary, since default docker network does not transport mdns|dns-sd, since they route through docker0
|
||||
// "runArgs": [ "--network=host", "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux.shell":"/bin/sh"
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
// "extensions": [
|
||||
// "ms-vscode.cpptools"
|
||||
// ],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "gcc -v",
|
||||
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
// "remoteUser": "vscode",
|
||||
|
||||
// "postStartCommand" : "sudo rm /var/run/avahi-daemon/pid; sudo avahi-daemon --no-drop-root --no-chroot -D || true",
|
||||
}
|
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
@@ -1,7 +1,4 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
@@ -13,7 +10,10 @@
|
||||
"args": [
|
||||
"/home/tkl/Nextcloud/Bicycle",
|
||||
"${workspaceFolder}"
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -13,8 +13,8 @@ LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s"
|
||||
|
||||
UPDATE_INTERVAL = 60
|
||||
|
||||
logging.basicConfig(format=LOG_FORMAT, level=log_level, filename=LOG_FILE)
|
||||
# logging.basicConfig(format=LOG_FORMAT, level=log_level)
|
||||
# logging.basicConfig(format=LOG_FORMAT, level=log_level, filename=LOG_FILE)
|
||||
logging.basicConfig(format=LOG_FORMAT, level=log_level)
|
||||
log = logging.getLogger('bicycle-statistics')
|
||||
|
||||
def parse_args():
|
||||
|
@@ -15,6 +15,8 @@ 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']
|
||||
|
||||
|
||||
@@ -75,7 +77,10 @@ def plot_line_chart(values, ticklabels, title, xlabel, ylabel, filename, xtick_r
|
||||
plt.ylabel(ylabel)
|
||||
|
||||
for key in values.keys():
|
||||
plt.plot(values[key], label=key)
|
||||
if key == 'blind':
|
||||
plt.plot(values[key], linestyle='None')
|
||||
else:
|
||||
plt.plot(values[key], label=key)
|
||||
|
||||
plt.legend()
|
||||
plt.savefig(filename)
|
||||
@@ -86,6 +91,25 @@ 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):
|
||||
@@ -100,9 +124,10 @@ class Gpx2Html(object):
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
infiles = glob.glob(os.path.join(self.infolder, '*.gpx'))
|
||||
for filename in infiles:
|
||||
self.tracks.add(filename)
|
||||
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()
|
||||
@@ -121,39 +146,61 @@ class Gpx2Html(object):
|
||||
'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)
|
||||
now = datetime.datetime.now()
|
||||
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):
|
||||
for track in tracks:
|
||||
if track.start_time.month == date.month and track.start_time.day == date.day:
|
||||
if len(acc_year_dist) < date.timetuple().tm_yday:
|
||||
acc_year_dist.append(track.distance / 1000)
|
||||
else:
|
||||
acc_year_dist.append(track.distance / 1000 + acc_year_dist[-1])
|
||||
elif len(acc_year_dist) < date.timetuple().tm_yday:
|
||||
if len(acc_year_dist) > 0:
|
||||
acc_year_dist.append(acc_year_dist[-1])
|
||||
else:
|
||||
acc_year_dist.append(0)
|
||||
if len(acc_year_dist) == 365:
|
||||
acc_year_dist.append(acc_year_dist[-1])
|
||||
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',
|
||||
"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=14)
|
||||
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()
|
||||
@@ -256,6 +303,7 @@ class Gpx2Html(object):
|
||||
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')
|
||||
|
@@ -61,26 +61,30 @@ class Tracks(object):
|
||||
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
|
||||
try:
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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()
|
||||
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()
|
||||
|
||||
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.")
|
||||
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.")
|
||||
except Exception as exception:
|
||||
# TODO: Add logging mechanism.
|
||||
pass
|
||||
|
||||
def years(self):
|
||||
ret = None
|
||||
@@ -110,6 +114,6 @@ class Tracks(object):
|
||||
tracks = list()
|
||||
dates = pd.date_range(start_date.date(), end_date.date())
|
||||
for track in self.__tracks:
|
||||
if track.start_time.date() in dates:
|
||||
if track.start_time.date() in dates.date:
|
||||
tracks.append(track)
|
||||
return tracks
|
||||
|
13
setup.py
13
setup.py
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
from setuptools import setup
|
||||
|
||||
NAME = 'bicycle-statistics'
|
||||
VERSION = '0.2.0'
|
||||
@@ -12,11 +12,18 @@ AUTHOR = 'Thomas Klaehn'
|
||||
EMAIL = 'tkl@blackfinn.de'
|
||||
PACKAGES = ['bicycle_statistics', 'gpx_parser', 'gpx2html']
|
||||
SCRIPTS = ['example-gpx-parser', 'bicycle-stat']
|
||||
# REQUIRES = ['geopy', 'gpxpy', 'matplotlib', 'numpy', 'pandas']
|
||||
|
||||
DAEMON_START_SCRIPT = os.path.join("/lib/systemd/system", "bicycle-stat.service")
|
||||
SERVICEDIR = "/lib/systemd/system"
|
||||
|
||||
DAEMON_START_SCRIPT = os.path.join(SERVICEDIR, "bicycle-stat.service")
|
||||
|
||||
if sys.argv[1] == 'install':
|
||||
os.makedirs(SERVICEDIR)
|
||||
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)
|
||||
# setup(name=NAME, version=VERSION, author=AUTHOR, author_email=EMAIL,
|
||||
# packages=PACKAGES, scripts=SCRIPTS, install_requires=REQUIRES)
|
||||
setup(name=NAME, version=VERSION, author=AUTHOR, author_email=EMAIL,
|
||||
packages=PACKAGES, scripts=SCRIPTS)
|
||||
|
Reference in New Issue
Block a user