Make movie of an experimental campaign#

Document:

  • lon, lat must be “longitude”, “latitude”

  • drifter data must be interpolated on the movie target sampling rate, likely a bug

import numpy as np
import pandas as pd
import xarray as xr

%matplotlib inline
import matplotlib.pyplot as plt

import pynsitu as pin
import pynsitu.movies as movies

crs = pin.maps.crs
# movie parameters
dt = "10T"  # sampling rate

generate sample data#

start = "2018-01-01"
end = "2018-01-15"

extent = [4, 8, 41, 44]

lonc, latc = (extent[0] + extent[1]) * 0.5, (extent[2] + extent[3]) * 0.5
scale = 111e3


## drifters
def generate_drifter_data(drifter_id):
    """Create a drifter time series."""
    time = pd.date_range(start=start, end=end, freq="1H")
    v = 0.5  # m/s approx
    ux = v * np.random.randn(time.size)
    uy = v * np.random.randn(time.size)
    df = pd.DataFrame(dict(u=ux, v=uy, time=time))
    dt = (time[1] - time[0]) / pd.Timedelta("1s")
    df["longitude"] = lonc + df["u"].cumsum() * dt / scale / np.cos(latc * np.pi / 180)
    df["latitude"] = latc + df["v"].cumsum() * dt / scale
    # lon = v * np.cos(2 * np.pi * ((time - time[0]) / time_scale)) / scale
    # lat = v * np.sin(2 * np.pi * ((time - time[0]) / time_scale)) / scale
    # df = pd.DataFrame(dict(lon=lonc + lon, lat=latc + lat, time=time))
    df["id"] = drifter_id
    df = df.set_index("time")
    return df


dr = pd.concat([generate_drifter_data(i) for i in range(0, 5)])
# resample into target temporal resolution
dr = dr.groupby("id").apply(lambda df: df.ts.resample_uniform(dt)).droplevel(0)


## ships
def generate_ship_data():
    """Create a ship time series."""
    time = pd.date_range(start=start, end=end, freq="30T")
    time_scale = pd.Timedelta("1D")
    dl = 0.5
    lon = dl * np.cos(2 * np.pi * ((time - time[0]) / time_scale))
    lat = dl * np.sin(2 * np.pi * ((time - time[0]) / time_scale))
    df = pd.DataFrame(dict(longitude=lonc + lon, latitude=latc + lat, time=time))
    df["id"] = "myid"
    df = df.set_index("time")
    return df


ship = generate_ship_data()

## wind
dl = 0.25
wind = xr.Dataset(
    None,
    coords=dict(
        longitude=np.arange(extent[0], extent[1], dl),
        latitude=np.arange(extent[2], extent[3], dl),
        time=ship.index,
    ),
)
omega = 2 * np.pi / 5
k = 2 * np.pi / 10
U0 = 10
wind["u"] = U0 * np.cos(
    omega * (wind.time - wind.time[0]) / pin.day - k * wind.longitude
)
wind["v"] = U0 * np.sin(
    omega * (wind.time - wind.time[0]) / pin.day - k * wind.longitude
)

prepare for movie generation#

# map_gen = lambda : cp.map(extent=bounds, rivers=False, tile=("toner-lite", 12), land=False, bathy=False, coastline=False)

# or dynamic adjustment of extent
# dl = 1
# extent = (lonc - dl, lonc + dl, latc - dl, latc + df)  # fixed extent
# extent = None # dynamic adjustment of extent

# map_gen = lambda extent: pin.maps.plot_map(extent)
map_gen = lambda: pin.maps.plot_map(extent=extent, coastline="i")

# if extent is None:
#    # dynamic adjustment of extent
#    extent = dict(buffer=0.1, aspect_ratio=(2, 1), exclude=["wind"])

dr_keys = list(dr["id"].unique())
colorsd = {d: c for d, c in zip(dr_keys, pin.get_cmap_colors(len(dr_keys)))}
mv_dr = dict(
    dtype="drifter", data=dr, colors=colorsd, dt_trail="1h", head_style=dict(alpha=0.5)
)

mv_ship = dict(
    dtype="moving",
    data=ship,
    color="k",
    dt_trail="5h",
)

mv_wd = dict(
    dtype="vector_field",
    data=wind,
    u="u",
    v="v",
    uref=10,
    di=2,
    scale=1e2,
    color="0.7",
)

instantiate movie object#

?movies.movie
Init signature:
movies.movie(
    start,
    end,
    freq,
    map_generator,
    fig_dir,
    extent=None,
    title=None,
    extra=None,
    legend=None,
    **kwargs,
)
Docstring:     
Movie generator object

Parameters
----------
start: str or datetime-like
    Movie start time
end: str or datetime-like
    Movie end time
freq: str, datetime.timedelta, or DateOffset
    Movie snapshots frequency
map_generator: method
    Function generating a snapshot
fig_dir: str
    Path to directory where temporary figures will be generated
extent: tuple, optional
    Geographical extent
title: method, optional
    Method generating figures, should take time as an input
extra: list, optional
    List of methods adding extra information on figures.
    Method signatures must be e(t, self.data, fig, ax)
legend: str, list of str, optional
    List of labels that will legended
**kwargs: actual data of pandas or xarray, each kwarg must be a dict with
items `data` and `dtype`. `dtype` must be one of: "drifter", "moving", "vector_field"
"drifter" data must have an id variable
File:           ~/Code/pynsitu/pynsitu/movies.py
Type:           type
Subclasses:     
mv = movies.movie(
    start,
    end,
    dt,
    map_gen,
    "figs",
    # extent=extent, #dynamic extent
    drifters=mv_dr,
    ship=mv_ship,
    wind=mv_wd,
    # legend=True,
    legend="upper right",
)
mv.plot_snapshot(200);
_images/324afd6121adc880f7dc64eb22678db3701049de5865786908c89c8578807dba.png

generate movie figures#

mv.make_figures()

generate movie figures withs smoothing of the geographical extent#

extents = mv.dry_run()

# padd and smooth extents
new_extents = pad_smooth_extents(extents, 10)
# dev
# e = pd.DataFrame(padded_extents)
# ne = pd.DataFrame(new_extents)
# e[1].hvplot() * ne[1].hvplot()
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 405/405 [00:18<00:00, 22.04it/s]
mv.make_figures(extents=new_extents)
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 405/405 [10:12<00:00,  1.51s/it]

transform figures into mp4#

Requires ffmpeg to be installed, for example: conda install ffmpeg

movie.generate_mpg(mv.fig_dir, "movie")
movies should be ready at: /Users/aponte/Code/taos/insitu/taos2/taos2_drifters1.mp4