python4oceanographers

Turning ripples into waves

Simple drifter experiment with "kitchen tools"

Last Monday I took my oceanography students to Itaguaré beach near Bertioga. It is a very interesting region with a very peculiar (and dynamic) geomorphology. It can change drastically from one day to another, ranging from a small lagoon to a dry beach passing through a small stream of water reaching the ocean.

The image below shows a scenario similar to the one we saw last Monday. In reality, the water level was higher and the stream was completely hidden. (The image is from Google Earth's "time-series" capture.)

In [2]:
from IPython.display import Image

Image('./data/itaguare_cropped.jpg', format='jpg')
Out[2]:

Our plan was to test a "home-made" drifter and test it in the field. We used an old feature-phone as a GPS, a PET plastic bottle, and tape. Just turn on the phone GPS, place it inside the bottle and seal it with the tape. That's it!

We had a little contest for drifter designs, but unfortunately we did not have GPS for them. However, it was interesting to deploy them at the same time and observe them drifting away from each other due to drogue/sail effects and/or the current non-linearity.

I borrowed a function from another post to geo-reference the aerial image to plot our drifter tracks.

In [3]:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap


def make_map(llcrnrlon=None, urcrnrlon=None, llcrnrlat=None, urcrnrlat=None, img='./data/itaguare_cropped.jpg'):
    m = Basemap(projection='merc', llcrnrlon=llcrnrlon, urcrnrlon=urcrnrlon,
                llcrnrlat=llcrnrlat, urcrnrlat=urcrnrlat, resolution='c',
                lat_ts=(llcrnrlat + urcrnrlat) / 2.)

    fig, ax = plt.subplots(figsize=(10, 10), facecolor='none')
    m.ax = ax

    image = plt.imread(img)
    m.imshow(image, origin='upper', alpha=0.75)
  
    meridians = np.linspace(llcrnrlon, urcrnrlon, 4)
    parallels = np.linspace(llcrnrlat, urcrnrlat, 4)
    kw = dict(linewidth=0)
    m.drawparallels(parallels, labels=[1, 0, 0, 0], **kw)
    m.drawmeridians(meridians, labels=[0, 0, 0, 1], **kw)
    return fig, m

After a few deployments we chose just two to plot, to avoid polluting the figure. In addition we converted from longitude and latitude to UTM coordinates and computed the drifter velocity.

In [4]:
from pandas import DataFrame

df1 = DataFrame.from_csv('./data/drifter_01.txt')
df2 = DataFrame.from_csv('./data/drifter_02.txt')

We used Pyproj to convert from lon, lat to UTM coordinates. Such a small area is easier to wrap your mind around if you use meters.

In [5]:
from pyproj import Proj

myProj = Proj("+proj=utm +zone=23K, +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs")

UTMx, UTMy = myProj(df1['lon'].values, df1['lat'].values)
df1['UTMx'], df1['UTMy'] = UTMx, UTMy

UTMx, UTMy = myProj(df2['lon'].values, df2['lat'].values)
df2['UTMx'], df2['UTMy'] = UTMx, UTMy

For the velocity we performed just a simple differentiation scheme, nothing fancy so the students can follow the computations easily:

\begin{align*} \dfrac{dx}{dt} &= u \\ \dfrac{dy}{dt} &= v \end{align*}

In [6]:
def vel(x, t):
    dx = np.diff(x)
    dt = np.diff(t) / np.timedelta64(1, 's')
    return np.r_[0, dx / dt]

df1['u'] = vel(df1['UTMx'], df1.index.values)
df1['v'] = vel(df1['UTMy'], df1.index.values)

df2['u'] = vel(df2['UTMx'], df2.index.values)
df2['v'] = vel(df2['UTMy'], df2.index.values)

These are how our final data tables look like with the collected and calculated data.

In [7]:
df1[['lon', 'lat']].head(5)
Out[7]:
lon lat
time
2014-03-24 09:30:00 -45.972003 -23.781251
2014-03-24 09:30:28 -45.972012 -23.781312
2014-03-24 09:30:56 -45.971974 -23.781383
2014-03-24 09:31:24 -45.972003 -23.781410
2014-03-24 09:31:52 -45.972012 -23.781481
In [8]:
df1[['UTMx', 'UTMy', 'u', 'v']].head(5)
Out[8]:
UTMx UTMy u v
time
2014-03-24 09:30:00 400966.954053 7369652.962228 0.000000 0.000000
2014-03-24 09:30:28 400966.015669 7369646.098498 -0.033514 -0.245133
2014-03-24 09:30:56 400970.010481 7369638.287480 0.142672 -0.278965
2014-03-24 09:31:24 400967.074695 7369635.328706 -0.104849 -0.105670
2014-03-24 09:31:52 400966.143024 7369627.483983 -0.033274 -0.280169

The figure below shows the positions overlaid on the aerial image. The lower left and upper right corner positions were obtained by marking them on Google Earth and than cropping at the mark with an image editor. (That is probably the easiest to obtain and geo-reference an image.)

In [9]:
llcrnrlon = -(45 + (58 + 32.27/60) / 60)  # 45°58'32.27"W
urcrnrlon = -(45 + (57 + 54.54/60) / 60)  # 45°57'54.54"W
llcrnrlat = -(23 + (47 +  7.65/60) / 60)  # 23°47' 7.65"S
urcrnrlat = -(23 + (46 + 42.25/60) / 60)  # 23°46'42.25"S

fig, m = make_map(llcrnrlon=llcrnrlon, urcrnrlon=urcrnrlon, llcrnrlat=llcrnrlat, urcrnrlat=urcrnrlat)

kw = dict(marker='o', markeredgecolor='w', markersize=6, linestyle='none', latlon=True)

m.plot(df1['lon'].values, df1['lat'].values, markerfacecolor='b', **kw)
m.plot(df2['lon'].values, df2['lat'].values, markerfacecolor='cornflowerblue', **kw)

# Start.
m.plot(df1['lon'].values[0], df1['lat'].values[0], markerfacecolor='g', **kw)
m.plot(df2['lon'].values[0], df2['lat'].values[0], markerfacecolor='g', **kw)

# End.
m.plot(df1['lon'].values[-1], df1['lat'].values[-1], markerfacecolor='r', **kw)
m.plot(df2['lon'].values[-1], df2['lat'].values[-1], markerfacecolor='r', **kw)
m.ax.grid(False)

The first deployment (blue) was not that interesting, the drifter kept going back and forth due to the waves. The second deployment (red) was more interesting, the drifter went against the stream course ending up inside the cove. The next cell shows a $x, y$ plot centered at the deployment point with speed and direction to help understand what was going on.

In [10]:
x = df2['UTMx'] - df2['UTMx'][0]
y = df2['UTMy'] - df2['UTMy'][0]

fig, ax = plt.subplots()
kw = dict(cmap=plt.cm.rainbow, zorder=1)
speed = np.sqrt(df2['u']**2 + df2['v']**2)
ax.scatter(x, y, c=speed, **kw)

kw = dict(scale=20, alpha=0.6, color='black', zorder=3)
Q = ax.quiver(x, y, df2['u'], df2['v'], **kw)
fig.tight_layout()
ax.grid(True)

One theory we raised was that the wind started to pick up around $y$ = 200 meters. In fact we did noticed stronger winds at some point, but the lack of wind data render us unable to confirm that. There was a lot of back-and-forth movement as well because the waves were entering the lagoon. But I filtered them by averaging the data every 8 seconds. Hopefully, next time we will have our homemade anemometer.

I'll leave you with some pics of the field trip taken by Carol Gabani:

In [11]:
def html_fig_table(images, ncols=2, nrows=5):
    if ncols*nrows != len(images):
        raise ValueError("ncols x nrows must be equal the number of images!")
        
    unit = ["<th><img src='%s'/></th>"]
    row = ['<tr>\n%s\n</tr>' % '\n'.join(unit*ncols)]

    table = """<table>%s</table>""" % '\n'.join(row*nrows)
    return table % tuple(images)
In [12]:
import base64
from glob import glob
from IPython.display import display

images = []
for img in glob("../../images/itaguare*.jpg"):
    with open(img, "rb") as f:
        base64_data = base64.b64encode(f.read()).decode("ascii")
        images.append('data:image/png;base64,{0}'.format(base64_data))

table = html_fig_table(images, ncols=2, nrows=5)
display(HTML(table))
In [13]:
HTML(html)
Out[13]:

This post was written as an IPython notebook. It is available for download or as a static html.

Creative Commons License
python4oceanographers by Filipe Fernandes is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Based on a work at https://ocefpaf.github.io/.

Comments