-
Notifications
You must be signed in to change notification settings - Fork 172
/
sql-make-graph-speed-ave.py
289 lines (253 loc) · 10.7 KB
/
sql-make-graph-speed-ave.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/usr/bin/env python
'''
written by Claude Pageau
Speed Camera Utility to create graph image files from
the sqlite3 database data/speed_cam.db using matplotlib.
'''
from __future__ import print_function
prog_ver = '13_02'
DEBUG = False
print('Loading ver %s DEBUG= %s ... ' % (prog_ver, DEBUG))
import sqlite3
import os
import time
import datetime as dt
import sys
try:
import matplotlib
except ImportError:
print('''
matplotlib import failed.
To install run the following commands
sudo apt update
sudo apt upgrade
sudo apt install python-matplotlib python3-matplotlib
Note these installs will take some time ...
''')
sys.exit(1)
matplotlib.use('Agg') # Allow graphs to be created without display eg SSH
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)-8s %(funcName)-10s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
logging.getLogger('matplotlib.font_manager').disabled = True
# Import Variable constants from config.py
from config import DB_DIR
from config import DB_NAME
from config import DB_TABLE
from config import MO_SPEED_MPH_ON
from config import GRAPH_PATH
from config import GRAPH_ADD_DATE_TO_FILENAME # Prefix graph image filename with datetime for uniqueness.
from config import GRAPH_RUN_TIMER_HOURS
from config import GRAPH_RUN_LIST
if not os.path.exists(GRAPH_PATH): # Check if grpahs directory exists
os.makedirs(GRAPH_PATH) # make directory if Not Found
# Create help Message Strings
help_msg_title = ('''
This program will query the speed camera sqlite3 database per config.py settings or command line parameters.
It will then generate matplotlib graph images per selection and grouping values.
Command Line parameters
-----------------------
Selects Records per -s speed over, -d days previous and groups counts by -t totals eg hour, day, month
matplotlib will then create jpg graph images to auto created filenames. You can add optional datetime prefix by
setting config.py GRAPH_ADD_DATE_TO_FILENAME boolean variable. Default is False (overwrites previous existing filenames)
Graph will show record count totals for specified speeds over and days previous
totaled by log_timestamp Grouping eg hour, day, month.
No Command Line Parameters
--------------------------
If NO command line parameters are supplied then multiple graphs can be created per the speed camera config.py
GRAPH_RUN_DATA list variable. This can generate multiple graph image files per specified list data criteria.
See config.py comments for required GRAPH_RUN_DATA list values.
''')
help_msg = ('''
NOTE:
Run a single graph by passing parameters to this script. For details See
%s -h
or Create Multiple Graphs from config.py
To Edit/Add/Remove Graph Images.
Edit config.py GRAPH_RUN_LIST variable under matplotlib settings.
Graph Images can be viewed from Speed Camera Web Page at %s
''' % (sys.argv[0], GRAPH_PATH))
if len(sys.argv) > 1:
import argparse
ap = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=help_msg_title)
ap.add_argument('-s', required=True, type=int, action='store', dest='speed',
help='speed over- Integer - Selects Database Records with Speeds >= specified Value')
ap.add_argument('-d', required=True, type=int, action='store', dest='days',
help='days prev - Integer - Selects Database Records where log_timestamp is between now and specified number of days previous')
ap.add_argument('-t', required=True, action='store', dest='totals',
help='total by - String - Groups Count Totals by specified string. Valid Strings are: hour day month')
args = ap.parse_args()
if args.totals not in ['hour', 'day', 'month']:
print('-t option must be a valid string value: hour, day or month')
sys.exit(1)
speed_over = args.speed
days_prev = args.days
total_by = args.totals
#----------------------------------------------------------------------------------------
def is_int(var):
''' Check if variable string can successfully be converted to an integer.
'''
try:
int(var)
except ValueError:
return False
return True
#----------------------------------------------------------------------------------------
def get_timestamp_substr(total_by):
'''
Convert hour, day or month string to required
values for changing the log_timestamp to
an appropriate substring value
'''
total_by = total_by.upper()
if total_by == 'HOUR':
timestamp_subst = '2, 13'
elif total_by == 'DAY':
timestamp_subst = '2, 10'
elif total_by == 'MONTH':
timestamp_subst = '2, 7'
else:
logging.info("total_by variable must be string. Valid values are hour, day, month")
logging.warning("Defaulting to hour")
timestamp_subst = '2, 13'
return timestamp_subst
#----------------------------------------------------------------------------------------
def get_speed_units_str():
'''
Convert config.py MO_SPEED_MPH_ON boolean to a string.
'''
speed_unit = 'kph'
if MO_SPEED_MPH_ON:
speed_unit = 'mph'
return speed_unit
#----------------------------------------------------------------------------------------
def get_query_str(total_by, days_prev, speed_over):
''' Create Sqlite3 Query to Get Speed Averages for
specified days previous and speeds over
'''
timestamp_subst = get_timestamp_substr(total_by)
sql_query_speed_ave = ('''
select
substr(log_timestamp, %s) log_date,
round(avg(ave_speed), 1) speed_ave
from %s
where
ave_speed >= %s and
substr(log_timestamp, 2, 11) >= DATE('now', '-%i days') and
substr(log_timestamp, 2, 11) <= DATE('now', '+1 day')
group by
log_date
''' % (timestamp_subst, DB_TABLE, speed_over, int(days_prev)))
return sql_query_speed_ave
#----------------------------------------------------------------------------------------
def make_graph_image(total_by, days_prev, speed_over):
''' Extract Data from sql db and generate matplotlib graph showing totals for specified
hour, day, month
'''
if not (is_int(days_prev) and is_int(speed_over)):
logging.error("days_prev and speed_over must be integers >= 0")
return
days_prev = abs(days_prev) # Make sure they are positive
speed_over = abs(speed_over)
speed_units = get_speed_units_str()
total_by = total_by.upper()
db_path = os.path.join(DB_DIR, DB_NAME)
count_sql_query = get_query_str(total_by, days_prev, speed_over)
right_now = dt.datetime.now()
now = ("%02d-%02d-%02d-%02d:%02d" % (right_now.year,
right_now.month,
right_now.day,
right_now.hour,
right_now.minute))
if GRAPH_ADD_DATE_TO_FILENAME:
file_now = now + '_' # prefix file name with datetime
else:
file_now = '' # No Datetime on filename
image_filepath = os.path.join(GRAPH_PATH,
file_now +
'graph_speed_' +
'prev' + str(days_prev) +
'days_by' + total_by.lower() +
'_ge' + str(speed_over) + speed_units +
'.jpg')
graph_title = ('Previous %s days AVE %s by %s for SPEEDS >= %s %s\n%s' %
(str(days_prev), speed_units.upper(), total_by, str(speed_over), speed_units, now))
if DEBUG:
logging.info("Running: %s", graph_title)
logging.info("Connect to Database %s", db_path)
connection = sqlite3.connect(db_path)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
if DEBUG:
logging.info('Executing Query \n %s', count_sql_query)
cursor.execute(count_sql_query)
xd = [] # list for database query date data
y = [] # list for database query count data
while True:
row = cursor.fetchone()
if row is None:
break
if DEBUG:
print(row)
xdat, ydat = row
# Create x,y data lists for matplotlib plt
if total_by == 'HOUR':
xd.append(dt.datetime.strptime(xdat, '%Y-%m-%d %H'))
elif total_by == 'DAY':
xd.append(dt.datetime.strptime(xdat, '%Y-%m-%d'))
elif total_by == 'MONTH':
xd.append(dt.datetime.strptime(xdat, '%Y-%m'))
y.append(ydat)
cursor.close()
connection.close()
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d %H'))
plt.gca().xaxis.set_major_locator(mdates.DayLocator())
plt.gcf().autofmt_xdate()
plt.figure(figsize=(10.0, 7.0), dpi=100)
plt.title(graph_title)
plt.ylabel('AVE ' + speed_units.upper() + ' by ' + total_by)
plt.plot(xd, y)
plt.xticks(rotation=60)
plt.tight_layout()
plt.savefig(image_filepath)
logging.info('Saved - %s', image_filepath)
return image_filepath
#----------------------------------------------------------------------------------------
def graph_from_list():
''' Generate multiple graph images from config.py GRAPH_RUN_LIST Variable
'''
run_cntr = 0
total_graphs = len(GRAPH_RUN_LIST)
logging.info("--- Start Generating %i Graph Images from config.py GRAPH_RUN_LIST Variable", total_graphs)
start_time = time.time() # Start timer for processing duration
for graph_data in GRAPH_RUN_LIST:
run_cntr +=1
total_by = graph_data[0]
days_prev = graph_data[1]
speed_over = graph_data[2]
logging.info("%i of %i - prev %i days, count by %s, speed over %i %s)",
run_cntr, total_graphs, days_prev, total_by.upper(), speed_over, get_speed_units_str())
make_graph_image(total_by, days_prev, speed_over)
duration = time.time() - start_time
logging.info("--- Finish. Processing Took %.2f seconds", duration)
if __name__ == '__main__':
try:
if len(sys.argv) > 1:
make_graph_image(total_by, days_prev, speed_over)
else:
if GRAPH_RUN_TIMER_HOURS > 0.0:
while True:
graph_from_list()
logging.warning('Looping Timer Set per config.py GRAPH_RUN_TIMER_HOURS = %.2f', GRAPH_RUN_TIMER_HOURS)
logging.info('Next Run is in %.2f hours', GRAPH_RUN_TIMER_HOURS)
print('Waiting ... Ctr-C to Exit')
time.sleep(GRAPH_RUN_TIMER_HOURS * 3600)
else:
graph_from_list()
except KeyboardInterrupt:
print("\nUser Pressed Keyboard Ctrl-C to Exit")
print(help_msg)
print('Ver %s Bye ....' % prog_ver)