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)