Skip to content

Commit 082208b

Browse files
Move homepage animation to markdown format
1 parent a3c69f4 commit 082208b

2 files changed

Lines changed: 150 additions & 245 deletions

File tree

docs/user_guide/examples/documentation_homepage_animation.ipynb

Lines changed: 0 additions & 245 deletions
This file was deleted.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# 🎓 The homepage animation
2+
3+
This notebook shows how to create the animation on the homepage of the Parcels documentation. It uses a dataset of virtual particles at the surface of the global ocean simulation that can be retrieved from the [Copernicus Marine Service](https://marine.copernicus.eu/).
4+
5+
```python
6+
import cartopy
7+
import cartopy.crs as ccrs
8+
import matplotlib.gridspec as gridspec
9+
import matplotlib.pyplot as plt
10+
import numpy as np
11+
import polars as pl
12+
import xarray as xr
13+
from matplotlib.animation import FuncAnimation, PillowWriter
14+
15+
import parcels
16+
```
17+
18+
The cell below provides the code needed to run this simulation - but because it is a time-consuming run (~20 minutes) and requires a login on the Copernicus Marine Service, we also provide the output file for download [here](https://github.com/Parcels-code/parcels-data/raw/refs/heads/main/data-parquet/copernicusmarine_globalsurface.parquet).
19+
20+
```python
21+
particle_filename = "copernicusmarine_globalsurface.parquet"
22+
23+
24+
def run_global_copernicusmarine():
25+
import copernicusmarine
26+
copernicusmarine.login()
27+
28+
ds = copernicusmarine.open_dataset(
29+
dataset_id="cmems_mod_glo_phy-cur_anfc_0.083deg_P1D-m",
30+
variables=["uo", "vo"],
31+
start_datetime="2024-01-01",
32+
end_datetime="2024-12-31",
33+
minimum_depth=0.5,
34+
maximum_depth=0.5,
35+
service="arco-geo-series",
36+
chunk_size_limit=1,
37+
)
38+
ds = ds.fillna(0)
39+
40+
ds_wrap = xr.concat(
41+
[
42+
ds,
43+
ds.isel(longitude=slice(0, 1)).assign_coords(
44+
longitude=ds.longitude.isel(longitude=slice(0, 1)) + 360
45+
),
46+
],
47+
dim="longitude",
48+
)
49+
ds = parcels.convert.copernicusmarine_to_sgrid(
50+
fields={"U": ds_wrap["uo"], "V": ds_wrap["vo"]}
51+
)
52+
fieldset = parcels.FieldSet.from_sgrid_conventions(ds)
53+
fieldset.UV.interp_method = parcels.interpolators.XFreeslip()
54+
fieldset.to_windowed_arrays()
55+
56+
lon, lat = np.meshgrid(np.arange(-179, 180, 2), np.arange(-89, 90, 2))
57+
lon = lon.flatten()
58+
lat = lat.flatten()
59+
60+
# Filter out particles that are not in the ocean (i.e. where speed is zero)
61+
u, v = fieldset.UV[np.zeros_like(lon), np.zeros_like(lat), lat, lon]
62+
speed = np.sqrt(u**2 + v**2)
63+
inocean = speed > 0
64+
65+
pset = parcels.ParticleSet(fieldset, x=lon[inocean], y=lat[inocean])
66+
67+
oufile = parcels.ParticleFile(
68+
particle_filename,
69+
outputdt=np.timedelta64(5, "D"),
70+
)
71+
72+
def AdvectionRK2_periodic(particles, fieldset): # pragma: no cover
73+
"""Advection of particles using second-order Runge-Kutta integration,
74+
keeping particles within the periodic domain [-180, 180].
75+
"""
76+
(u1, v1) = fieldset.UV[particles]
77+
x1 = particles.x + u1 * 0.5 * particles.dt
78+
x1 = ((x1 + 180) % 360) - 180
79+
y1 = particles.y + v1 * 0.5 * particles.dt
80+
(u2, v2) = fieldset.UV[
81+
particles.t + 0.5 * particles.dt, particles.z, y1, x1, particles
82+
]
83+
particles.dx += u2 * particles.dt
84+
particles.dx = ((particles.dx + particles.x + 180) % 360) - (particles.x + 180)
85+
particles.dy += v2 * particles.dt
86+
87+
pset.execute(
88+
[AdvectionRK2_periodic],
89+
dt=np.timedelta64(1, "h"),
90+
runtime=np.timedelta64(365, "D"),
91+
output_file=oufile,
92+
)
93+
94+
95+
run_global_copernicusmarine()
96+
```
97+
98+
The animation consists of two figures: the northern hemisphere and the southern hemisphere, using the [Cartopy package](https://cartopy.readthedocs.io/stable/) for map projections.
99+
100+
```python
101+
fig = plt.figure(figsize=(8, 4))
102+
gs = gridspec.GridSpec(ncols=8, nrows=4, figure=fig)
103+
104+
scat = [None, None]
105+
particles = df.filter(pl.col("t") == pl.lit(plottimes[0]))
106+
107+
for i, central_latitude in enumerate([90, -90]):
108+
ax = fig.add_subplot(
109+
gs[:, :4] if central_latitude == 90 else gs[:, 4:],
110+
projection=ccrs.NearsidePerspective(
111+
central_latitude=central_latitude,
112+
central_longitude=-30,
113+
satellite_height=15000000,
114+
),
115+
)
116+
ax.add_feature(cartopy.feature.LAND, zorder=1)
117+
ax.add_feature(cartopy.feature.OCEAN, zorder=1)
118+
ax.coastlines()
119+
scat[i] = ax.scatter(
120+
particles["x"],
121+
particles["y"],
122+
marker=".",
123+
s=25,
124+
c="#AB2200",
125+
edgecolor="white",
126+
linewidth=0.15,
127+
transform=ccrs.PlateCarree(),
128+
)
129+
130+
131+
def animate(i):
132+
particles = df.filter(pl.col("t") == pl.lit(plottimes[i]))
133+
scat[0].set_offsets(np.c_[particles["x"], particles["y"]])
134+
scat[1].set_offsets(np.c_[particles["x"], particles["y"]])
135+
return scat[0], scat[1]
136+
137+
138+
plt.rcParams["animation.html"] = "jshtml"
139+
anim = FuncAnimation(fig, animate, frames=len(plottimes), interval=150, blit=True)
140+
plt.close(fig)
141+
anim.save(
142+
particle_filename.replace(".parquet", ".gif"),
143+
writer=PillowWriter(fps=6),
144+
savefig_kwargs={"transparent": True},
145+
)
146+
```
147+
148+
The resulting animation is then
149+
150+
![gif](../../_static/homepage.gif)

0 commit comments

Comments
 (0)