OSCAR 2022 sea surface velocity streamplot animation
Tags: python, bash, tutorial, ffmpeg
Published: 2024-02-17 23:31:03

[Sea surface currents derived from OSCAR. Dec 20, 2022](https://dev.to/jordanbell2357/sea-surface-currents-derived-from-oscar-2ln1)

We use xarray, cartopy, and matplotlib to plot sea surface current derived from sea surface velocities. We create scrollbars for these plots using ImageMagick and combine these into an mp4 file using ffmpeg.

## OSCAR (Ocean Surface Current Analysis Real-time)

[OSCAR third degree resolution ocean surface currents
(OSCAR_L4_OC_third-deg). PO.DAAC](https://podaac.jpl.nasa.gov/dataset/OSCAR_L4_OC_third-deg)

> OSCAR (Ocean Surface Current Analysis Real-time) contains near-surface ocean current estimates, derived using quasi-linear and steady flow momentum equations. The horizontal velocity is directly estimated from sea surface height, surface vector wind and sea surface temperature. These data were collected from the various satellites and in situ instruments. The model formulation combines geostrophic, Ekman and Stommel shear dynamics, and a complementary term from the surface buoyancy gradient. Data are on a 1/3 degree grid with a 5 day resolution. OSCAR is generated by Earth Space Research (ESR) https://www.esr.org/research/oscar/oscar-surface-currents/. This collection contains data in 5-day files. For yearly files, see https://doi.org/10.5067/OSCAR-03D1Y

We obtain the file `oscar_vel2022.nc`. The format of this file is NetCDF. See [NetCDF Users Guide  v1.1](https://docs.unidata.ucar.edu/nug/current/)

## Xarray

Now we use [Xarray](https://docs.xarray.dev/en/stable/index.html) to interact with the NetCDF file.

```python
import xarray as xr

# Open the dataset
ds = xr.open_dataset("local_folder/oscar_vel2022.nc")
```

We inspect `ds`:

```python
ds.info()

xarray.Dataset {
dimensions:
	time = 72 ;
	year = 72 ;
	depth = 1 ;
	latitude = 481 ;
	longitude = 1201 ;

variables:
	datetime64[ns] time(time) ;
		time:long_name = Day since 1992-10-05 00:00:00 ;
	float32 year(year) ;
		year:units = time in years ;
		year:long_name = Time in fractional year ;
	float32 depth(depth) ;
		depth:units = meter ;
		depth:long_name = Depth ;
	float64 latitude(latitude) ;
		latitude:units = degrees-north ;
		latitude:long_name = Latitude ;
	float64 longitude(longitude) ;
		longitude:units = degrees-east ;
		longitude:long_name = Longitude ;
	float64 u(time, depth, latitude, longitude) ;
		u:units = meter/sec ;
		u:long_name = Ocean Surface Zonal Currents ;
	float64 v(time, depth, latitude, longitude) ;
		v:units = meter/sec ;
		v:long_name = Ocean Surface Meridional Currents ;
	float64 um(time, depth, latitude, longitude) ;
		um:units = meter/sec ;
		um:long_name = Ocean Surface Zonal Currents Maximum Mask ;
	float64 vm(time, depth, latitude, longitude) ;
		vm:units = meter/sec ;
		vm:long_name = Ocean Surface Meridional Currents Maximum Mask ;

// global attributes:
	:VARIABLE = Ocean Surface Currents ;
	:DATATYPE = 1/72 YEAR Interval ;
	:DATASUBTYPE = unfiltered ;
	:GEORANGE = 20 to 420 -80 to 80 ;
	:PERIOD = Jan.01,2022 to Dec.26,2022 ;
	:year = 2022 ;
	:description = OSCAR Third Degree Sea Surface Velocity ;
	:CREATION_DATE = 02:21 06-Feb-2023 ;
	:version = 2009.0 ;
	:source = Gary Lagerloef, ESR (lager@esr.org) and Kathleen Dohan, ESR (kdohan@esr.org) ;
	:contact = Kathleen Dohan (kdohan@esr.org) or John T. Gunn (gunn@esr.org) ;
	:company = Earth & Space Research, Seattle, WA ;
	:reference = Bonjean F. and G.S.E. Lagerloef, 2002 ,Diagnostic model and analysis of the surface currents in the tropical Pacific ocean, J. Phys. Oceanogr., 32, 2,938-2,954 ;
	:note1 = Maximum Mask velocity is the geostrophic component at all points + any concurrent Ekman and buoyancy components ;
	:note2 = Longitude extends from 20 E to 420 E to avoid a break in major ocean basins. Data repeats in overlap region. ;
}
```

## Plate carrée projection with Cartopy

[Cartopy](https://scitools.org.uk/cartopy/docs/latest/)

```python
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import os

dec = 2 # decimation

# Create a directory for the images if it doesn't exist
output_dir = "oscar_images"
os.makedirs(output_dir, exist_ok=True)

for t in range(len(ds.time)):
    plt.figure(figsize=(18, 9))
    ax = plt.axes(projection=ccrs.PlateCarree()) # plate carrée projection
    
    lon = ds.longitude.values[::dec]
    lon[lon > 180] = lon[lon > 180] - 360
    
    plt.streamplot(
        lon,
        ds.latitude.values[::dec],
        ds.u.values[t, 0, ::dec, ::dec],
        ds.v.values[t, 0, ::dec, ::dec],
        8,
        transform = ccrs.PlateCarree()
    )
    
    ax.coastlines()

    # Create a title using the time dimension
    plt.title(f'Sea surface currents derived from oscar_vel2022.nc, Time: {ds.time.values[t]}')

    # Save the figure with a filename based on the time step
    plt.savefig(f"{output_dir}/oscar_vel2022_t{t}.png", dpi=150)

    # Close the figure to free up memory
    plt.close()
```

We now use ImageMagick convert and ffmpeg to create scrollbars and combine these into an animation.

```bash
#!/bin/bash

# Define total number of time steps
N=72

# Define output directory
output_dir="ffmpeg"

# Create output directory if it doesn't exist
mkdir -p $output_dir

# Define width and height
width=2700
height=1350

# Define progress bar height
progress_height=80

# Create N images with increasing filled progress bars
for i in $(seq 1 $N); do
    progress=$((i*width/N))  # proportionally increase size
    convert -size ${width}x${progress_height} xc:grey50 -fill "rgb(0,0,0)" -draw "rectangle 0,0 $progress,${progress_height}" ${output_dir}/progress_${i}.png
done

# Overlay progress bar onto each image, create new image
for i in $(seq 1 $N); do
    # Calculate progress bar position
    y_offset=$(( height-progress_height-10 ))  # place the progress bar at the bottom, with 10 pixels padding

    # Overlay progress bar and save as new image
    convert oscar_vel2022_t${i}.png ${output_dir}/progress_${i}.png -geometry +0+${y_offset} -composite ${output_dir}/oscar_vel2022_progress_t${i}.png
done

# Create video using ffmpeg
ffmpeg -framerate 5 -i "${output_dir}/oscar_vel2022_progress_t%d.png" -c:v libx264 -r 30 -pix_fmt yuv420p ${output_dir}/oscar_vel2022.mp4

# Clean up progress bar and composite images
rm ${output_dir}/progress_*.png
rm ${output_dir}/oscar_vel2022_progress_t*.png
```

We have created `oscar_vel2022.mp4`. This is the video file hosted on YouTube at https://youtu.be/PGpqtkXvNVw. We embed the YouTube video using the following Liquid tag.

```liquid
{% youtube PGpqtkXvNVw %}
```

{% youtube PGpqtkXvNVw %}

We convert this to `oscar_vel2022.gif`,

```bash
ffmpeg -i ffmpeg/oscar_vel2022.mp4 -vf "fps=3,scale=1000:-1:flags=lanczos" -c:v gif ffmpeg/oscar_vel2022.gif
```

which is this


![OSCAR 2022 streamplot animation in plate carrée projection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3cnfqjhpr6n120dldsd.gif)

## Peters equal area projection

```python
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import os

dec = 2 # decimation

# Create a directory for the images if it doesn't exist
output_dir = "output_Peters"
os.makedirs(output_dir, exist_ok=True)

for t in range(len(ds.time)):
    plt.figure(figsize=(18, 9))
    ax = plt.axes(projection=ccrs.EqualEarth()) # Equal Earth (similar to Peters) projection
    
    lon = ds.longitude.values[::dec]
    lon[lon > 180] = lon[lon > 180] - 360
    
    plt.streamplot(
        lon,
        ds.latitude.values[::dec],
        ds.u.values[t, 0, ::dec, ::dec],
        ds.v.values[t, 0, ::dec, ::dec],
        8,
        transform = ccrs.PlateCarree()
    )
    
    ax.coastlines()

    # Create a title using the time dimension
    plt.title(f'Sea surface currents derived from oscar_vel2022.nc, Time: {ds.time.values[t]}')

    # Save the figure with a filename based on the time step
    plt.savefig(f"{output_dir}/oscar_vel2022_t{t}.png", dpi=150)

    # Close the figure to free up memory
    plt.close()
```

We modify these images using ImageMagick convert and combine the modified images into an animation using ffmpeg.

```bash
#!/bin/bash

# Define total number of time steps
N=72

# Define output directory
output_dir="ffmpeg_Peters"

# Create output directory if it doesn't exist
mkdir -p $output_dir

# Define width and height
width=2700
height=1350

# Define progress bar height
progress_height=80

# Create N images with increasing filled progress bars
for i in $(seq 1 $N); do
    progress=$((i*width/N))  # proportionally increase size
    convert -size ${width}x${progress_height} xc:grey50 -fill "rgb(0,0,0)" -draw "rectangle 0,0 $progress,${progress_height}" ${output_dir}/progress_${i}.png
done

# Overlay progress bar onto each image, create new image
for i in $(seq 1 $N); do
    # Calculate progress bar position
    y_offset=$(( height-progress_height-10 ))  # place the progress bar at the bottom, with 10 pixels padding

    # Overlay progress bar and save as new image
    convert oscar_vel2022_t${i}.png ${output_dir}/progress_${i}.png -geometry +0+${y_offset} -composite ${output_dir}/oscar_vel2022_progress_t${i}.png
done

# Create video using ffmpeg
ffmpeg -framerate 5 -i "${output_dir}/oscar_vel2022_progress_t%d.png" -c:v libx264 -r 30 -pix_fmt yuv420p oscar_vel2022.mp4

# Clean up progress bar and composite images
rm ${output_dir}/progress_*.png
rm ${output_dir}/oscar_vel2022_progress_t*.png
```

This creates a file `oscar_vel2022_peters.mp4`. The video file is hosted on YouTube at https://youtu.be/xy0P8l4690o.

We use ffmpeg to convert `oscar_vel2022_peters.mp4` to `oscar_vel2022_peters.gif`:

```bash
ffmpeg -i oscar_vel2022_peters.mp4 -vf "fps=3,scale=1000:-1:flags=lanczos" -c:v gif oscar_vel2022_peters.gif
```

which is this:

![OSCAR 2022 streamplot animation in Peters equal area projection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6oved0zsc424usy3li2m.gif)