-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiveplan
More file actions
executable file
·363 lines (304 loc) · 13.6 KB
/
diveplan
File metadata and controls
executable file
·363 lines (304 loc) · 13.6 KB
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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/usr/bin/python3
import csv
import sys
import datetime
import math
import time
import itertools
import argparse
import calendar
import wget
import os
from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx import Document
from docx.shared import Pt
#pip install python-docx
#pip install python3-wget
#Data from data.marine.ie/Dataset/Details/20955
#http://erddap.marine.ie/erddap/tabledap/IMI-TidePrediction.csv?&stationID=%22Lahinch_MODELLED%22&time%3E=2017-01-01T00:00:00Z&time%3C=2017-12-31T00:20:00Z&longitude%3E=-10.27019&longitude%3C=-5.92167&latitude%3E=51.53115&latitude%3C=55.37168
def parsetime(time):
return datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ")
def daterange(start, end):
if start <= end:
for n in range((end - start).days + 1):
yield start + datetime.timedelta(n)
class DiveSite(object):
name = ""
# The max level the tide should be at at start of dive
start_level_max = math.inf
# The min level the tide should be at at start of dive
start_level_min = -math.inf
# The max level the tide should be at at end of dive
end_level_max = math.inf
# The min level the tide should be at at end of dive
end_level_min = -math.inf
# The distance from site to point of departure in time
dist = 0
def __init__(self, name, start_level_max, start_level_min, end_level_max, end_level_min):
self.start_level_max = start_level_max
self.start_level_min = start_level_min
self.end_level_max = end_level_max
self.end_level_min = end_level_min
self.name = name
def can_dive(self, start_level): #, end_level):
#print("Level: ", start_level, ", self.start_level_max:", self.start_level_max, ", self.start_level_min:", self.start_level_min)
if start_level > self.start_level_max:
return False
if start_level < self.start_level_min:
return False
#if end_level > self.end_level_max:
# return False
# if end_level < self.end_level_min:
# return False
else:
return True
def __repr__(self):
return self.name
def rule_of_twelfths(high, low, time): # time in hours relative to high
range = float(high)- float(low)
return float(low) + (range * (math.cos(math.pi * time/6) + 1 ) / 2)
class DivePlan(object):
def __init__(self):
self.document = Document()
style = self.document.styles['Normal']
font = style.font
font.name = "Arial"
font.size = Pt(10)
styles = self.document.styles
style = styles.add_style('Bold Underline Style', WD_STYLE_TYPE.PARAGRAPH)
style.base_style = styles['Normal']
font = style.font
font.bold = True
font.underline = True
self.table = self.document.add_table(1,8)
self.table.style = 'Table Grid'
self.table.alignment = WD_TABLE_ALIGNMENT.CENTER
self.table.style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
font = self.table.style.font
font.name = "Arial"
font.size = Pt(10)
def add_dive(self, date, tide_list, levels, sites):
tides = [tide for tide in tide_list if tide.isNear(date)]
high = [tide for tide in tides if tide.isNear(date) and tide.get_type() == "High"]
low = [tide for tide in tides if tide.isNear(date) and tide.get_type() == "Low"]
level = levels[date]
site = [site for site in sites if site.can_dive(float(level))]
if high:
high_time = high[0].get_time().strftime("%H:%M")
high_level = high[0].get_level()
else:
high_time = ""
high_level = ""
if low:
low_time = low[0].get_time().strftime("%H:%M")
low_level = low[0].get_level()
else:
low_time = ""
low_level = ""
return self.add_row(date, site, "", "", "{}\n{}m".format(high_time, high_level), "{}\n{}m".format(low_time, low_level))
def add_row(self, time, venue, dod, coxn, hw, lw):
r = self.table.add_row()
r.cells[0].text = time.strftime("%a")
r.cells[1].text = time.strftime("%b\n%d")
r.cells[2].text = str(venue)
r.cells[3].text = time.strftime("%H:%M")
r.cells[4].text = dod
r.cells[5].text = coxn
r.cells[6].text = hw
r.cells[7].text = lw
print("Day:", time.strftime("%a"), ",Date:", time.strftime("%b %d"), ",Venue:", venue, ",Time:", time.strftime("%H:%M"), ",HW:", hw.replace('\n',' '), ",LW:", lw.replace('\n',' '))
return r
def add_blank(self):
r = self.table.add_row()
return r
def add_header(self, day, date, venue, time, dod, coxn, hw, lw):
r = self.table.add_row()
r.style = self.document.styles['Bold Underline Style']
r.cells[0].text = day
r.cells[1].text = date
r.cells[2].text = venue
r.cells[3].text = time
r.cells[4].text = dod
r.cells[5].text = coxn
r.cells[6].text = hw
r.cells[7].text = lw
def publish(self):
self.document.save('demo.docx')
class Tide(object):
def __init__(self, level, time, type):
self.level = level
self.time = time
self.type = type
def get_time(self):
return self.time
def get_type(self):
return self.type
def get_level(self):
return self.level
def __str__(self):
return self.get_type()+"("+self.get_level()+"):"+self.time.strftime("%H:%M")
def isNear(self, t):
delta = t - self.time
return delta < datetime.timedelta(hours=6)
class TideParse(object):
def __init__(self, level, time):
self.levels = []
self.times = []
self.increasing = None
self.max = level
self.min = level
self.levels.append(level)
self.times.append(parsetime(time))
def add(self, level, time):
self.levels.append(level)
self.times.append(parsetime(time))
return self.parse()
def get_time(self, level):
return self.times[self.levels.index(level)]
def parse(self):
if (self.max != max(self.levels)):
#Values increasing, rising tide
self.increasing = True
self.max = max(self.levels)
return None
if (self.min != min(self.levels)):
#Values decreasing, falling tide
self.increasing = False
self.min = min(self.levels)
return None
if (self.max != max(self.levels)) & (self.min != min(self.levels)):
raise Exception("minmax error")
if (self.max == max(self.levels)) & (self.min == min(self.levels)):
#Possible turning point
if (self.increasing == True) & (self.levels[-1] < self.levels[-2]):
t = self.get_time(self.max)
return Tide(self.max, t, "High")
elif (self.increasing == False) & (self.levels[-1] > self.levels[-2]):
t = self.get_time(self.min)
return Tide(self.min, t, "Low")
else:
return None
max_spring_high_tide = 0
max_neap_high_tide = 0
min_spring_low_tide = 0
min_neap_low_tide = 0
def datetype(string):
value = datetime.datetime.strptime(string, "%d/%m/%y")
return value
def divetimetype(string):
# datetime will make the date 1/1/1900 by default. This was
# a Monday
value = datetime.datetime.strptime(string, "%a %H:%M")
# Calculate day of the week as a number with Mon = 0
day_offset = list(calendar.day_abbr).index(string.split()[0])
# Update date to reflect the correct day of week
value = value + datetime.timedelta(days=day_offset)
return value
# First we read the csv file and parse all the high/low tides
def main(argv):
# Handle command line arguments
parser = argparse.ArgumentParser(description='Calculate dive plan.')
parser.add_argument('--publish','-p', action='store_true', help='Create table in word document.')
parser.add_argument('--data','-d', default='data.csv', metavar = "<data_file>", help='Location of raw data file in CSV format.')
parser.add_argument('--start','-s', required=True, metavar = "dd/mm/yy", type=datetype, help='Starting date to calculate dive plan from.')
parser.add_argument('--end','-e', required=True, metavar = "dd/mm/yy", type=datetype, help='Ending date to calculate dive plan to.')
parser.add_argument('--time','-t', required=True, action='append' , metavar = "\"<Mon/Tue/Wed/Thu/Fri/Sat/Sun> HH:MM\"", type=divetimetype, help='Specify the days and times for dives.')
parser.add_argument('--overwrite','-o', action='store_true', help='Overwrite data file if file exists.')
args = parser.parse_args()
publish = args.publish
data = args.data
start = args.start
end = args.end
if args.overwrite:
try:
os.remove(data)
except OSError:
pass
# Get all data for the calendar year
url = "http://erddap.marine.ie/erddap/tabledap/IMI-TidePrediction.csv?&stationID=%22Lahinch_MODELLED%22&time%3E=" + start.strftime("%Y-01-01T00:00:00Z") + "&time%3C=" + end.strftime("%Y-12-31T23:59:00Z") + "&longitude%3E=-10.27019&longitude%3C=-5.92167&latitude%3E=51.53115&latitude%3C=55.37168"
data = wget.download(url, out=data, bar=None)
tide_list = []
levels = {}
sites = []
# Parse tide data
with open(data, 'r') as csvfile:
tidedata = csv.DictReader(csvfile)
# skip title row in csv file
row = next(tidedata)
row = next(tidedata)
tc = TideParse(row['Water_Level'], row['time'])
levels.update({parsetime(row['time']) : row['Water_Level']})
for row in tidedata:
tide = tc.add(row['Water_Level'], row['time'])
levels.update({parsetime(row['time']) : row['Water_Level']})
if tide:
tide_list.append(tide)
tc = TideParse(row['Water_Level'], row['time'])
high_tides = [tide for tide in tide_list if tide.get_type() == "High"]
low_tides = [tide for tide in tide_list if tide.get_type() == "Low"]
max_spring_high_tide = max(tide.get_level() for tide in high_tides)
max_neap_high_tide = min(tide.get_level() for tide in high_tides)
min_spring_low_tide = min(tide.get_level() for tide in low_tides)
min_neap_low_tide = max(tide.get_level() for tide in low_tides)
print("Max Spring high: ", max_spring_high_tide)
print("Max Neap high: ", max_neap_high_tide)
print("Min Spring low: ", min_spring_low_tide)
print("Min Neap Low: ", min_neap_low_tide)
site = DiveSite("White Strand",
start_level_max = rule_of_twelfths(max_spring_high_tide, min_spring_low_tide, 2),
start_level_min = -math.inf,
end_level_max = rule_of_twelfths(max_spring_high_tide, min_spring_low_tide, 2),
end_level_min = -math.inf)
sites.append(site)
site = DiveSite("Ballyvaughan",
start_level_max = math.inf,
start_level_min = rule_of_twelfths(max_neap_high_tide, min_neap_low_tide, 1.5),
end_level_max = math.inf,
end_level_min = rule_of_twelfths(max_neap_high_tide, min_neap_low_tide, 1.5))
sites.append(site)
site = DiveSite("Gleninagh",
start_level_max = rule_of_twelfths(max_spring_high_tide, min_spring_low_tide, 1),
start_level_min = -math.inf,
end_level_max = rule_of_twelfths(max_spring_high_tide, min_spring_low_tide, 1),
end_level_min = -math.inf)
sites.append(site)
site = DiveSite("Quilty",
start_level_max = math.inf,
start_level_min = rule_of_twelfths(max_neap_high_tide, min_neap_low_tide, 0),
end_level_max = math.inf,
end_level_min = -math.inf)
sites.append(site)
site = DiveSite("Kilkee",
start_level_max = math.inf,
start_level_min = rule_of_twelfths(max_spring_high_tide, min_spring_low_tide, 5),
end_level_max = math.inf,
end_level_min = rule_of_twelfths(max_spring_high_tide, min_spring_low_tide, 5))
sites.append(site)
site = DiveSite("Doolin",
start_level_max = math.inf,
start_level_min = -math.inf,
end_level_max = math.inf,
end_level_min = -math.inf)
sites.append(site)
diveplan = DivePlan()
# diveplan.add_header("Day", "Date", "Venue", "Time", "DoD", "Coxn", "HW", "LW")
for date in daterange(start, end):
r_last = None
for divedate in args.time:
if date.weekday() == divedate.weekday():
date = date.replace(hour=divedate.hour, minute=divedate.minute)
r = diveplan.add_dive(date, tide_list, levels, sites)
if r_last:
for id in [0,1,2,6,7]:
c1 = r_last.cells[id]
text = c1.text
c2 = r.cells[id]
c1 = c1.merge(c2)
c1.text = text
r_last = r
if publish:
diveplan.publish()
if __name__ == "__main__":
main(sys.argv[1:])