Tidal Environments#

In chapter 4 of the textbook, we looked into the global variation in the main processes that shape the coast: wind, waves, and tides. In this notebook, we will focus on the large-scale variation in tides. The two main variables on the basis of which tidal environments can be classified are:

  • Magnitude of the tide, characterised by the tidal range;

  • Tidal character, determined by the importance of diurnal vs. semi-diurnal components

In this notebook, we will explore both classifications using python code. Therefore, please import the libraries that we need for the analysis from the cell below.

import pathlib
from pathlib import Path
import sys
from warnings import filterwarnings
import ipywidgets as widgets
from ipywidgets import interact
from matplotlib.patches import Patch
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import cartopy.crs as ccrs
import xarray as xr
from datetime import datetime, timedelta
from IPython.display import display, Image
import math
import pandas as pd
import pooch
import pickle

F_data_fp = pooch.retrieve(
    "https://coclico.blob.core.windows.net/coastal-dynamics/02-tide/02_F_data.pkl",
    known_hash="eae7be0e7b44ed5b211e931bd6e5948e0aa8db067403956fe2d486f69e49c769",
)
F_data = pd.read_pickle(F_data_fp)

cwd = pathlib.Path().resolve()
proj_dir = cwd.parent.parent.parent  # this is the root of the CoastalCodeBook
sys.path.append(str(proj_dir))

from initialize.Tide_Initialize import questions_3a1, questions_3a2, plot_4timeseries_with_interactive_controls
Downloading data from 'https://coclico.blob.core.windows.net/coastal-dynamics/02-tide/02_F_data.pkl' to file '/home/runner/.cache/pooch/918dec1d06d7bc0691082dd30d324d60-02_F_data.pkl'.
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[1], line 20
     17 import pooch
     18 import pickle
---> 20 F_data_fp = pooch.retrieve(
     21     "https://coclico.blob.core.windows.net/coastal-dynamics/02-tide/02_F_data.pkl",
     22     known_hash="eae7be0e7b44ed5b211e931bd6e5948e0aa8db067403956fe2d486f69e49c769",
     23 )
     24 F_data = pd.read_pickle(F_data_fp)
     26 cwd = pathlib.Path().resolve()

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/pooch/core.py:239, in retrieve(url, known_hash, fname, path, processor, downloader, progressbar)
    236 if downloader is None:
    237     downloader = choose_downloader(url, progressbar=progressbar)
--> 239 stream_download(url, full_path, known_hash, downloader, pooch=None)
    241 if known_hash is None:
    242     get_logger().info(
    243         "SHA256 hash of downloaded file: %s\n"
    244         "Use this value as the 'known_hash' argument of 'pooch.retrieve'"
   (...)
    247         file_hash(str(full_path)),
    248     )

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/pooch/core.py:807, in stream_download(url, fname, known_hash, downloader, pooch, retry_if_failed)
    803 try:
    804     # Stream the file to a temporary so that we can safely check its
    805     # hash before overwriting the original.
    806     with temporary_file(path=str(fname.parent)) as tmp:
--> 807         downloader(url, tmp, pooch)
    808         hash_matches(tmp, known_hash, strict=True, source=str(fname.name))
    809         shutil.move(tmp, str(fname))

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/pooch/downloaders.py:240, in HTTPDownloader.__call__(self, url, output_file, pooch, check_only)
    238     progress = self.progressbar
    239     progress.total = total
--> 240 for chunk in content:
    241     if chunk:
    242         output_file.write(chunk)

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/requests/models.py:820, in Response.iter_content.<locals>.generate()
    818 if hasattr(self.raw, "stream"):
    819     try:
--> 820         yield from self.raw.stream(chunk_size, decode_content=True)
    821     except ProtocolError as e:
    822         raise ChunkedEncodingError(e)

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/urllib3/response.py:1060, in HTTPResponse.stream(self, amt, decode_content)
   1058 else:
   1059     while not is_fp_closed(self._fp) or len(self._decoded_buffer) > 0:
-> 1060         data = self.read(amt=amt, decode_content=decode_content)
   1062         if data:
   1063             yield data

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/urllib3/response.py:949, in HTTPResponse.read(self, amt, decode_content, cache_content)
    946     if len(self._decoded_buffer) >= amt:
    947         return self._decoded_buffer.get(amt)
--> 949 data = self._raw_read(amt)
    951 flush_decoder = amt is None or (amt != 0 and not data)
    953 if not data and len(self._decoded_buffer) == 0:

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/urllib3/response.py:873, in HTTPResponse._raw_read(self, amt, read1)
    870 fp_closed = getattr(self._fp, "closed", False)
    872 with self._error_catcher():
--> 873     data = self._fp_read(amt, read1=read1) if not fp_closed else b""
    874     if amt is not None and amt != 0 and not data:
    875         # Platform-specific: Buggy versions of Python.
    876         # Close the connection when no data is returned
   (...)
    881         # not properly close the connection in all cases. There is
    882         # no harm in redundantly calling close.
    883         self._fp.close()

File ~/micromamba/envs/coastal/lib/python3.11/site-packages/urllib3/response.py:856, in HTTPResponse._fp_read(self, amt, read1)
    853     return self._fp.read1(amt) if amt is not None else self._fp.read1()
    854 else:
    855     # StringIO doesn't like amt=None
--> 856     return self._fp.read(amt) if amt is not None else self._fp.read()

File ~/micromamba/envs/coastal/lib/python3.11/http/client.py:473, in HTTPResponse.read(self, amt)
    470 if self.length is not None and amt > self.length:
    471     # clip the read to the "end of response"
    472     amt = self.length
--> 473 s = self.fp.read(amt)
    474 if not s and amt:
    475     # Ideally, we would raise IncompleteRead if the content-length
    476     # wasn't satisfied, but it might break compatibility.
    477     self._close_conn()

File ~/micromamba/envs/coastal/lib/python3.11/socket.py:706, in SocketIO.readinto(self, b)
    704 while True:
    705     try:
--> 706         return self._sock.recv_into(b)
    707     except timeout:
    708         self._timeout_occurred = True

File ~/micromamba/envs/coastal/lib/python3.11/ssl.py:1314, in SSLSocket.recv_into(self, buffer, nbytes, flags)
   1310     if flags != 0:
   1311         raise ValueError(
   1312           "non-zero flags not allowed in calls to recv_into() on %s" %
   1313           self.__class__)
-> 1314     return self.read(nbytes, buffer)
   1315 else:
   1316     return super().recv_into(buffer, nbytes, flags)

File ~/micromamba/envs/coastal/lib/python3.11/ssl.py:1166, in SSLSocket.read(self, len, buffer)
   1164 try:
   1165     if buffer is not None:
-> 1166         return self._sslobj.read(len, buffer)
   1167     else:
   1168         return self._sslobj.read(len)

KeyboardInterrupt: 



1. Tidal Environments#

The tidal wave is distorted by local differences in water depth and by the location and shape of land masses and large embayments. This results in a global variation in tidal range controlled by the large-scale coastal configuration. The tidal character expressed through mean spring tidal range:

Category

Mean spring tidal range

Micro-tidal

< 2m

Meso-tidal

2m - 4m

Macro-tidal

> 4m


The tidal character, on the other hand, is defined by the form factor F:

F = (K1 + O1)/(M2 + S2),
where K1, O1, M2, and S2 are the amplitudes of the corresponding tidal constituents.

Category

Value of F

Semi-diurnal

0 - 0.25

Mixed, mainly semi-diurnal

0.25 - 1.5

Mixed, mainly diurnal

1.5 - 3

Diurnal

> 3


In the figure below, you can see the world distribution of mean spring tidal range (left) and tidal characters (right). The attribution of the tidal types follows J.L. Davies and Clayton (1980) (Figures 4.10 and 4.12 from the textbook).

  • Look into the semi-enclosed seas vs. open coasts; do you notice anything? Why?

  • Compare the two: do you notice any repetitive patterns? Hint: Look at the tidal range for specific tidal characters.

image




2. Tidal Character at Specific Locations#

Let’s now categorize the tide at four specific locations. We will once again use the FES2014 Global Tide data, which provides amplitude and phase information for 34 tidal constituents, distributed on 1/16˚ grids.

For each location, the amplitudes of the constituents that are needed to compute the form factor are shown in the table below.

Tidal amplitudes [cm]

M2

S2

K1

O1

Scheveningen (Netherlands)

75.78

17.74

8.39

11.85

Galveston (US Gulf of Mexico)

13.08

3.97

16.17

15.89

Jakarta (Indonesia)

4.58

5.18

25.75

13.46

Valparaiso (Chile)

42.91

14.40

15.29

10.22


In cell below, write your own code to calculate the form factor F at each location.

# Write your code here.
# Run to get questions

print("\n What is the category of:")
questions_3a1()



Let’s now plot the tidal characters across the globe obtained from the FES2014 dataset and mark the four locations from above. Do your answers match the locations on the map?

## FES2014 Tidal Characters
# the code is a bit slow, it is doing a global contour!

# Define the categories and corresponding colors
categories = {
    'Diurnal': {'min': 3, 'max': float('inf'), 'color': '#ad4123'},
    'Mixed, mainly diurnal': {'min': 1.5, 'max': 2.9, 'color': '#e7541e'},
    'Mixed, mainly semidiurnal': {'min': 0.25, 'max': 1.49, 'color': '#f4a030'},
    'Semidiurnal': {'min': 0, 'max': 0.249, 'color': '#f8ce97'}
}

# Create a figure and axis
fig, ax = plt.subplots(figsize=(15, 13), subplot_kw={'projection': ccrs.Robinson(central_longitude=0.0)})
ax.set_global()

# Plot the scatter points with specific colors for each category
legend_patches = []
for category, values in categories.items():
    subset_data = F_data[(F_data['F'] >= values['min']) & (F_data['F'] <= values['max'])]
    if not subset_data.empty:
        scatter = ax.scatter(subset_data.index.get_level_values('lon').values, 
                             subset_data.index.get_level_values('lat').values, s=1, color=values['color'], 
                             label=category, transform=ccrs.PlateCarree())
        legend_patches.append(Patch(color=scatter.get_facecolor()[0], label=category))

# Add markers for specific locations - here you can edit the code if you are wondering for a specific location
locs = {
    'Scheveningen': [4.25, 52.125],  # lon, lat
    'Galveston': [-94.6875, 29.25],
    'Valparaiso': [-71.625, -33],
    'Jakarta': [106.8125, -6.0625],
}

for loc, coordinates in locs.items():
    lon, lat = coordinates
    ax.scatter(lon, lat, color='black', s=10, transform=ccrs.PlateCarree(), zorder=4)
    ax.text(lon - 25, lat+3, loc, color='black', fontsize=12, fontweight='bold', transform=ccrs.PlateCarree())

ax.legend(handles=legend_patches, loc='lower center', bbox_to_anchor=(0.5, -0.2), ncol=2, fontsize=12)
ax.coastlines(resolution='110m', color='black', linewidth=0.5)

plt.show()

Compare this figure to the tidal characters illustration mentioned earlier (Figure 4.12 in the textbook). What could be the reasons behind the observed differences?




3. Variation of Tidal Constituents#

Now that we’ve identified the tidal characteristics of each location, we can visualize their tidal constituents and address various questions.

Execute the block below to generate an interactive figure. The figure displays the individual tidal components (upper plot) and their combined tidal signal (second plot) for four locations, each characterized by distinct tidal behaviours.

You can adjust the plotted time range using the slider (from 1 day to 1 year) and select which tidal constituents to display with tick boxes. This allows you to experiment with different constituents, observe the resulting signals, and compare the locations.

# Choose tidal constituents
comps = ['M2', 'S2', 'N2', 'K2',  #semi-diurnal
         'K1', 'O1', 'P1', 'Q1',  #diurnal
         'MM', 'MF', 'SSA',       #long period
         'M4', 'M6', 'S4', 'MN4'] #short period (overtides)

# We choose one year to plot, 2000-2001 same as in the previous Notebook
dates = np.array([
    datetime(2000, 1, 1, 0, 0, 0) + timedelta(seconds=item * 3600)
    for item in range(24*365) #1 year
])

# download and load tidal signals
scheveningen_fp = pooch.retrieve(
    "https://coclico.blob.core.windows.net/coastal-dynamics/2_wind_waves_tides/tide_scheveningen.p",
    known_hash="4ebac210fc0893e52655cbc3c9501a6c805e3537f327fed7edb9e7dbfe7fa06a",
)
galveston_fp = pooch.retrieve(
    "https://coclico.blob.core.windows.net/coastal-dynamics/2_wind_waves_tides/tide_galveston.p",
    known_hash="26af20b240804a18a939d477632b4507d44ebdc98510deb07e601732c0846224",
)
jakarta_fp = pooch.retrieve(
    "https://coclico.blob.core.windows.net/coastal-dynamics/2_wind_waves_tides/tide_jakarta.p",
    known_hash="7950246c47e757d9dd427063d7c809fda4267ed119efd18de43237aa9f98c9c6",
)
valparaiso_fp = pooch.retrieve(
    "https://coclico.blob.core.windows.net/coastal-dynamics/2_wind_waves_tides/tide_valparaiso.p",
    known_hash="a19a51e3607822bc72ab902f83990d2b318a08d1982c25461f8ffd6e5caae35f",
)
with open(scheveningen_fp, 'rb') as pickle_file:
    scheveningen = pickle.load(pickle_file)
with open(galveston_fp, 'rb') as pickle_file:
    galveston = pickle.load(pickle_file)
with open(jakarta_fp, 'rb') as pickle_file:
    jakarta = pickle.load(pickle_file)
with open(valparaiso_fp, 'rb') as pickle_file:
    valparaiso = pickle.load(pickle_file)

tide = {
    'Scheveningen': scheveningen,
    'Valparaiso': valparaiso,
    'Jakarta': jakarta,
    'Galveston': galveston,
    }

plot_4timeseries_with_interactive_controls(comps, dates, tide)
# We start with the main constituents preselected

Set the time range to around 30 days and
 a) select only the main semi-diurnal components
 b) select only the main diurnal components
 c) select only one semi-diurnal (M2) and one diurnal (K1) component
What kind of signal do you see for each location? Why are there differences between locations? Don’t forget to look at the y-axis range.

We also know that there are much longer variations than the ones above. Set the time range to the whole year and
 a) select only S2 and K2
 b) select only K1 and P1
What kind of combined signal can you see now?

Finally activate all components, including some short period constituents that we will further explore next week. Analyse the combined signals. What are the dominant components at each location?



4. Tidal Beating#

Using the knowledge gained from Chapter 3 of the textbook and the interactive figure above, try to answer the questions below. You can use the next cell as a calculator. Give your numeric answer with at least three significant figures.

Tidal constituents

Name

Equil.
Amplitude [m]

Period [h]

Semi-diurnal

Principal lunar

M2

0.24

12.4206012

Principal solar

S2

0.11

12

Lunar elliptical

N2

0.046

12.65834751

Lunar-solar declinational

K2

0.031

11.96723606

Diurnal

Lunar-solar declinational

K1

0.14

23.93447213

Principal lunar

O1

0.10

25.81933871

Principal solar

P1

0.047

24.06588766

Lunar elliptical

Q1

0.019

26.868350

Long period

Fortnightly

Mf

0.042

327.8599387

Monthly

Mm

0.022

661.3111655

Semi-annual

Ssa

0.019

4383.076325

# Write your code here to get answers to questions 1 and 2. Use the values from the table above 
# and use at least 3 significant figures.
# Run to get questions. 
# The following answers are interchangeable, so do not put the same answer twice - this will result in a false correct answer.
# TC1, TC2
# TC3, TC4
# Month1, Month2
# TC5, TC6
# Month3, Month4
# TC7, TC8

questions_3a2()