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")
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. ;
}
Plate carrée projection with 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()
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
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 %}
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
which is this
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()
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
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
which is this:
Top comments (3)
Wow, this is super cool!
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.
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.