Skip to content

How to make your own Tide Table using Python and Pytides

Sam Cox edited this page Nov 15, 2013 · 2 revisions

This page assumes you know how to obtain a tidal model from Pytides. If not, please see here (we will be using the same data as in that example).

###Aim We want to produce a monthly tide table for King's Point, with the highs and lows of each day listed. It is best practice to interact with Pytides in UTC/GMT datetimes, but it would be more convenient to have our tide table in US/Eastern time. We will generate an HTML file with our output.

###Requirements In addition to Pytides and its requirements (numpy, scipy), we require:

  • Pytz, for timezone conversions
  • Jinja2, for rendering our output into HTML.

###Method ####Template First, we will use a free table generator to make an attractive table for our output. After a couple of changes to the HTML, we adapt this for use as a Jinja template:

<html>
<head>
<style>
h3 {font: sans-serif; color: #002277}
.datagrid table { border-collapse: collapse; text-align: left; width: 500px; }
.datagrid {font: normal 12px/150% Arial, Helvetica, sans-serif; background: #fff; overflow: hidden; border: 1px solid #006699; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; width:500px; }
.datagrid table td, .datagrid table th { padding: 3px 10px; }
.datagrid table thead th {background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #006699), color-stop(1, #00557F) );background:-moz-linear-gradient( center top, #006699 5%, #00557F 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#006699', endColorstr='#00557F');background-color:#006699; color:#FFFFFF; font-size: 15px; font-weight: bold; border-left: 1px solid #0070A8; }
.datagrid table thead th:first-child { border: none; }
.datagrid table tbody td { color: #00557F; border-left: 1px solid #E1EEF4;font-size: 12px;font-weight: normal; }
.datagrid table tbody .alt td { background: #E1EEf4; color: #00557F; }
.datagrid table tbody td:first-child { border-left: none; }
.datagrid table tbody tr:last-child td { border-bottom: none; }
.datagrid table tfoot td div { border-top: 1px solid #006699;background: #E1EEf4;}
.datagrid table tfoot td { padding: 0; font-size: 12px }
.datagrid table tfoot td div{ padding: 2px; }
.date {float:left}
.day {float:right}
.time {float:left}
.height {float:right}
</style>
</head>
<body>
<h3>Tide Table for {{location}} ({{month}} {{year}})</h3>
<div class="datagrid">
<table>
<thead><tr><th>Date</th><th colspan="4">Predictions</th></thead>
<tfoot><tr><td colspan="5"><div id="no-paging">Predictions given in {{units}} and {{tzname}} relative to {{datum}}</div></tr></tfoot>
<tbody>
{% for row in rows %}
<tr class="{{ loop.cycle('', 'alt') }}">
	<td><strong class="date">{{row[0]['date']}}</strong><strong class="day">{{row[0]['day']}}</strong></td>
	{% for e in row[1] %}
	<td><span class="time">{{ e['time'] }}</span><span class="height">{{ e['height'] }}</span></td>
	{% endfor %}
</tr>
{% endfor %}
</tbody>
</table></div>
</body>
</html>

Now we can save this file as template.html and it should be ready for templating.

####Rendering In one single script, we will:

  • build a tidal model from previously collected data
  • find the highs and lows and convert them to local time
  • render the results using our template

Here's an example of how that might be done:

from datetime import datetime, timedelta
import calendar
from pytides.tide import Tide
from pytz import timezone
import numpy as np
from jinja2 import Environment, FileSystemLoader

##Import our tidal data
station_id = '8516945'
heights = []
t = []
f = open('data/'+station_id, 'r')
for i, line in enumerate(f):
    t.append(datetime.strptime(" ".join(line.split()[:2]), "%Y-%m-%d %H:%M"))
    heights.append(float(line.split()[2]))
f.close()

##Fit the tidal data to the harmonic model using Pytides
my_tide = Tide.decompose(np.array(heights[::10]), np.array(t[::10]))

##Prepare our variables for the template
location = "King's Point"
tzname = "US/Eastern"
tz = timezone(tzname)
utc = timezone('UTC')
datum = "MLLW"
units = "metres"
year = 2013
month = 1
rows = []
for day in range(1,calendar.monthrange(year,month)[1] + 1):
	start = tz.localize(datetime(year, month, day))
	end = start + timedelta(days=1)
	startUTC = utc.normalize(start.astimezone(utc))
	endUTC = utc.normalize(end.astimezone(utc))
	extremaUTC = my_tide.extrema(startUTC, endUTC)
	date = {'date': day, 'day': calendar.day_abbr[start.weekday()]}
	extrema = []
	for e in extremaUTC:
		time = tz.normalize(e[0].astimezone(tz))
		##Round the time to the nearest minute
		time = time + timedelta(minutes=time.second > 30)
		height = e[1][0]
		extrema.append({'time': time.strftime('%H:%M'), 'height': "{0:.2f}".format(height)})
	#This is just for nicer formatting of days with only three tides
	for _ in range(4 - len(extrema)):
		extrema.append({'time': '', 'height': ''})
	rows.append([date, extrema])

##Render our template
env = Environment(loader=FileSystemLoader(""),trim_blocks=True)
template = env.get_template('template.html')
with open("output.html", "wb") as fh:
    fh.write(template.render(
	location = location,
	tzname = tzname,
	datum = datum,
	units = units,
	year = year,
	month = calendar.month_name[month],
	rows = rows
	))

Saving this as generate.py and running it will output our result to output.html. It looks something like this: Result

You can compare it the NOAA's table here (see the right hand side of the page).