DEV Community

Cover image for OSCAR 2022 sea surface velocity streamplot animation
Jordan Bell
Jordan Bell

Posted on • Updated on

OSCAR 2022 sea surface velocity streamplot animation

Sea surface currents derived from OSCAR. Dec 20, 2022

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

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

Xarray

Now we use Xarray to interact with the NetCDF file.

import xarray as xr

# Open the dataset
ds = xr.open_dataset("local_folder/oscar_vel2022.nc")
Enter fullscreen mode Exit fullscreen mode

We inspect ds:

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. ;
}
Enter fullscreen mode Exit fullscreen mode

Plate carrée projection with Cartopy

Cartopy

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()
Enter fullscreen mode Exit fullscreen mode

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

#!/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
Enter fullscreen mode Exit fullscreen mode

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.

{% youtube PGpqtkXvNVw %}
Enter fullscreen mode Exit fullscreen mode

We convert this to oscar_vel2022.gif,

ffmpeg -i ffmpeg/oscar_vel2022.mp4 -vf "fps=3,scale=1000:-1:flags=lanczos" -c:v gif ffmpeg/oscar_vel2022.gif
Enter fullscreen mode Exit fullscreen mode

which is this

OSCAR 2022 streamplot animation in plate carrée projection

Peters equal area projection

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()
Enter fullscreen mode Exit fullscreen mode

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

#!/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
Enter fullscreen mode Exit fullscreen mode

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:

ffmpeg -i oscar_vel2022_peters.mp4 -vf "fps=3,scale=1000:-1:flags=lanczos" -c:v gif oscar_vel2022_peters.gif
Enter fullscreen mode Exit fullscreen mode

which is this:

OSCAR 2022 streamplot animation in Peters equal area projection

Top comments (3)

Collapse
 
michaeltharrington profile image
Michael Tharrington

Wow, this is super cool!

Collapse
 
jordanbell2357 profile image
Jordan Bell

Thank you!

This process will work for other geospatial datasets with velocity data. We make streamlines from the velocity vector field data. This could be done for the atmosphere or other geophysical datasets from sources such as NOAA and NASA.

The heavy lifting is done by matplotlib with matplotlib.org/stable/api/_as_gen/...

The arguments I fed it were trial and error to make a nice looking plot. The 8 that occurs as an argument is the density parameter for spacing between the streamlines.

Collapse
 
jordanbell2357 profile image
Jordan Bell

Also, about the cute way of making progress bars and combining into an animation using ImageMagick and ffmpeg, the bash script is fairly general and can be modified for other uses.