From cf99eed4f21d57d783281397dae93f3adbeca542 Mon Sep 17 00:00:00 2001
From: j-atkins <106238905+j-atkins@users.noreply.github.com>
Date: Wed, 5 Nov 2025 14:54:05 +0100
Subject: [PATCH 01/26] add plotting materials to UU ocean of future directory
---
.../UU_ocean_of_future/CTD_transects.ipynb | 505 ++++++++++++++++++
.../UU_ocean_of_future/plot_3D.py | 250 +++++++++
.../UU_ocean_of_future/plot_slider.py | 279 ++++++++++
.../UU_ocean_of_future/timeseries.py | 177 ++++++
4 files changed, 1211 insertions(+)
create mode 100644 docs/user-guide/teacher-content/UU_ocean_of_future/CTD_transects.ipynb
create mode 100644 docs/user-guide/teacher-content/UU_ocean_of_future/plot_3D.py
create mode 100644 docs/user-guide/teacher-content/UU_ocean_of_future/plot_slider.py
create mode 100644 docs/user-guide/teacher-content/UU_ocean_of_future/timeseries.py
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/CTD_transects.ipynb b/docs/user-guide/teacher-content/UU_ocean_of_future/CTD_transects.ipynb
new file mode 100644
index 00000000..570c5501
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU_ocean_of_future/CTD_transects.ipynb
@@ -0,0 +1,505 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "cca80169",
+ "metadata": {},
+ "source": [
+ "# CTD Transect Plotting\n",
+ "\n",
+ "This notebook demonstrates a simple plotting exercise for CTD data across a transect, using the output of a VirtualShip expedition. There are example plots embedded at the end, but these will ultimately be replaced by your own versions as you work through the notebook.\n",
+ "\n",
+ "We can plot physical (temperature, salinity) or biogeochemical data (oxygen, chlorophyll, primary production, phytoplankton, nutrients, pH) as measured by the VirtualShip `CTD` and `CTD_BGC` instruments, respectively.\n",
+ "\n",
+ "The plot(s) we will produce are simple plots which follow the trajectory of the expedition as a function of distance from the first waypoint, and are intended to be a starting point for your analysis. \n",
+ "\n",
+ "
\n",
+ "Note: This notebook assumes that each waypoint in the expedition is further from the start than the last waypoint. The code will still work if not, but the resultant plots might not be very intuitive.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aad20bd7",
+ "metadata": {},
+ "source": [
+ "## Set up\n",
+ "\n",
+ "#### Imports\n",
+ "\n",
+ "The first step is to import the Python packages required for post-processing the data and plotting. \n",
+ "\n",
+ "
\n",
+ "Tip: You may need to set the Kernel to the relevant (Conda) environment in the top right of this notebook to access the required packages! \n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "c7f9f2ee",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import cmocean.cm as cmo\n",
+ "import matplotlib.colors as mcolors\n",
+ "import matplotlib.patches as mpatches\n",
+ "import numpy as np\n",
+ "import xarray as xr\n",
+ "from matplotlib import pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4f387780",
+ "metadata": {},
+ "source": [
+ "\n",
+ "#### Data directory\n",
+ "\n",
+ "Next, you should set `data_dir` to be the path to your expedition results in the code block below. You should replace `\"/path/to/EXPEDITION/results/\"` with the path for your machine.\n",
+ "\n",
+ "
\n",
+ "Tip: You can get the path to your expedition results by navigating to the `results` folder in Terminal (using `cd`) and then using the `pwd` command. This will print your working directory which you can copy to the `data_dir` variable in this notebook. Don't forget to keep it as a string (in \"quotation\" marks)!\n",
+ "
\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "cf497101",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "data_dir = \"GRP1993/results/\" # set this to be where your expedition output data is located on your (virtual) machine"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a499ebe2",
+ "metadata": {},
+ "source": [
+ "#### Variable choice\n",
+ "\n",
+ "You should now consider which variable from your CTD casts you would like to plot. Which ones are available to you will depend on whether you have used the `CTD` (physical variables) or `CTD_BGC` (biogeochemical) instrument, or both. Below is a list of all valid variable choices for both instruments...\n",
+ "\n",
+ "`CTD` (physical):\n",
+ "- \"temperature\"\n",
+ "- \"salinity\"\n",
+ "\n",
+ "`CTD_BGC` (biogeochemical):\n",
+ "- \"oxygen\"\n",
+ "- \"nitrate\"\n",
+ "- \"phosphate\"\n",
+ "- \"ph\"\n",
+ "- \"phytoplankton\"\n",
+ "- \"primary_production\"\n",
+ "- \"chlorophyll\"\n",
+ "\n",
+ "Copy one of the above to `plot_variable` below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "8de8b4ae",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plot_variable = \"temperature\" # change this to your chosen variable"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a05fad14",
+ "metadata": {},
+ "source": [
+ "\n",
+ "We also define the `VARIABLES` dictionary here, which we use to store some parameters for the plots related to each variable choice (e.g. labels, what units each is in, and which colour map we should use for the plots).\n",
+ "\n",
+ "
\n",
+ "Tip: You don't need to change anything here, but should you wish to change the colour scheme (`cmap`) for any CTD variable you can do so. At the moment it's set to use relevant cmaps from the cmocean Python package, which has developed specialist colour schemes for oceanographic data applications.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "b32d2730",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "VARIABLES = {\n",
+ " \"temperature\": {\n",
+ " \"cmap\": cmo.thermal,\n",
+ " \"label\": \"Temperature (°C)\",\n",
+ " \"ds_name\": \"temperature\",\n",
+ " },\n",
+ " \"salinity\": {\n",
+ " \"cmap\": cmo.haline,\n",
+ " \"label\": \"Salinity (psu)\",\n",
+ " \"ds_name\": \"salinity\",\n",
+ " },\n",
+ " \"oxygen\": {\n",
+ " \"cmap\": cmo.oxy,\n",
+ " \"label\": r\"Dissolved oxygen (mmol m$^{-3}$)\",\n",
+ " \"ds_name\": \"o2\",\n",
+ " },\n",
+ " \"nitrate\": {\n",
+ " \"cmap\": cmo.matter,\n",
+ " \"label\": r\"Nitrate (mmol m$^{-3}$)\",\n",
+ " \"ds_name\": \"no3\",\n",
+ " },\n",
+ " \"phosphate\": {\n",
+ " \"cmap\": cmo.matter,\n",
+ " \"label\": r\"Phosphate (mmol m$^{-3}$)\",\n",
+ " \"ds_name\": \"po4\",\n",
+ " },\n",
+ " \"ph\": {\n",
+ " \"cmap\": cmo.balance,\n",
+ " \"label\": \"pH\",\n",
+ " \"ds_name\": \"ph\",\n",
+ " },\n",
+ " \"phytoplankton\": {\n",
+ " \"cmap\": cmo.algae,\n",
+ " \"label\": r\"Total phytoplankton (mmol m$^{-3}$)\",\n",
+ " \"ds_name\": \"phyc\",\n",
+ " },\n",
+ " \"primary_production\": {\n",
+ " \"cmap\": cmo.matter,\n",
+ " \"label\": r\"Total primary production of phytoplankton (mg m$^{-3}$ day$^{-1}$)\",\n",
+ " \"ds_name\": \"nppv\",\n",
+ " },\n",
+ " \"chlorophyll\": {\n",
+ " \"cmap\": cmo.algae,\n",
+ " \"label\": r\"Chlorophyll (mg m$^{-3}$)\",\n",
+ " \"ds_name\": \"chl\",\n",
+ " },\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6f9a5afb",
+ "metadata": {},
+ "source": [
+ "## Load data\n",
+ "\n",
+ "We are now ready to read in the data. You can carry on executing the next cells without making changes to the code..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "13f4664b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# load CTD data\n",
+ "filename = (\n",
+ " \"ctd.zarr\" if plot_variable in [\"temperature\", \"salinity\"] else \"ctd_bgc.zarr\"\n",
+ ")\n",
+ "ctd_ds = xr.open_dataset(f\"{data_dir}/{filename}\")\n",
+ "if ctd_ds[\"trajectory\"].size <= 1:\n",
+ " raise ValueError(\"Number of waypoints must be > 1\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a8201b14",
+ "metadata": {},
+ "source": [
+ "## Data post-processing\n",
+ "\n",
+ "Before we can continue, we need to do some post-processing to get it ready for plotting. Below are various helper functions which perform tasks such as calculating the distance of each waypoint from the start, capturing only the downcasts of the CTD casts, as well as some other utility methods. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "id": "785b2b35",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# utility functions\n",
+ "\n",
+ "\n",
+ "def haversine(lon1, lat1, lon2, lat2):\n",
+ " \"\"\"Great-circle distance (meters) between two points.\"\"\"\n",
+ " lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])\n",
+ " dlon, dlat = lon2 - lon1, lat2 - lat1\n",
+ " a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2\n",
+ " c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))\n",
+ " return 6371000 * c\n",
+ "\n",
+ "\n",
+ "def distance_from_start(ds):\n",
+ " \"\"\"Add 'distance' variable: meters from first waypoint.\"\"\"\n",
+ " lon0, lat0 = (\n",
+ " ds.isel(trajectory=0)[\"lon\"].values[0],\n",
+ " ds.isel(trajectory=0)[\"lat\"].values[0],\n",
+ " )\n",
+ " d = np.zeros_like(ds[\"lon\"].values, dtype=float)\n",
+ " for ob, (lon, lat) in enumerate(zip(ds[\"lon\"], ds[\"lat\"], strict=False)):\n",
+ " d[ob] = haversine(lon, lat, lon0, lat0)\n",
+ " ds[\"distance\"] = xr.DataArray(\n",
+ " d,\n",
+ " dims=ds[\"lon\"].dims,\n",
+ " attrs={\"long_name\": \"distance from first waypoint\", \"units\": \"m\"},\n",
+ " )\n",
+ " return ds\n",
+ "\n",
+ "\n",
+ "def descent_only(ds, variable):\n",
+ " \"\"\"Extract descending CTD data (downcast), pad with NaNs for alignment.\"\"\"\n",
+ " min_z_idx = ds[\"z\"].argmin(\"obs\")\n",
+ " da_clean = []\n",
+ " for i, traj in enumerate(ds[\"trajectory\"].values):\n",
+ " idx = min_z_idx.sel(trajectory=traj).item()\n",
+ " descent_vals = ds[variable][\n",
+ " i, : idx + 1\n",
+ " ] # take values from surface to min_z_idx (inclusive)\n",
+ " da_clean.append(descent_vals)\n",
+ " max_len = max(len(arr[~np.isnan(arr)]) for arr in da_clean)\n",
+ " da_padded = np.full((ds[\"trajectory\"].size, max_len), np.nan)\n",
+ " for i, arr in enumerate(da_clean):\n",
+ " da_dropna = arr[~np.isnan(arr)]\n",
+ " da_padded[i, : len(da_dropna)] = da_dropna\n",
+ " return xr.DataArray(\n",
+ " da_padded,\n",
+ " dims=[\"trajectory\", \"obs\"],\n",
+ " coords={\"trajectory\": ds[\"trajectory\"], \"obs\": np.arange(max_len)},\n",
+ " )\n",
+ "\n",
+ "\n",
+ "def build_masked_array(data_up, profile_indices, n_profiles):\n",
+ " arr = np.full((n_profiles, data_up.shape[1]), np.nan)\n",
+ " for i, idx in enumerate(profile_indices):\n",
+ " if idx is not None:\n",
+ " arr[i, :] = data_up.values[idx, :]\n",
+ " return arr\n",
+ "\n",
+ "\n",
+ "def get_profile_indices(distance_1d):\n",
+ " \"\"\"\n",
+ " Returns regular distance bins and profile indices for CTD transect plotting.\n",
+ "\n",
+ " Bin size is set to one order of magnitude lower than max distance.\n",
+ " \"\"\"\n",
+ " dist_min, dist_max = float(distance_1d.min()), float(distance_1d.max())\n",
+ " if dist_max > 1e6:\n",
+ " dist_step = 1e5\n",
+ " elif dist_max > 1e5:\n",
+ " dist_step = 1e4\n",
+ " elif dist_max > 1e4:\n",
+ " dist_step = 1e3\n",
+ " else:\n",
+ " dist_step = 1e2 # fallback for very short transects\n",
+ "\n",
+ " distance_regular = np.arange(dist_min, dist_max + dist_step, dist_step)\n",
+ " threshold = dist_step / 2\n",
+ " profile_indices = [\n",
+ " np.argmin(np.abs(distance_1d.values - d))\n",
+ " if np.min(np.abs(distance_1d.values - d)) < threshold\n",
+ " else None\n",
+ " for d in distance_regular\n",
+ " ]\n",
+ " return profile_indices, distance_regular"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2bdf98e6",
+ "metadata": {},
+ "source": [
+ "\n",
+ "Now we will execute the utility functions, plus define some extra useful arrays to be used for the plotting..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "f59824a1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# add distance from start\n",
+ "ctd_distance = distance_from_start(ctd_ds)\n",
+ "\n",
+ "# exract descent-only data\n",
+ "z_up = descent_only(ctd_distance, \"z\")\n",
+ "d_up = descent_only(ctd_distance, \"distance\")\n",
+ "var_up = descent_only(ctd_distance, VARIABLES[plot_variable][\"ds_name\"])\n",
+ "\n",
+ "# 1d array of depth dimension (from deepest trajectory)\n",
+ "traj_idx, obs_idx = np.where(z_up == np.nanmin(z_up))\n",
+ "z1d = z_up.values[traj_idx[0], :]\n",
+ "\n",
+ "# distance as 1d array\n",
+ "distance_1d = d_up.isel(obs=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17745cf1",
+ "metadata": {},
+ "source": [
+ "## Plotting\n",
+ "\n",
+ "
\n",
+ "Note: The plots produced next are a starting point for your analysis. You are encouraged to make adjustments, for example axis limits and scaling if the defaults not best suited to your specific data. Use your preferred AI coding assistant for help!\n",
+ "
\n",
+ "\n",
+ "We are now ready to plot our transect data. We will use distance from the first waypoint/CTD cast for the x-axis, and water column depth for the y-axis. The data for the chosen variable will then be plotted according to the colour map. The CTD casts are likely to be different depths because some parts of the ocean are of course shallower than others.\n",
+ "\n",
+ "There are a few extra steps below which arrange the CTD casts into regular distance bins, so as to clearly demonstrate where along the transect we made CTD casts and indeed where there are gaps.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "id": "ce83c3b9",
+ "metadata": {
+ "tags": [
+ "test"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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Soi+/PJLffDBB4qK4jxXAAAAIJQ5ecrcs88+q4KCAk2YMEGvvPKKJKlTp06aOXOm1q5d61csvzKbSy+9VH/729/8egMAAAAAaEvz5s3TypUrNWfOHF+xpn///vrss8/8juVXhWjhwoU677zzNGDAAKWnpzdpe/jhh/1+83BnbNoJzsjYFhsAAACRwclT5vbt26f+/ftLklwul+//H/3YH35ViO666y7961//ksfjUWVlZZMLAAAAAIIhKytLr732WpN7S5cu1eDBg/2O5VeF6PXXX9fnn3+u7t27+/1GAAAAAILHa9q7B/aZP3++LrnkEi1evFgHDx7UuHHjtGrVKq1YscLvWH4lRL169VJMTIzfb+JcHpviem2MDQAAAIS3wYMHa9u2bVq0aJG6d++unj17av78+crIyPA7ll8J0eTJk3XNNdfoF7/4hdLS0pq0DRo0yO83BwAAAGAPp64h8ng86tGjh0pLS3XvvfcGHM+vhCg/P1+S9M477zS573K55PFQ0QAAAABgrw4dOiguLk719fX63ve+F3A8vzZV8Hq9zV4kQwAAAEBo8RqXbVd7u//++3XzzTdr69at2r9/v6qrq32Xv/yqEAEAAABAe/vpT38qSSoqKvJttW2MOaGZa61WiMaOHatPP/30uK/59NNPNXbsWL/eGAAAAIB9jFy2Xe2ttLTUd/3zn//UP//5T9/H/mq1QnTFFVdo9OjR6tq1qy644AL17dtXCQkJqq6u1t///netWrVK+/bt09y5c0/oYcKaXYenGmNfbAAAACDM9e7du81itZoQXXfddRozZozefPNNLV26VAsXLlRlZaWSk5Pldrs1c+ZMXXbZZYqK8ms5EgAAAAAbOfmf149OmWvOc88951csS2uIXC6XRo0apVGjRvkVHAAAAED7MCGw+YFdEhMTm3xeVlamN99884SW8YRtWefZZ5/VD3/4Q8XHx6tv375atGhRk/Z169YpKytLsbGxcrvdWr9+vV/tAAAAAELTI4880uT605/+pDfeeEM1NTV+xwrLhGjz5s2aNGmSnnnmGVVXV2vBggWaOHGib/OHiooK5eTkKD8/X5WVlZo8ebJycnK0f/9+S+0AAABAuPPKZdsVikaMGKHly5f7/XVhmRCVlpaqT58+Ov/88+VyuXThhReqV69evoSoqKhImZmZysvLU3R0tPLy8pSenq6ioiJL7QAAAABC13fPHaqurtaePXs0b948paen+x0rLBOiH//4x4qPj9ef//xneb1erVixQpWVlRo2bJgkacuWLXK73U2+xu12a8uWLZbaAQAAgHBnjH1Xe0tKSlJycrLvysjI0NNPP62FCxf6HSvkDmZtaGg47mFK0dHRio2N1fXXX6/LL79cDQ0N6tChg55//nl1795dklRbW6ukpKQmX5eUlOSbU9hae3MKCgo0e/bsJvfGXjrMjyezbnC/k2yJK0nl5eW2xQ5XBw4caO8uRBTGO7gY7+BivIOHsQ4uxhuhprS0tMnncXFx6tq16wnF8ish2rZtm26//XZ99NFHqq2tlXTiJ8K2JDc397hz/0pLS/XOO+/ooYce0t/+9jcNHDhQW7du1ejRo5WcnKxLL71UcXFxqqioaPJ1VVVV6tatmyS12t6cgoICFRQU+D53uVx6+a11J/CE1tgV+/nf32dL3HCXmpra3l2IKIx3cDHewcV4Bw9jHVyMd/gJ1bU+beHBBx/UU089dcz9/Px8Pfnkk37F8mvK3E033aRTTz1Vb731ljZt2qRNmzZp8+bN2rRpk19vejzLli2TMabFq0+fPtq8ebMuvfRSZWVlKSoqSllZWbr44ot9idSgQYNUUlLSJG5JSYkGDhxoqd0qI69Nl7EtNgAAABDuXnzxxWbvv/zyy37H8qtCtH37dm3cuLHdD2E955xz9Mtf/lLbtm3T6aefrm3btmnFihW+KW25ubm69957VVhYqPHjx2vRokUqKytTbm6upXYAAAAg3DnxHKKlS5dKkjwej4qLi2W+s6DpH//4xzHnE1nhV0I0bNgwffLJJxo0aJDfb9SWrr/+eu3cuVOjR4/Wv/71L3Xt2lU//elPfSfWpqSkqLi4WJMmTVJ+fr5++MMfqri4WMnJyZbaAQAAAISeO++8U5JUV1enO+64w3c/KipKaWlpevzxx/2O2WpC9N2ggwcP1qhRozR+/PhjtrT7boeC4b777tN997W8HiY7O/u4u8a11g4AAACEMyculji6mcK1116rV155pU1itpoQ/efZPKeccorWr1/f5J7L5Qp6QgQAAACgZU6cMndUWyVDkoWEaPXq1W32ZgAAAAAQqMOHD+vhhx/WmjVrVF5e3mQtkb8bvvm1O0JLu7D95yGnAAAAANqX18arvU2ZMkUvvviiLrvsMm3fvl033XSTDh48qCuuuMLvWH4lRF999VWz93fu3On3GwMAAADAiViyZImWL1+uO++8Ux07dtSdd96poqIirVmzxu9YlnaZmzJliiSpoaHB9/FR//znP/WDH/zA7zd2BGNXfmxsjA0AAIBI4OQ1RAcOHFCfPn0kSd/73vdUV1enfv366aOPPvI7lqWEqLKyUpLk9Xp9H0tHtrfr37+/Hn74Yb/fGAAAAABOxKmnnqqPP/5YWVlZGjhwoB555BElJSUpNTXV71iWEqLnn39e0pFtt2+//Xa/3wQAAABAcDl5vtHcuXNVW1srSfrtb3+rsWPHqqamRs8884zfsfw6mPX2229XaWmpXn75Ze3evVuZmZm67rrrInfKHAAAAICg8ng8qq+v1/DhwyVJQ4YM0RdffHHC8fzaVGHJkiXq37+/3n//fXm9Xq1bt04DBgw45qwiAAAAAO3LyGXb1Z46dOig6667TtHR0W0Sz68K0bRp0/Tqq68qJyfHd2/58uW6++67lZub2yYdAgAAAIDj+dGPfqSPPvpIZ555ZsCx/EqI9uzZo8suu6zJvZEjR2rcuHEBdyQs2bUTnGGXOQAAAATG6+Bd5txut0aNGqVx48apV69eior698S3O+64w69YfiVE11xzjZ577jlNnDjRd++FF17Qtdde69ebAgAAAMCJ2rhxo/r166fNmzdr8+bNvvsul8vehOjbb7/VpEmT9Oijj6p3797asWOHPv/8c40cOVJXXXWV73Wvv/66X50AAAAA0LZMe3fARqtXr26zWH4lREOGDNGQIUN8nw8dOrTNOgIAAACg7XidnBHpyFmpb775pr755htNnTpV33zzjbxer3r06OFXHL8SolmzZvkVHAAAAADa2vr16zV69Gj17dtXH3/8saZOnarPPvtMjz/+uN544w2/Yvm17bZ0pDyVl5en0aNHS5I+/PDDNi1ZAQAAAAicU7fdlqS77rpLzz77rN5//3117HikxnPOOedo48aNfsfyKyF69tlnNX78eKWlpem9996TJHXq1EkzZ870+40BAAAA4ER8/vnnuvLKKyUd2UhBkmJjY3X48GG/Y/mVEM2bN08rV67UnDlzfFvb9e/fX5999pnfbwwAAADAPl5j39XeevXqpY8//rjJvU2bNumkk07yO5ZfCdG+ffvUv39/Sf/OxFwul+9jAAAAALDb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SrfeeqvuueceX0Vo3759+t///d+Anv+GG27Q3Llz9fXXX6u2tlZTpkzRRRdd5JsuF4jt27frz3/+sw4dOqTOnTsrLi5OHTsemcWdlpamsrIyHTp0yPf66upqxcfHKyEhQV9//bV+//vft/oeaWlp+uc//3nc14wePVpr166Vx+Npcv973/ue3njjDR0+fFg/+clPVF9ffwJPecSqVas0atSoE/56AIC9SIgA4DumTZvmO4foqquuUnp6uj788MNm17d4vV499thj6tmzpxITE7VgwQL93//9n6KionTaaafpZz/7mfr166ekpCS9//77mj17tr788kslJydr2LBhuvTSS/3q24MPPqgLL7xQF110kRISEnTNNdf41jb99re/1TnnnKMLLrhA8fHxOvPMM7Vy5cqAxuK+++7Tj3/8Y51zzjnq06ePGhoa9OKLLwYU86j6+nrNmDFDaWlp6tq1q1atWqUXXnhBknTBBRfo7LPPVmZmppKSkrRz5049/PDDWrZsmRISEnTFFVfoJz/5SavvMXHiRO3evVvJyckaNGhQs68ZOHCgTj31VL311lvHtEVHR2vJkiUyxig3N1eHDx/2+zm9Xq9eeuklTZ482e+vBQAEh8tYmasBAIBDrV+/Xnfffbf+9re/tXnsxYsXa/ny5XrppZfaPDYAoG2QEAEAAACIWEyZAwAAABCxSIgAAAAARCwSIgAAAAARi4QIAAAAQMQiIQIAAAAQsUiIAAAAAEQsEiIAAAAAEYuECAAAAEDEIiECAAAAELFIiAAAAABErP8HFU7kuuuBRXQAAAAASUVORK5CYII=",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# regularised transect\n",
+ "profile_indices, distance_regular = get_profile_indices(distance_1d)\n",
+ "var_masked = build_masked_array(var_up, profile_indices, len(distance_regular))\n",
+ "\n",
+ "xticks_reg = np.linspace(\n",
+ " float(distance_regular.min()),\n",
+ " float(distance_regular.max()),\n",
+ " len(distance_regular),\n",
+ ")\n",
+ "\n",
+ "# plot regularised transect\n",
+ "fig, ax = plt.subplots(figsize=(10, 6), dpi=90)\n",
+ "\n",
+ "ax.grid(True, which=\"both\", color=\"lightgrey\", linestyle=\"-\", linewidth=0.7, alpha=0.5)\n",
+ "\n",
+ "mesh = ax.pcolormesh(\n",
+ " distance_regular / 1000, # distance in km\n",
+ " z1d,\n",
+ " var_masked.T,\n",
+ " cmap=VARIABLES[plot_variable][\"cmap\"],\n",
+ ")\n",
+ "\n",
+ "ax.set_ylabel(\"Depth (m)\")\n",
+ "ax.set_xlabel(\"Distance from start (km)\")\n",
+ "\n",
+ "# ax.set_ylim(-600,)\n",
+ "\n",
+ "plt.colorbar(mesh, ax=ax, label=VARIABLES[plot_variable][\"label\"])\n",
+ "plt.tight_layout()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "68c5c8c2",
+ "metadata": {},
+ "source": [
+ "In the plot above, we can see that there are gaps in the transects where no CTD casts have been made. After all, it's impossible to take measurements at every point across the transect! There will always be gaps when making tens of deployments across transects 1000s of kms long 🙃 This makes expedition/sampling site planning all the more important...\n",
+ "\n",
+ "We can also also plot a 'filled' version without the distance bins, to give an alternative view of the evolution across the transect which is not dominated by gaps and white space. This time we will also add a 'sea bed' to the plot.\n",
+ "\n",
+ "
\n",
+ "Note: It is important to remember that the gaps do actually exist in reality and this is a caveat which must be considered when interpreting the transect derived from CTD casts. Indeed, if you look at the x-axis of the plot below you will see that the deployments are not necessarily regularly spaced and some gaps are larger than others.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "id": "fcf8a137",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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1axZUzVr1tTatWvVuHFj7d27VzabYw+fFjnhO3TokCTXTBUKAAAAAIUxct1Yu0uuUXcFdevWTV9++aWaNWumhx56SIMHD9b8+fO1e/duDRo0yKGYxV54/cKpQv/44w9JIvkDAAAAgMs0Y8YM+3/37dtXMTExWr9+vWrXrq34+HiHYhZ70pacnBxNmjRJERERCgsLU1hYmCIiIjRp0iSHZ44BAAAAgMIYWVy6eYKzZ8/qjjvu0P79++37brnlFo0aNcrhZE9yoMI3bNgwJSQkaNq0aYqNjZUkrV+/Xk899ZQOHz6s119/3eHOAAAAAEBp5O/vr6SkJIfH6hWm2Anfe++9p08++URxcXH2fQ0bNlTNmjUVHx9PwgcAAADAqWzGIpuLJkZ0VVxHPPDAA5ozZ06eRzsvV7ETvquuukqRkZH59oeHh6tixYpO6VSJxiydAAAAABxw6NAhLVu2TJ999pkaN26soKCgPO0XLpVXVMUewzd16lT961//0t69e+379u7dq5EjRzq0LgQAAAAAXIxx8eYp/Pz81LNnT918880qX768fH1982yOKHaFb+zYscrIyNDVV1+t8uXLy2Kx6OTJkwoICNCePXs0fvx4+7G//PKLQ50CAAAAgNJmwYIFTo9Z7ITv6aefdnonAAAAAKAwNnNuc1VsT2KM0bp163TgwAHFx8crODhYf/zxh4KCghQQEFDseMVO+Pr27VvskwAAAAAALu7nn3/WP/7xDx04cEBnzpzRnj17FBwcrAkTJignJ0dz5swpdsxij+GTzj2qOXXqVD3wwANKT0+XJK1atSrPuD4AAAAAcIbSsA6fJD300EOKjY3VH3/8ocDAQPv+nj176uuvv3YoZrETvtWrV+vaa6/V6tWr9Z///Ed//vmnJCk5OVljx451qBMAAAAAUNp99913Gj16tPz8/PLsr169un799VeHYhY74Xvsscf0zDPP6KuvvpK/v799f1xcnNatW+dQJwAAAACgMOfH8Llq8xR+fn46depUvv179uxRWFiYQzGLnfD9+OOP6tq1a779lSpVUkZGhkOdAAAAAIDS7s4779TYsWN14sQJSZLFYtGOHTs0atQo9e7d26GYxU74oqKiChyrt2bNGsXExDjUCW9iZGO7YAMAAAAuV2kZw/f8888rIiJCkZGRyszMVKNGjdSoUSNdffXVmjJlikMx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IyN16623atu2bZKk9evX64YbblD58uXt74mLi7P/e7NarcrNzVVgYKC9PSAgQL6+vkpKSrqyF+OgQ4cOKTExUf369SuwvaDPUnHeXxK1atVKy5Yt07Fjx5Sbm6v58+erSpUqatCgQZF+Fl0oKytLb731lurXr6/w8PArfCXFc6nPe1F+ByUkJKh27dpasmSJqlWrpvr162vixIkeXfF2ha1btyoqKkr16tXT8OHD9ccff9jbSvo9OnbsmCpVqlRgW0E/D7Kzs/P8jJSkoKAgbdy4UVar1ZVdhRcg4fMSGRkZstlsioiIyLM/PDxcaWlpbuqVe5w4cUIPPPCAFixYoLJly+ZrLy33Kjs7W5MmTVLfvn3tC7rGxMTo008/1aBBg+Tv76/q1avr5ptv1siRI+3vmzFjhoKDg9W/f393dd2linJ9N954o8qWLasmTZro4Ycf1r333lus95c26enpyszM1HPPPae7775bX3zxhapVq6a4uDidOHFCaWlpBf57Oz++tEKFCoqNjdXEiROVkZGhM2fOaOzYscrJySkxY2v/85//qEqVKoqLi8uz/2KfpaK8vyR7+eWXFRYWpvDwcAUEBGjatGlKSEhQcHBwkX4WSdJnn32m4OBglStXTgkJCfriiy/k4+PZX10u9Xkvyu+glJQUHTx4UF9//bU+/PBDTZ8+Xa+88oqeeeaZK3MRHqBly5Z65513tGLFCr3wwgtavXq17rjjDpm/lo8uyffoo48+0k8//VSsnwcdOnTQN998o88++0w2m03btm3Tm2++qZycHB07duxKdR0llGf/1ESRnf8BCOlf//qXevXqpZYtWxbYXhruVW5urvr06SNJev755+37jxw5ogcffFCjRo3Sf//7Xy1fvlxff/21nn32WUnnxmG98MILev31193Sb1cr6vUtXrxY//3vfzVv3jzNmDHDPkbC2++Po2w2mySpZ8+eGjJkiK6//nrNnTtXFotFy5cvL9K/uf/85z9KT09XeHi4goODdfjwYV1//fUe/+X+vLffflv33Xdfvv4W9lkq6vtLslmzZmnv3r1asWKFNmzYoLvvvlvdunVTRkbGJX8WndeuXTtt2bJFa9as0TXXXKO7777b46sZl/q8F+Xfg81m09mzZ/XWW2+pRYsW6t69u8aNG6c333zTWd30eJ07d1b37t3VsGFD/eMf/9Cy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uFDbtm1lsVhksVgUGBio2rVrq1+/ftq6dWue42bNmqVXXnnlkvFycnJksVi0atUqF/XYOc6ePathw4Zp9OjR+vXXX9WrVy93d6lQ0dHRTktIixpryZIlqlixom655RannLcgN910kypVqqRFixa57BwAAM9HwgcALvbII4/o6NGj2r17t958801ZrVbdcMMN+vTTT+3HVKxYURUrVnRjL53ryJEjOnPmjLp27arKlSsrMDAw3zHZ2dlu6JlrFPda5syZo/vvv99FvfmfPn366NVXX3X5eQAAnouEDwBcrFy5coqKilL16tXVtm1bLVy4UPfff78efPBBWa1WSfkf6Zw5c6Zq1aqlgIAARUdH66mnnpIk1alTR5LUrl07WSwW9evXT5L05ptvqkmTJipXrpxq1KihJ554Is/jgefjjx8/XpUqVVKVKlX04osv5unn/v37dccdd6hChQqqWLGiOnTooD/++EOSlJubqyeeeELR0dEqX7682rZtq23bthV4vatWrVKtWrUkSTExMbJYLEpJSVG/fv107733asyYMQoLC1PPnj0lSV988YUaNmyogIAA1alTR++88449VkpKiiwWi5YuXarmzZsrMDBQHTp0UEZGhj744APVrl1bV111lR599FEZYwr93+C9997T1VdfrbJlyyoqKkqDBw+WdK4C++uvv6p///6yWCxq27atJGn58uVq2bKlypcvrypVqmjYsGE6ffq0Pd5TTz2lm2++WTNmzFDVqlXVvHnzQmP9XWpqqtauXasuXboU2t/U1FQ1aNBAQ4YMkTFGb731lqKjo/X++++rVq1aCg4O1sMPP2z/3yU0NFTR0dF6991388Tp2rWrkpKS9OuvvxZ6LgCAdyPhAwA3ePjhh/Xrr79q06ZN+do2bNigCRMm6LXXXtPevXu1ZMkSe6L3ww8/SJI++ugjHT16VLNmzZJ0bkzY888/rx9//FGvvfaa5s2bp9dffz1P3OXLl8tqteqHH37QU089pVGjRtmTtuzsbHXs2FE2m00rV65UcnKyevToodzcXEnSxIkT9fnnn+u9997T5s2bddNNN+nWW2/VyZMn8/X/xhtv1Lp16yRJ69ev19GjR1WtWjVJ0rJly5SVlaXvv/9eL774olJSUhQfH6/4+Hht27ZNjzzyiAYMGKDvv/8+T8xJkybphRde0Lp16/Tzzz/rzjvv1Lvvvqtly5bp3Xff1Zw5c/TZZ58VeK+PHj2q/v37a+LEidq9e7c+++wzNWvWTJK0dOlSVa5cWTNnztTRo0e1dOlSSdKZM2c0btw4bd26Ve+//75WrlypiRMn5om7ZcsWJScn6+uvv9aSJUsKjfV3SUlJioiIUNWqVQtsP3z4sNq0aaO4uDi99tprslgskqSMjAwtWrRIn376qRYvXqzXX39dt912m2w2m9atW6cHH3xQgwYNUnp6uj1W1apVFRUVpaSkpALPBQAoBQwAwGXatGljxo0bl2//mTNnjCTz/vvvG2OM6du3r7n33nuNMcZ8+OGHpl69esZqteZ7n9VqNZLMypUrL3readOmmXbt2tlf9+3b11x77bV5jqlXr555+eWXjTHGzJ8/34SHh5vTp0/ni5WVlWUCAwPN9u3b8+yvW7eu+c9//lPg+ffu3WskmYMHD+bpQ0xMjMnNzbXvGz16tLnhhhvyvLdXr16mZ8+exhhjDh48aCSZxYsX57k2i8VifvvtN/u+Tp06mZEjRxbYl40bN5oKFSqYP//8s8D2qlWrmgULFhTYdt57771natWqZX89YcIEExwcnC9mUWK9+OKLpnHjxnn2rVy50kgye/fuNbVq1TL//ve/87QvWLDAWCwWk5qaat/XqVMnc91119lf5+TkmHLlypnly5fneW/Tpk3Nc889d9E+AQC8FxU+AHAD89fjh+erNxfq0KGDLBaLateuraFDhyohIeGijytK56pGHTt2VNWqVRUcHKynnnpKhw4dynNMgwYN8ryOiopSWlqaJOnHH39UbGysgoKC8sXev3+/srKy1LJlSwUHB9u3/fv368CBA8W67saNG8vH53+/enbv3q2WLVvmOaZVq1bavXt3nn0NGza0/3dkZKTCw8MVERGRZ9+Fla2/n7NRo0aKiYlRv379tGTJEp09e/ai/dy5c6e6d++u6tWrq3z58urfv3+++1m3bl0FBwdf/IILcObMGQUEBBTY1rZtW91+++169tln87WFh4crMjLS/joyMlLXXXed/bWvr69CQ0Pz3YfAwEBlZWUVu58AAO9AwgcAbrBr1y5JUs2aNfO1VaxYUdu2bdOrr74qf39/DRgwQHfccUehsf7880917dpVtWrV0kcffaRNmzZpzJgx9vGB5/n5+eV5bbFY7FP2XyyhPHXqlKRzY/O2bNli33bv3q2HHnqoSNd73t8Tykslsudd2HeLxXLRa/m7MmXKaNWqVVq8eLEiIyP12GOP6cYbb7xo0tetWzdZLBYtXLhQGzdu1EsvvZRvyYSCkuOiCA0N1fHjxwts69y5sz7//HMdPXo0X1tB11yU+/D7778rLCzMob4CAEq+Mu7uAACURi+//LKqVaum66+/vsB2f39/denSRV26dFGfPn3UokULpaWlKTw8XD4+PvaxddK5Ktnx48f1zDPPKCQkRJLyVaMupWHDhlq4cKEyMzPzJTLXXHON/P39dfToUTVv3rx4F3oJV199tRITE/PsW7duna6++mqnnsfX11ft2rVTu3btNGrUKEVGRmrLli2KjY2Vn59fnvt57Ngx7d+/Xx9++KGaNGki6dwyCkXx91gFady4sQ4ePKjs7Ox8lb45c+ZowIABuvXWW7V69WqFhoYW70L/Jjs7WwcOHFDjxo0vKw4AoOSiwgcALnb69Gmlpqbql19+0apVq3Tvvffq3Xff1WuvvaYyZfL/3e2zzz7TK6+8ou3bt+vAgQNavHixwsLCFBoaKovFomrVqikxMVFpaWk6deqUqlevLj8/P82ZM0cHDhzQa6+9pk8++aRYfbznnnsUHBysXr166b///a/27NmjuXPn6tixY6pQoYIeeughPfjgg/roo4908OBBrVu3To8//rh27NhxWffmwQcf1NatW/Xkk09qz549mj17tj788EM98sgjlxX3QsnJyXrmmWe0adMm/fzzz3rnnXcUEBCgGjVqSJJq1KihNWvWKDU1VSdOnNBVV12lq666Sm+88Yb9/s+dO7dI5/p7rIJcf/31Kl++vDZs2JCv7fwi9XXr1lWnTp0KnBSnODZs2KCgoCCnJ+oAgJKDhA8AXGzmzJmqXLmy6tWrpwEDBsjPz08bNmwodFr+kJAQLV68WLfccosaNWqk9evX67PPPpOvr68k6dlnn9XChQtVuXJlPfTQQ4qIiNDrr7+uOXPmqGHDhvr66681ZsyYYvUxICBAX331lWw2m1q3bq0bbrhBS5cutSekzz33nIYNG6b/+7//U/369XXXXXfp0KFDl12BqlGjhj755BN9/PHHatCggWbOnKk333xTN95442XFvVCFChX07bffqmPHjrrmmmv03nvvaenSpfbxcE899ZSSk5NVrVo13XHHHfL19dXChQv19ddf67rrrtPcuXM1adKkIp3r77EK4ufnp3vvvVcffPBBge1lypTR+++/r0qVKqlr167KzMx07MIlffDBB7rnnnvk7+/vcAwAQMlmMUUdQAEAAJwiJSVFLVu21J49e1ShQgWXnOPPP/9UnTp1tG7dOsXExLjkHAAAz0eFDwCAK6xmzZqaMWOGUlJSXHaOn3/+WS+++CLJHgCUclT4AAAAAMBLUeEDAAAAAC9FwgcAAAAAXoqEDwAAAAC8FAkfAAAAAHgpEj4AAAAA8FIkfAAAAADgpUj4AAAAAMBLkfABAAAAgJci4QMAAAAAL/X/WeSPhZJuXPIAAAAASUVORK5CYII=",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# plot 'filled' transect (with sea bed visualised as well)\n",
+ "fig, ax = plt.subplots(figsize=(10, 6), dpi=96)\n",
+ "\n",
+ "mesh = ax.pcolormesh(\n",
+ " distance_1d / 1000, # distance in km\n",
+ " z1d,\n",
+ " var_up.T,\n",
+ " cmap=VARIABLES[plot_variable][\"cmap\"],\n",
+ ")\n",
+ "\n",
+ "seabed = xr.where(np.isnan(var_up), 1, np.nan) # sea bed\n",
+ "ax.pcolormesh(\n",
+ " distance_1d / 1000, # distance in km\n",
+ " z1d,\n",
+ " seabed.T,\n",
+ " cmap=mcolors.ListedColormap([mcolors.to_rgba(\"tan\"), mcolors.to_rgba(\"white\")]),\n",
+ ")\n",
+ "\n",
+ "tan_patch = mpatches.Patch(color=mcolors.to_rgba(\"tan\"), label=\"Land / sea bed\")\n",
+ "ax.legend(handles=[tan_patch], loc=\"lower right\")\n",
+ "\n",
+ "ax.set_xticks(distance_1d / 1000)\n",
+ "\n",
+ "ax.set_ylabel(\"Depth (m)\")\n",
+ "ax.set_xlabel(\"Distance from start (km)\")\n",
+ "\n",
+ "\n",
+ "plt.colorbar(mesh, ax=ax, label=VARIABLES[plot_variable][\"label\"])\n",
+ "plt.tight_layout()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "91e0da97",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "ship",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.9"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/plot_3D.py b/docs/user-guide/teacher-content/UU_ocean_of_future/plot_3D.py
new file mode 100644
index 00000000..31c5c22b
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU_ocean_of_future/plot_3D.py
@@ -0,0 +1,250 @@
+"""N.B. Quick, inflexible (under active development) version whilst experimenting best approaches!""" # noqa: D400
+# TODO: WORK IN PROGRESS
+
+# %%
+import os
+from glob import glob
+
+import cmocean.cm as cmo
+import matplotlib as mpl
+import numpy as np
+import plotly.graph_objects as go
+import xarray as xr
+
+var = "temperature" # change this to your chosen variable
+
+
+base_dir = os.getcwd()
+filename = "ctd.zarr" if var in ["temperature", "salinity"] else "ctd_bgc.zarr"
+grp_dirs = sorted(glob(os.path.join(base_dir, "GRP????/results/", filename)))
+
+
+VARIABLES = {
+ "temperature": {
+ "cmap": cmo.thermal,
+ "label": "Temperature (°C)",
+ "ds_name": "temperature",
+ },
+ "salinity": {
+ "cmap": cmo.haline,
+ "label": "Salinity (psu)",
+ "ds_name": "salinity",
+ },
+ "oxygen": {
+ "cmap": cmo.oxy,
+ "label": r"Dissolved oxygen (mmol m$^{-3}$)",
+ "ds_name": "o2",
+ },
+ "nitrate": {
+ "cmap": cmo.matter,
+ "label": r"Nitrate (mmol m$^{-3}$)",
+ "ds_name": "no3",
+ },
+ "phosphate": {
+ "cmap": cmo.matter,
+ "label": r"Phosphate (mmol m$^{-3}$)",
+ "ds_name": "po4",
+ },
+ "ph": {
+ "cmap": cmo.balance,
+ "label": "pH",
+ "ds_name": "ph",
+ },
+ "phytoplankton": {
+ "cmap": cmo.algae,
+ "label": r"Total phytoplankton (mmol m$^{-3}$)",
+ "ds_name": "phyc",
+ },
+ "primary_production": {
+ "cmap": cmo.matter,
+ "label": r"Total primary production of phytoplankton (mg m$^{-3}$ day$^{-1}$)",
+ "ds_name": "nppv",
+ },
+ "chlorophyll": {
+ "cmap": cmo.algae,
+ "label": r"Chlorophyll (mg m$^{-3}$)",
+ "ds_name": "chl",
+ },
+}
+
+
+def haversine(lon1, lat1, lon2, lat2):
+ """Great-circle distance (meters) between two points."""
+ lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
+ dlon, dlat = lon2 - lon1, lat2 - lat1
+ a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
+ c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
+ return 6371000 * c
+
+
+def distance_from_start(ds):
+ """Add 'distance' variable: meters from first waypoint."""
+ lon0, lat0 = (
+ ds.isel(trajectory=0)["lon"].values[0],
+ ds.isel(trajectory=0)["lat"].values[0],
+ )
+ d = np.zeros_like(ds["lon"].values, dtype=float)
+ for ob, (lon, lat) in enumerate(zip(ds["lon"], ds["lat"], strict=False)):
+ d[ob] = haversine(lon, lat, lon0, lat0)
+ ds["distance"] = xr.DataArray(
+ d,
+ dims=ds["lon"].dims,
+ attrs={"long_name": "distance from first waypoint", "units": "m"},
+ )
+ return ds
+
+
+def descent_only(ds, variable):
+ """Extract descending CTD data (downcast), pad with NaNs for alignment."""
+ min_z_idx = ds["z"].argmin("obs")
+ da_clean = []
+ for i, traj in enumerate(ds["trajectory"].values):
+ idx = min_z_idx.sel(trajectory=traj).item()
+ descent_vals = ds[variable][
+ i, : idx + 1
+ ] # take values from surface to min_z_idx (inclusive)
+ da_clean.append(descent_vals)
+ max_len = max(len(arr[~np.isnan(arr)]) for arr in da_clean)
+ da_padded = np.full((ds["trajectory"].size, max_len), np.nan)
+ for i, arr in enumerate(da_clean):
+ da_dropna = arr[~np.isnan(arr)]
+ da_padded[i, : len(da_dropna)] = da_dropna
+ return xr.DataArray(
+ da_padded,
+ dims=["trajectory", "obs"],
+ coords={"trajectory": ds["trajectory"], "obs": np.arange(max_len)},
+ )
+
+
+def build_masked_array(data_up, profile_indices, n_profiles):
+ arr = np.full((n_profiles, data_up.shape[1]), np.nan)
+ for i, idx in enumerate(profile_indices):
+ if idx is not None:
+ arr[i, :] = data_up.values[idx, :]
+ return arr
+
+
+def get_profile_indices(distance_1d):
+ """
+ Returns regular distance bins and profile indices for CTD transect plotting.
+
+ Bin size is set to one order of magnitude lower than max distance.
+ """
+ dist_min, dist_max = float(distance_1d.min()), float(distance_1d.max())
+ if dist_max > 1e6:
+ dist_step = 1e5
+ elif dist_max > 1e5:
+ dist_step = 1e4
+ elif dist_max > 1e4:
+ dist_step = 1e3
+ else:
+ dist_step = 1e2 # fallback for very short transects
+
+ distance_regular = np.arange(dist_min, dist_max + dist_step, dist_step)
+ threshold = dist_step / 2
+ profile_indices = [
+ np.argmin(np.abs(distance_1d.values - d))
+ if np.min(np.abs(distance_1d.values - d)) < threshold
+ else None
+ for d in distance_regular
+ ]
+ return profile_indices, distance_regular
+
+
+# %%
+
+# pre processing, concat to 3D array
+expeditions = []
+times = []
+for i, path in enumerate(grp_dirs):
+ ctd_ds = xr.open_dataset(path)
+
+ # add distance from start
+ ctd_distance = distance_from_start(ctd_ds)
+
+ # extract descent-only data
+ if i == 0:
+ z_up = descent_only(ctd_distance, "z")
+ d_up = descent_only(ctd_distance, "distance")
+ var_up = descent_only(ctd_distance, VARIABLES[var]["ds_name"])
+
+ # append
+ expeditions.append(var_up)
+ times.append(ctd_ds["time"][0][0].values)
+
+# concat
+var_concat = xr.concat(expeditions, dim="expedition")
+
+
+# 1d array of depth dimension (from deepest trajectory)
+traj_idx, obs_idx = np.where(z_up == np.nanmin(z_up))
+z1d = z_up.values[traj_idx[0], :]
+
+# distance as 1d array
+distance_1d = d_up.isel(obs=0)
+
+# %%
+
+## plotting
+
+# trim to upper 600m
+var_trim = var_concat.where(z_up >= -600)
+
+# Convert cmo.thermal to Plotly colorscale
+thermal_cmap = cmo.thermal
+thermal_colorscale = [
+ [i / 255, mpl.colors.rgb2hex(thermal_cmap(i / 255))] for i in range(256)
+]
+
+# meshgrid for 3D plotting
+expeditions = var_trim["expedition"].values
+trajectories = distance_1d.values
+depths = z1d
+
+xx, yy, zz = np.meshgrid(expeditions, trajectories, depths, indexing="ij")
+
+# values
+values = var_trim.values # shape: (expedition, trajectory, obs)
+valid_values = values[~np.isnan(values)]
+isomin = np.nanpercentile(valid_values, 2.5)
+isomax = np.nanpercentile(valid_values, 97.5)
+
+fig = go.Figure(
+ data=go.Volume(
+ x=xx.flatten(),
+ y=yy.flatten() / 1000.0, # convert to km
+ z=zz.flatten(),
+ value=np.nan_to_num(values, nan=-9999).flatten(),
+ isomin=isomin,
+ isomax=isomax,
+ opacity=0.3,
+ surface_count=21,
+ # opacityscale=[[2, 0.2], [5, 0.5], [5, 0.5], [8, 1]],
+ # opacityscale="extremes",
+ # colorscale=thermal_colorscale,
+ caps=dict(x_show=False, y_show=False, z_show=False), # Hide caps for clarity
+ )
+)
+
+fig.update_layout(
+ scene=dict(
+ zaxis=dict(title="Depth (m)", range=[-600, 0]),
+ yaxis=dict(
+ title="Distance from start (km)",
+ range=[0, np.nanmax(trajectories) / 1000.0],
+ ),
+ xaxis=dict(
+ title="Year",
+ tickvals=np.array([i for i in range(len(expeditions))])[::-1],
+ ticktext=[
+ str(np.datetime64(times[i], "Y")) for i in range(len(expeditions))
+ ][::-1],
+ ),
+ ),
+ margin=dict(l=0, r=0, b=0, t=40),
+ title="3D Volume Plot of " + VARIABLES[var]["label"],
+)
+
+fig.show()
+
+fig.write_html(f"./sample_3D_{var}.html")
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/plot_slider.py b/docs/user-guide/teacher-content/UU_ocean_of_future/plot_slider.py
new file mode 100644
index 00000000..324c5ba4
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU_ocean_of_future/plot_slider.py
@@ -0,0 +1,279 @@
+"""N.B. Quick (under active development) version whilst experimenting best approaches!""" # noqa: D400
+# TODO: WORK IN PROGRESS
+
+# %%
+import os
+from glob import glob
+
+import cmocean.cm as cmo
+import matplotlib as mpl
+import numpy as np
+import plotly.graph_objs as go
+import xarray as xr
+
+var = "primary_production" # change this to your chosen variable
+
+
+base_dir = os.getcwd()
+filename = "ctd.zarr" if var in ["temperature", "salinity"] else "ctd_bgc.zarr"
+grp_dirs = sorted(glob(os.path.join(base_dir, "GRP????/results/", filename)))
+
+
+VARIABLES = {
+ "temperature": {
+ "cmap": cmo.thermal,
+ "label": "Temperature (°C)",
+ "ds_name": "temperature",
+ },
+ "salinity": {
+ "cmap": cmo.haline,
+ "label": "Salinity (PSU)",
+ "ds_name": "salinity",
+ },
+ "oxygen": {
+ "cmap": cmo.oxy,
+ "label": r"Dissolved oxygen (mmol m-3)",
+ "ds_name": "o2",
+ },
+ "nitrate": {
+ "cmap": cmo.matter,
+ "label": r"Nitrate (mmol m-3)",
+ "ds_name": "no3",
+ },
+ "phosphate": {
+ "cmap": cmo.matter,
+ "label": r"Phosphate (mmol m-3)",
+ "ds_name": "po4",
+ },
+ "ph": {
+ "cmap": cmo.balance,
+ "label": "pH",
+ "ds_name": "ph",
+ },
+ "phytoplankton": {
+ "cmap": cmo.algae,
+ "label": r"Total phytoplankton (mmol m-3)",
+ "ds_name": "phyc",
+ },
+ "primary_production": {
+ "cmap": cmo.matter,
+ "label": "Total primary production of phytoplankton (mg m-3 day-1)",
+ "ds_name": "nppv",
+ },
+ "chlorophyll": {
+ "cmap": cmo.algae,
+ "label": "Chlorophyll (mg m-3)",
+ "ds_name": "chl",
+ },
+}
+
+
+def haversine(lon1, lat1, lon2, lat2):
+ """Great-circle distance (meters) between two points."""
+ lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
+ dlon, dlat = lon2 - lon1, lat2 - lat1
+ a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
+ c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
+ return 6371000 * c
+
+
+def distance_from_start(ds):
+ """Add 'distance' variable: meters from first waypoint."""
+ lon0, lat0 = (
+ ds.isel(trajectory=0)["lon"].values[0],
+ ds.isel(trajectory=0)["lat"].values[0],
+ )
+ d = np.zeros_like(ds["lon"].values, dtype=float)
+ for ob, (lon, lat) in enumerate(zip(ds["lon"], ds["lat"], strict=False)):
+ d[ob] = haversine(lon, lat, lon0, lat0)
+ ds["distance"] = xr.DataArray(
+ d,
+ dims=ds["lon"].dims,
+ attrs={"long_name": "distance from first waypoint", "units": "m"},
+ )
+ return ds
+
+
+def descent_only(ds, variable):
+ """Extract descending CTD data (downcast), pad with NaNs for alignment."""
+ min_z_idx = ds["z"].argmin("obs")
+ da_clean = []
+ for i, traj in enumerate(ds["trajectory"].values):
+ idx = min_z_idx.sel(trajectory=traj).item()
+ descent_vals = ds[variable][
+ i, : idx + 1
+ ] # take values from surface to min_z_idx (inclusive)
+ da_clean.append(descent_vals)
+ max_len = max(len(arr[~np.isnan(arr)]) for arr in da_clean)
+ da_padded = np.full((ds["trajectory"].size, max_len), np.nan)
+ for i, arr in enumerate(da_clean):
+ da_dropna = arr[~np.isnan(arr)]
+ da_padded[i, : len(da_dropna)] = da_dropna
+ return xr.DataArray(
+ da_padded,
+ dims=["trajectory", "obs"],
+ coords={"trajectory": ds["trajectory"], "obs": np.arange(max_len)},
+ )
+
+
+def build_masked_array(data_up, profile_indices, n_profiles):
+ arr = np.full((n_profiles, data_up.shape[1]), np.nan)
+ for i, idx in enumerate(profile_indices):
+ if idx is not None:
+ arr[i, :] = data_up.values[idx, :]
+ return arr
+
+
+def get_profile_indices(distance_1d):
+ """
+ Returns regular distance bins and profile indices for CTD transect plotting.
+
+ Bin size is set to one order of magnitude lower than max distance.
+ """
+ dist_min, dist_max = float(distance_1d.min()), float(distance_1d.max())
+ if dist_max > 1e6:
+ dist_step = 1e5
+ elif dist_max > 1e5:
+ dist_step = 1e4
+ elif dist_max > 1e4:
+ dist_step = 1e3
+ else:
+ dist_step = 1e2 # fallback for very short transects
+
+ distance_regular = np.arange(dist_min, dist_max + dist_step, dist_step)
+ threshold = dist_step / 2
+ profile_indices = [
+ np.argmin(np.abs(distance_1d.values - d))
+ if np.min(np.abs(distance_1d.values - d)) < threshold
+ else None
+ for d in distance_regular
+ ]
+ return profile_indices, distance_regular
+
+
+# %%
+
+# pre processing, concat to 3D array
+expeditions = []
+times = []
+for i, path in enumerate(grp_dirs):
+ ctd_ds = xr.open_dataset(path)
+
+ # add distance from start
+ ctd_distance = distance_from_start(ctd_ds)
+
+ # extract descent-only data
+ if i == 0:
+ z_up = descent_only(ctd_distance, "z")
+ d_up = descent_only(ctd_distance, "distance")
+ var_up = descent_only(ctd_distance, VARIABLES[var]["ds_name"])
+
+ # append
+ expeditions.append(var_up)
+ times.append(ctd_ds["time"][0][0].values)
+
+# concat
+var_concat = xr.concat(expeditions, dim="expedition")
+var_concat["expedition"] = times
+
+# 1d array of depth dimension (from deepest trajectory)
+traj_idx, obs_idx = np.where(z_up == np.nanmin(z_up))
+z1d = z_up.values[traj_idx[0], :]
+
+# distance as 1d array
+distance_1d = d_up.isel(obs=0)
+
+# %%
+
+## plotting (interactive with Plotly)
+
+depth_lim = -200 # [m]
+
+# trim to upper 600m
+var_trim = var_concat.where(z_up >= depth_lim)
+
+
+# Prepare colorscale for Plotly from matplotlib colormap
+def mpl_to_plotly(cmap, n=256):
+ return [[i / (n - 1), mpl.colors.rgb2hex(cmap(i / (n - 1)))] for i in range(n)]
+
+
+plotly_cmap = mpl_to_plotly(VARIABLES[var]["cmap"])
+
+# Prepare slider steps
+steps = []
+data = []
+for t in range(var_trim.shape[0]):
+ seabed = xr.where(np.isnan(var_trim[t]), 1, None).T
+
+ # main cross-section
+ trace = go.Heatmap(
+ z=var_trim[t].T,
+ x=distance_1d / 1000.0, # distance in km
+ y=z1d,
+ zmin=np.nanmin(var_trim.values),
+ zmax=np.nanmax(var_trim.values),
+ colorscale=plotly_cmap,
+ colorbar=dict(title=VARIABLES[var]["label"]),
+ showscale=True,
+ visible=(t == 0),
+ customdata=None,
+ hovertemplate="Distance: %{x:.2f} km Depth: %{z:.1f} m Value: %{value:.2f}",
+ )
+ # Seabed overlay (tan color)
+ seabed_trace = go.Heatmap(
+ z=seabed,
+ x=distance_1d / 1000.0, # distance in km
+ y=z1d,
+ colorscale=[[0, "tan"], [1, "tan"]],
+ showscale=False,
+ opacity=1.0,
+ visible=(t == 0),
+ name="Land / sea bed",
+ hoverinfo="skip",
+ )
+ data.append(trace)
+ data.append(seabed_trace)
+ steps.append(
+ {
+ "method": "update",
+ "args": [
+ {"visible": [i // 2 == t for i in range(2 * var_trim.shape[0])]},
+ {
+ "title": f"{VARIABLES[var]['label']} (Date {np.datetime_as_string(var_trim['expedition'][t].values, unit='D')})"
+ },
+ ],
+ "label": str(
+ np.datetime_as_string(var_trim["expedition"][t].values, unit="D")
+ ),
+ }
+ )
+
+sliders = [
+ dict(active=0, currentvalue={"prefix": "Date: "}, pad={"t": 50}, steps=steps)
+]
+
+layout = go.Layout(
+ title=f"{VARIABLES[var]['label']} (Date {np.datetime_as_string(var_trim['expedition'][0].values, unit='D')})",
+ xaxis=dict(
+ title="Distance from start (km)",
+ tickvals=(distance_1d / 1000.0),
+ tickformat=".0f",
+ ),
+ yaxis=dict(
+ title="Depth (m)",
+ range=[depth_lim, np.nanmax(z1d)],
+ ),
+ sliders=sliders,
+ legend=dict(itemsizing="constant"),
+ width=900,
+ height=600,
+)
+
+fig = go.Figure(data=data, layout=layout)
+fig.show()
+
+fig.write_html(f"./sample_slider_{var}.html")
+
+
+# %%
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/timeseries.py b/docs/user-guide/teacher-content/UU_ocean_of_future/timeseries.py
new file mode 100644
index 00000000..24d97547
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU_ocean_of_future/timeseries.py
@@ -0,0 +1,177 @@
+# %%
+
+"""N.B. Quick, inflexible (under active development) version whilst experimenting best approaches!""" # noqa: D400
+# TODO: WORK IN PROGRESS!
+
+import glob
+import os
+
+import matplotlib.pyplot as plt
+import numpy as np
+import xarray as xr
+
+# TODO: incorporate uncertainty estimates in the plots, box plots per year/expedition
+
+# TODO: build conmplexity of plots, single points -> lines -> uncertainty/boxplots -> colour boxplots by month of the (half) year
+
+# TODO: timeseries - can just do it for surface...
+
+# TODO: 3D plots of CTD Transects!
+
+variables = ["phyc", "temperature", "salinity", "o2", "no3", "po4"]
+
+base_dir = os.getcwd()
+
+dict_vars = {}
+for var in variables:
+ print(f"Processing variable: {var}")
+ filename = "ctd.zarr" if var in ["temperature", "salinity"] else "ctd_bgc.zarr"
+ grp_dirs = sorted(glob.glob(os.path.join(base_dir, "GRP????/results/", filename)))
+
+ var_values = []
+ times = []
+
+ tmp = {}
+ for zarr_path in grp_dirs:
+ ds = xr.open_zarr(zarr_path)
+
+ # extract variable values and time
+ var_values.append(ds[var].values.flatten())
+ times.append(ds["time"].values[0][0])
+
+ # organise to dict
+ tmp["values"], tmp["time"] = var_values, times
+
+ # master dict
+ dict_vars[var] = tmp
+
+# %%
+
+plot_dict = {
+ "phyc": {
+ "label": "Phytoplankton",
+ "units": "mmol m$^{-3}$",
+ "color": "forestgreen",
+ },
+ "temperature": {
+ "label": "Temperature",
+ "units": "°C",
+ "color": "crimson",
+ },
+ "salinity": {
+ "label": "Salinity",
+ "units": "PSU",
+ "color": "lightseagreen",
+ },
+ "o2": {
+ "label": "Oxygen",
+ "units": "mmol m$^{-3}$",
+ "color": "dodgerblue",
+ },
+ "no3": {
+ "label": "Nitrate",
+ "units": "mmol m$^{-3}$",
+ "color": "darkorchid",
+ },
+ "po4": {
+ "label": "Phosphate",
+ "units": "mmol m$^{-3}$",
+ "color": "coral",
+ },
+}
+
+# %%
+
+combined_vars = [v for v in variables if v not in ["no3", "po4"]] + ["no3_po4"]
+fig, axs = plt.subplots(
+ len(combined_vars), 1, figsize=(10, 10), dpi=96, sharex=True, sharey=False
+)
+
+for i, ax in enumerate(axs):
+ if i < len(combined_vars) - 1:
+ var = combined_vars[i]
+ color = plot_dict[var]["color"]
+
+ ax.scatter(
+ dict_vars[var]["time"],
+ [np.nanmean(values) for values in dict_vars[var]["values"]],
+ color=color,
+ zorder=3,
+ s=50,
+ )
+
+ ax.plot(
+ dict_vars[var]["time"],
+ [np.nanmean(values) for values in dict_vars[var]["values"]],
+ linestyle="dotted",
+ alpha=1.0,
+ color=color,
+ lw=2.25,
+ )
+
+ ax.set_title(f"{plot_dict[var]['label']}", fontsize=12)
+ ax.set_ylabel(plot_dict[var]["units"], fontsize=11)
+ else:
+ color_no3 = plot_dict["no3"]["color"]
+ color_po4 = plot_dict["po4"]["color"]
+
+ ax.scatter(
+ dict_vars["no3"]["time"],
+ [np.nanmean(values) for values in dict_vars["no3"]["values"]],
+ color=color_no3,
+ zorder=3,
+ s=50,
+ label=plot_dict["no3"]["label"],
+ )
+ ax.plot(
+ dict_vars["no3"]["time"],
+ [np.nanmean(values) for values in dict_vars["no3"]["values"]],
+ linestyle="dotted",
+ alpha=1.0,
+ color=color_no3,
+ lw=2.25,
+ )
+ ax.set_ylabel(plot_dict["no3"]["units"], fontsize=11, color=color_no3)
+ ax.tick_params(axis="y", labelcolor=color_no3)
+
+ ax2 = ax.twinx()
+ ax2.scatter(
+ dict_vars["po4"]["time"],
+ [np.nanmean(values) for values in dict_vars["po4"]["values"]],
+ color=color_po4,
+ zorder=3,
+ s=50,
+ label=plot_dict["po4"]["label"],
+ )
+ ax2.plot(
+ dict_vars["po4"]["time"],
+ [np.nanmean(values) for values in dict_vars["po4"]["values"]],
+ linestyle="dotted",
+ alpha=1.0,
+ color=color_po4,
+ lw=2.25,
+ )
+ ax2.set_ylabel(plot_dict["po4"]["units"], fontsize=11, color=color_po4)
+ ax2.tick_params(axis="y", labelcolor=color_po4)
+
+ ax.set_title("Nutrients", fontsize=12)
+
+ handles, labels = [], []
+ for a in [ax, ax2]:
+ h, label = a.get_legend_handles_labels()
+ handles += h
+ labels += label
+ ax.legend(handles, labels, loc="upper right")
+
+ ax.set_xlim(np.datetime64("1993-01-01"), np.datetime64("2025-12-31"))
+
+ if i == len(axs) - 1: # bottom panel only for single column of subplots
+ ax.set_xlabel("Time")
+
+ ax.set_facecolor("gainsboro")
+ ax.grid(color="white", linewidth=1.0)
+
+
+plt.tight_layout()
+plt.show()
+# %%
From b5eab8ba32c5e7f3b774caa1bf808f52d3863971 Mon Sep 17 00:00:00 2001
From: j-atkins <106238905+j-atkins@users.noreply.github.com>
Date: Wed, 5 Nov 2025 14:57:57 +0100
Subject: [PATCH 02/26] rename course directory
---
.../CTD_transects.ipynb | 0
.../{UU_ocean_of_future => UU-ocean-of-future}/plot_3D.py | 0
.../{UU_ocean_of_future => UU-ocean-of-future}/plot_slider.py | 0
.../{UU_ocean_of_future => UU-ocean-of-future}/timeseries.py | 0
4 files changed, 0 insertions(+), 0 deletions(-)
rename docs/user-guide/teacher-content/{UU_ocean_of_future => UU-ocean-of-future}/CTD_transects.ipynb (100%)
rename docs/user-guide/teacher-content/{UU_ocean_of_future => UU-ocean-of-future}/plot_3D.py (100%)
rename docs/user-guide/teacher-content/{UU_ocean_of_future => UU-ocean-of-future}/plot_slider.py (100%)
rename docs/user-guide/teacher-content/{UU_ocean_of_future => UU-ocean-of-future}/timeseries.py (100%)
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/CTD_transects.ipynb b/docs/user-guide/teacher-content/UU-ocean-of-future/CTD_transects.ipynb
similarity index 100%
rename from docs/user-guide/teacher-content/UU_ocean_of_future/CTD_transects.ipynb
rename to docs/user-guide/teacher-content/UU-ocean-of-future/CTD_transects.ipynb
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/plot_3D.py b/docs/user-guide/teacher-content/UU-ocean-of-future/plot_3D.py
similarity index 100%
rename from docs/user-guide/teacher-content/UU_ocean_of_future/plot_3D.py
rename to docs/user-guide/teacher-content/UU-ocean-of-future/plot_3D.py
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/plot_slider.py b/docs/user-guide/teacher-content/UU-ocean-of-future/plot_slider.py
similarity index 100%
rename from docs/user-guide/teacher-content/UU_ocean_of_future/plot_slider.py
rename to docs/user-guide/teacher-content/UU-ocean-of-future/plot_slider.py
diff --git a/docs/user-guide/teacher-content/UU_ocean_of_future/timeseries.py b/docs/user-guide/teacher-content/UU-ocean-of-future/timeseries.py
similarity index 100%
rename from docs/user-guide/teacher-content/UU_ocean_of_future/timeseries.py
rename to docs/user-guide/teacher-content/UU-ocean-of-future/timeseries.py
From 8a60b82290a3d0d56de320e306bd330af793dcf9 Mon Sep 17 00:00:00 2001
From: Emma Daniels
Date: Wed, 5 Nov 2025 15:36:40 +0100
Subject: [PATCH 03/26] WIP presentation and tutorial handouts
---
.../UU-ocean-of-future/Presentation.qmd | 111 ++++++++++++++++++
.../UU-ocean-of-future/Tutorial1.ipynb | 76 ++++++++++++
.../UU-ocean-of-future/Tutorial2.ipynb | 55 +++++++++
3 files changed, 242 insertions(+)
create mode 100644 docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
create mode 100644 docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
create mode 100644 docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial2.ipynb
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
new file mode 100644
index 00000000..749d9edf
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
@@ -0,0 +1,111 @@
+---
+title: "Ocean observations"
+subtitle: "Ocean of the future 13-11-2025"
+author: "Emma Daniels"
+format:
+ revealjs:
+ slide-number: true
+ theme: sky
+ logo: "https://virtualship.readthedocs.io/en/latest/_static/virtual_ship_logo.png"
+ controls: true
+ incremental: false
+title-slide-attributes:
+ data-background-image: "https://www.euro-argo.eu/var/storage/images/_aliases/content_embed_page/medias-ifremer/medias-euro_argo/outreach/educational-material/illustration-ocean-observers-pour-visuel-onglet/1837322-1-eng-GB/illustration-Ocean-Observers-pour-visuel-onglet.jpg"
+ data-background-size: contain
+---
+
+## {background-iframe="https://wordwall.net/embed/4f6b5ced54d644c2bab35354305bb0eb?themeId=65&templateId=30&fontStackId=1" background-interactive="true"}
+
+## HMS Challenger
+First major scientific oceanographic expedition (1872-1876)
+
+
+## Difficulties of Measuring
+
+- **Vast Scale**: Covers 71% of Earth's surface, average depth 3,688m
+- **Accessibility**: Remote locations, harsh weather, high operational costs
+- **Extreme conditions**: Pressure, storms and salt require specialized equipment
+- **Light Limitation**: Optical methods only work in top ~200m
+- **Real-Time Monitoring**: Difficulty transmitting data from deep or remote locations
+
+## Satellite Observations
+
+
+Note that it's not possible to investigate the interior ocean
+
+## Advantages and Limitations
+
+::: {.nonincremental}
+- **Advantages**:
+ - Global coverage and accessibility
+ - Continuous monitoring of large areas
+ - Cost-effective compared to ship-based surveys
+
+- **Limitations**:
+ - Affected by atmospheric conditions
+ - Cannot penetrate deep into the ocean
+:::
+
+## Light Attenuation in the Ocean
+
+
+## Light Attenuation in the Ocean
+
+- Remote sensing only observes top few meters of ocean
+- Cameras and optical instruments are ineffective at depth
+- Alternative methods required:
+ - Acoustic sensors
+ - Cabled networks and instruments
+ - Resurfacing equipment
+
+## Alternative Methods
+
+- **Coastal Observatories**: Shore-based radar, tide gauges, and sensor networks
+- **Moored Buoys**: Fixed platforms for continuous time-series measurements
+- **Argo Floats and Gliders**: Autonomous floats and vehicles for extended ocean monitoring
+- **Animal-Borne Sensors**: Tags on marine animals collecting oceanographic data
+- **Research Vessels**: Ship-based surveys and deep-sea exploration
+
+
+
+## Alternative Methods
+
+
+## Ship-Based Measurements
+
+- **CTD Casts**: Conductivity, Temperature, and Depth profiling and water samples
+- **Acoustic Doppler Current Profilers (ADCP)**: Measuring ocean currents
+- **Multibeam Echosounders**: High-resolution seafloor mapping
+- **Sediment Cores**: Extracting seafloor samples for geological and climate studies
+- **Net Tows**: Biological sampling of plankton and marine organisms
+
+##
+
+
+##
+
+
+## Benefits of CTD Measurements
+
+- **Multiple Parameters**: Temperature, salinity, depth, plus additional sensors (O₂, chlorophyll, turbidity)
+- **Water Mass Identification**: Characterize ocean layers and circulation patterns
+- **High Vertical Resolution**: Continuous profiling from surface to seafloor
+- **Targeted Sampling**: Niskin bottles collect water at specific depths of interest
+- **Calibration Standard**: Validates satellite and autonomous sensor data
+
+## Plankton measurements
+
+- Net Tows
+- In-Situ Imaging
+- Water Sampling
+- Chlorophyll Analysis
+- eDNA Filtering
+- Preserving Samples
+
+##
+
+
+##
+
+
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
new file mode 100644
index 00000000..14b78a5a
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
@@ -0,0 +1,76 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "1536cbb7",
+ "metadata": {},
+ "source": [
+ "# Virtual Ship exercise\n",
+ "\n",
+ "## Form and register your duo (preferably with different backgrounds) to choose a year.\n",
+ "- Instructions Appy\n",
+ "\n",
+ "## Check out your expeditions measurement locations\n",
+ "https://nioz.marinefacilitiesplanning.com/cruiselocationplanning#\n",
+ "- Save and upload the cruise track (xls file)\n",
+ "- Check out the measurement locations and depths, and travel time between stations\n",
+ "\n",
+ "## Choose a week within the summer half year (April - October) for your expedition\n",
+ "- Consider weather conditions and daylight hours for optimal data collection\n",
+ "\n",
+ "## Open a terminal and prepare your expedition in virtual ship:\n",
+ "```virtualship plan GROUP_YOURGROUPNUMBER```\n",
+ "- Replace YOURGROUPNUMBER with your actual group number (e.g., GROUP_1997)\n",
+ "\n",
+ "## Follow the instructions in the terminal to set up your expedition\n",
+ "- From the Schedule Editor select Waypoints & Instrument Selection\n",
+ "- Click each waypoint to set the time (consider _transit times_ between locations and _time needed for measurements_) \n",
+ "- Select the instruments to use at each location: i.e. CTD and CTD-BGC\n",
+ "- Save your changes\n",
+ "\n",
+ "## Fetch the data needed for your expedition\n",
+ "`virtualship fetch GROUP_YOURGROUPNUMBER --username --password `\n",
+ "- Replace EXPEDITION_NAME with your actual expedition name (e.g., GROUP_1997)\n",
+ "- Provide your Copernicus username and password when prompted\n",
+ "- (Sign up for a Copernicus account if you don't have one yet: https://data.marine.copernicus.eu/register)\n",
+ "- Practice your patience - a key skill in oceanography! - data download may take some time.\n",
+ "\n",
+ "## Start your expedition\n",
+ "`virtualship run GROUP_YOURGROUPNUMBER`\n",
+ "\n",
+ "## Verify your expedition was successful by checking the output directory for data files\n",
+ "- Navigate to the results directory:\n",
+ "`cd GROUP_YOURGROUPNUMBER/results`\n",
+ "- Send the link to your results in via Brightspace before Wednesday 19-11-2025 13:00h.\n",
+ "`pwd`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7207b607",
+ "metadata": {},
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial2.ipynb b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial2.ipynb
new file mode 100644
index 00000000..c73d111f
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial2.ipynb
@@ -0,0 +1,55 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "c6b4d243",
+ "metadata": {},
+ "source": [
+ "## Open the notebook CTD transects and run all cells to generate some plots\n",
+ "- modify your data directory\n",
+ "data_dir\n",
+ "\n",
+ "## Plot CTD transects for different variables\n",
+ "Remember your MFP cruise plan and the bathymetry of the North West Shelf TODO: (links)\n",
+ "- Explain why the data is available at different depths throughout the expedition \n",
+ "- Are there temperatures that stand out? For example, temperatures below 0 degrees?\n",
+ "\n",
+ "Play around setting ax.set_ylim() to zoom in on certain depth ranges\n",
+ "- Describe the evolution of salinity along the transect\n",
+ "- Explain why salinity changes close to land\n",
+ "\n",
+ "Oxygen:\n",
+ "- Describe the evolution of oxygen along the transect\n",
+ "- Can you identify any oxygen minimum zones? Hypoxia?\n",
+ "\n",
+ "Nitrate: \n",
+ "Is there nearest to land there are very high nitrate values\n",
+ "- Explain why this is the case\n",
+ "\n",
+ "pH\n",
+ "- Hypothesize why pH is lower in some spots\n",
+ "- Is there an obvious correlation with temperature or other variables?\n",
+ "\n",
+ "Phytoplankton (Chlorophyll)\n",
+ "- Where are the highest concentrations of chlorophyll?\n",
+ "- Is there an obvious correlation with temperature or other variables?\n",
+ "\n",
+ "## Discussion points\n",
+ "- Which variables are most affected by proximity to land?\n",
+ "- How do you expect these variables to change with climate change?\n",
+ "\n",
+ "## Reflection\n",
+ "- Are there locations where you would have liked to take more measurements? \n",
+ "- Why? and how would you modify the cruise plan?\n",
+ "\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
From c066d9a2c876909631e5fd60cbf83157cbdd80ec Mon Sep 17 00:00:00 2001
From: Emma Daniels
Date: Thu, 6 Nov 2025 11:41:52 +0100
Subject: [PATCH 04/26] presentation updates
---
.gitignore | 1 +
.../UU-ocean-of-future/Presentation.qmd | 43 +++++++++++++------
2 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/.gitignore b/.gitignore
index 4efdfe45..deb0167c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ __pycache__/
*.jpg
*.gif
!docs/**/*.gif
+**/Presentation_files
# Data files created
/results
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
index 749d9edf..01a9246b 100644
--- a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
@@ -1,24 +1,24 @@
---
title: "Ocean observations"
subtitle: "Ocean of the future 13-11-2025"
-author: "Emma Daniels"
+author: "Emma Daniels & Jamie Atkins"
format:
revealjs:
slide-number: true
theme: sky
logo: "https://virtualship.readthedocs.io/en/latest/_static/virtual_ship_logo.png"
controls: true
- incremental: false
+ # incremental: true
title-slide-attributes:
- data-background-image: "https://www.euro-argo.eu/var/storage/images/_aliases/content_embed_page/medias-ifremer/medias-euro_argo/outreach/educational-material/illustration-ocean-observers-pour-visuel-onglet/1837322-1-eng-GB/illustration-Ocean-Observers-pour-visuel-onglet.jpg"
+ data-background-image: "https://cloudfront-us-east-2.images.arcpublishing.com/reuters/CQFY2GVMTNJ45KAVHBVEWJYZ44.jpg"
data-background-size: contain
+ # data-background-iframe: "https://www.youtube.com/embed/qeeipUefe8A?autoplay=1&controls=0&loop=1"
+ # data-background-video-loop: true
+ # data-background-video-muted: true
---
-## {background-iframe="https://wordwall.net/embed/4f6b5ced54d644c2bab35354305bb0eb?themeId=65&templateId=30&fontStackId=1" background-interactive="true"}
-
-## HMS Challenger
-First major scientific oceanographic expedition (1872-1876)
-
+##
+
## Difficulties of Measuring
@@ -33,7 +33,7 @@ First major scientific oceanographic expedition (1872-1876)

Note that it's not possible to investigate the interior ocean
-## Advantages and Limitations
+## Satellite Observations
::: {.nonincremental}
- **Advantages**:
@@ -58,7 +58,10 @@ Note that it's not possible to investigate the interior ocean
- Cabled networks and instruments
- Resurfacing equipment
-## Alternative Methods
+## PACE satellite ('24)
+
+
+## Observation * calibration Methods
- **Coastal Observatories**: Shore-based radar, tide gauges, and sensor networks
- **Moored Buoys**: Fixed platforms for continuous time-series measurements
@@ -66,8 +69,9 @@ Note that it's not possible to investigate the interior ocean
- **Animal-Borne Sensors**: Tags on marine animals collecting oceanographic data
- **Research Vessels**: Ship-based surveys and deep-sea exploration
-
+## HMS Challenger
+First major scientific oceanographic expedition (1872-1876)
+
## Alternative Methods

@@ -78,7 +82,7 @@ Note that it's not possible to investigate the interior ocean
- **Acoustic Doppler Current Profilers (ADCP)**: Measuring ocean currents
- **Multibeam Echosounders**: High-resolution seafloor mapping
- **Sediment Cores**: Extracting seafloor samples for geological and climate studies
-- **Net Tows**: Biological sampling of plankton and marine organisms
+- **Tows**: Biological sampling of plankton and marine organisms
##

@@ -96,7 +100,7 @@ Note that it's not possible to investigate the interior ocean
## Plankton measurements
-- Net Tows
+- Tows: nets or CPR
- In-Situ Imaging
- Water Sampling
- Chlorophyll Analysis
@@ -109,3 +113,14 @@ Note that it's not possible to investigate the interior ocean
##

+##
+
+
+##
+
+
+##
+
+
+##
+
From 7046d56c5b8f3604a67bdee34adce21d0f0d5bac Mon Sep 17 00:00:00 2001
From: Emma Daniels
Date: Thu, 6 Nov 2025 18:08:42 +0100
Subject: [PATCH 05/26] update tut1
---
.../UU-ocean-of-future/Tutorial1.ipynb | 43 +++++++++++--------
1 file changed, 26 insertions(+), 17 deletions(-)
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
index 14b78a5a..122c1b0d 100644
--- a/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
@@ -6,42 +6,51 @@
"metadata": {},
"source": [
"# Virtual Ship exercise\n",
+ "You can work on the exercise either individually or in pairs. You will work with the same person during the tutorials on November 17th as well. \n",
+ "You will plan and execute a virtual oceanographic expedition using the Virtual Ship software. Follow the steps below to complete the exercise.\n",
"\n",
- "## Form and register your duo (preferably with different backgrounds) to choose a year.\n",
- "- Instructions Appy\n",
+ "### Form and register your duo (preferably with different backgrounds) through Teams\n",
+ "- Choose a month in the summer half year in which you want to do the expedition (i.e. April - October)\n",
+ "- Fill in the sheet in the Teams channel and remember your group number (e.g. GROUP1) and the year it is associated with (e.g. 1993)\n",
"\n",
- "## Check out your expeditions measurement locations\n",
+ "### Check out your expeditions measurement locations\n",
"https://nioz.marinefacilitiesplanning.com/cruiselocationplanning#\n",
"- Save and upload the cruise track (xls file)\n",
+ "- Select _Texel - Netherlands_ as the Port of Departure \n",
"- Check out the measurement locations and depths, and travel time between stations\n",
+ "- Do a (rough) calculation on the time the CTD measurements will take at each station \n",
+ " - Assume 10 minutes for deployment and retrieval of the CTD at each station\n",
+ " - Assume 1 m/s for the CTD to go down and up (i.e., for a station at 100 m depth, it will take approximately 200 seconds to go down and up)\n",
"\n",
- "## Choose a week within the summer half year (April - October) for your expedition\n",
- "- Consider weather conditions and daylight hours for optimal data collection\n",
+ "### Open a terminal and prepare your expedition in virtual ship:\n",
+ "```\n",
+ "virtualship plan GROUP#\n",
+ "```\n",
+ "Replace # with your actual group number (e.g., GROUP1)\n",
"\n",
- "## Open a terminal and prepare your expedition in virtual ship:\n",
- "```virtualship plan GROUP_YOURGROUPNUMBER```\n",
- "- Replace YOURGROUPNUMBER with your actual group number (e.g., GROUP_1997)\n",
+ "- Decide on the exact timing of your expedition with in the year and month registered though Teams\n",
"\n",
- "## Follow the instructions in the terminal to set up your expedition\n",
+ "### Follow the instructions in the terminal to set up your expedition\n",
"- From the Schedule Editor select Waypoints & Instrument Selection\n",
"- Click each waypoint to set the time (consider _transit times_ between locations and _time needed for measurements_) \n",
"- Select the instruments to use at each location: i.e. CTD and CTD-BGC\n",
"- Save your changes\n",
"\n",
- "## Fetch the data needed for your expedition\n",
- "`virtualship fetch GROUP_YOURGROUPNUMBER --username --password `\n",
- "- Replace EXPEDITION_NAME with your actual expedition name (e.g., GROUP_1997)\n",
+ "### Fetch the data needed for your expedition\n",
+ "`virtualship fetch GROUP# --username --password `\n",
+ "Replace GROUP# with your actual expedition name (e.g., GROUP1)\n",
+ "\n",
"- Provide your Copernicus username and password when prompted\n",
"- (Sign up for a Copernicus account if you don't have one yet: https://data.marine.copernicus.eu/register)\n",
"- Practice your patience - a key skill in oceanography! - data download may take some time.\n",
"\n",
- "## Start your expedition\n",
- "`virtualship run GROUP_YOURGROUPNUMBER`\n",
+ "### Start your expedition\n",
+ "`virtualship run GROUP#`\n",
"\n",
- "## Verify your expedition was successful by checking the output directory for data files\n",
+ "### Verify your expedition was successful by checking the output directory for data files\n",
"- Navigate to the results directory:\n",
- "`cd GROUP_YOURGROUPNUMBER/results`\n",
- "- Send the link to your results in via Brightspace before Wednesday 19-11-2025 13:00h.\n",
+ "`cd GROUP#/results`\n",
+ "- Hand in the filepath to your results in via Brightspace before Wednesday 19-11-2025 13:00h.\n",
"`pwd`"
]
},
From ed57b2756a95de41a38844d0bb6b3cd4eb666455 Mon Sep 17 00:00:00 2001
From: Emma Daniels
Date: Tue, 11 Nov 2025 11:59:35 +0100
Subject: [PATCH 06/26] update material
---
.../UU-ocean-of-future/Presentation.html | 688 ++++++++++++++++++
.../UU-ocean-of-future/Presentation.qmd | 67 +-
.../UU-ocean-of-future/Tutorial1.ipynb | 33 +-
3 files changed, 756 insertions(+), 32 deletions(-)
create mode 100644 docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html
new file mode 100644
index 00000000..a1dad3f2
--- /dev/null
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html
@@ -0,0 +1,688 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Ocean observations
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Water Mass Identification: Characterize ocean layers and circulation patterns
+
High Vertical Resolution: Continuous profiling from surface to seafloor
+
Targeted Sampling: Niskin bottles collect water at specific depths of interest
+
Calibration Standard: Validates satellite and autonomous sensor data
+
+
+
+
+
+
+
+
+
+
+
+
Plankton measurements
+
+
In-Situ Imaging
+
Water Sampling and preserving
+
Chlorophyll Analysis
+
eDNA Filtering
+
Tows: nets or CPR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
VirtualShip expedition
+
+
9 days of ship time
+
Depart and arrive from Texel, Netherlands
+
Straight transect starting from the deep shelve
+
Inslingeren: (zeem.) opnieuw wennen aan het zeemansleven
+
Travel and CTD deployment time
+
SURF Research Cloud setup by Jamie
+
Instructions in Jupyter Notebook
+
+
+
+
Problems at sea and VR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
index 01a9246b..ed611df0 100644
--- a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.qmd
@@ -1,7 +1,7 @@
---
title: "Ocean observations"
subtitle: "Ocean of the future 13-11-2025"
-author: "Emma Daniels & Jamie Atkins"
+author: "Emma Daniels & Jamie Atkins Postdocs @ UU Virtual Ship Classroom"
format:
revealjs:
slide-number: true
@@ -20,18 +20,20 @@ title-slide-attributes:
##

+## {background-iframe="https://wordwall.net/embed/4f6b5ced54d644c2bab35354305bb0eb?themeId=65&templateId=30&fontStackId=1" background-interactive="true"}
+
## Difficulties of Measuring
- **Vast Scale**: Covers 71% of Earth's surface, average depth 3,688m
-- **Accessibility**: Remote locations, harsh weather, high operational costs
+- **Real-Time Monitoring**: Difficulty transmitting data from deep or remote locations
- **Extreme conditions**: Pressure, storms and salt require specialized equipment
+- **Accessibility**: Remote locations, harsh weather, high operational costs
- **Light Limitation**: Optical methods only work in top ~200m
-- **Real-Time Monitoring**: Difficulty transmitting data from deep or remote locations
## Satellite Observations

-Note that it's not possible to investigate the interior ocean
+Note that it's not possible to investigate the interior ocean with satellites
## Satellite Observations
@@ -46,6 +48,9 @@ Note that it's not possible to investigate the interior ocean
- Cannot penetrate deep into the ocean
:::
+## PACE Satellite (2024)
+
+
## Light Attenuation in the Ocean

@@ -58,23 +63,24 @@ Note that it's not possible to investigate the interior ocean
- Cabled networks and instruments
- Resurfacing equipment
-## PACE satellite ('24)
-
-
-## Observation * calibration Methods
+
+
+## Observation Methods
+
-## HMS Challenger
-First major scientific oceanographic expedition (1872-1876)
+## HMS Challenger (1872-1876)

-## Alternative Methods
-
+##
+```{=html}
+
+```
## Ship-Based Measurements
@@ -98,14 +104,19 @@ First major scientific oceanographic expedition (1872-1876)
- **Targeted Sampling**: Niskin bottles collect water at specific depths of interest
- **Calibration Standard**: Validates satellite and autonomous sensor data
+##
+
+
+##
+
+
## Plankton measurements
-- Tows: nets or CPR
- In-Situ Imaging
-- Water Sampling
+- Water Sampling and preserving
- Chlorophyll Analysis
- eDNA Filtering
-- Preserving Samples
+- Tows: nets or CPR
##

@@ -113,14 +124,26 @@ First major scientific oceanographic expedition (1872-1876)
##

-##
-
-
-##
-
-
##

##

+
+##
+```{=html}
+
+```
+
+## VirtualShip expedition
+
+- 9 days of ship time
+- Depart and arrive from Texel, Netherlands
+- Straight transect starting from the deep shelve
+- Inslingeren: (zeem.) opnieuw wennen aan het zeemansleven
+- Travel and CTD deployment time
+- SURF Research Cloud setup by Jamie
+- Instructions in Jupyter Notebook
+
+## Problems at sea and VR
+
\ No newline at end of file
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
index 122c1b0d..3eac8b76 100644
--- a/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Tutorial1.ipynb
@@ -7,7 +7,7 @@
"source": [
"# Virtual Ship exercise\n",
"You can work on the exercise either individually or in pairs. You will work with the same person during the tutorials on November 17th as well. \n",
- "You will plan and execute a virtual oceanographic expedition using the Virtual Ship software. Follow the steps below to complete the exercise.\n",
+ "You will plan and execute a virtual oceanographic expedition using the VirtualShip software. Follow the steps below to complete the exercise.\n",
"\n",
"### Form and register your duo (preferably with different backgrounds) through Teams\n",
"- Choose a month in the summer half year in which you want to do the expedition (i.e. April - October)\n",
@@ -29,6 +29,7 @@
"Replace # with your actual group number (e.g., GROUP1)\n",
"\n",
"- Decide on the exact timing of your expedition with in the year and month registered though Teams\n",
+ "- Remember you have 9 days of ship time available, including travel time from and to Texel, Netherlands\n",
"\n",
"### Follow the instructions in the terminal to set up your expedition\n",
"- From the Schedule Editor select Waypoints & Instrument Selection\n",
@@ -37,12 +38,27 @@
"- Save your changes\n",
"\n",
"### Fetch the data needed for your expedition\n",
- "`virtualship fetch GROUP# --username --password `\n",
+ "```\n",
+ "virtualship fetch GROUP# --username --password \n",
+ "```\n",
"Replace GROUP# with your actual expedition name (e.g., GROUP1)\n",
"\n",
"- Provide your Copernicus username and password when prompted\n",
"- (Sign up for a Copernicus account if you don't have one yet: https://data.marine.copernicus.eu/register)\n",
- "- Practice your patience - a key skill in oceanography! - data download may take some time.\n",
+ "- Practice your patience - a key skill in oceanography! - data download may take some time ;-)\n",
+ "\n",
+ "
\n",
+ "In the waiting time you can learn about (life on board) research vessels:\n",
+ "\n",
+ "- https://www.youtube.com/watch?v=hUl0TA-gCK0\n",
+ "\n",
+ "- https://www.youtube.com/watch?v=G82kIgc1imk\n",
+ "\n",
+ "- https://schmidtocean.org/cruise-log-post/four-unexpected-things-i-learned-while-working-on-a-research-vessel/\n",
+ "\n",
+ "Or browse through some blogs from many different cruises, e.g. https://www.nioz.nl/en/blog/topic/1027\n",
+ "
\n",
+ "\n",
"\n",
"### Start your expedition\n",
"`virtualship run GROUP#`\n",
@@ -50,15 +66,12 @@
"### Verify your expedition was successful by checking the output directory for data files\n",
"- Navigate to the results directory:\n",
"`cd GROUP#/results`\n",
- "- Hand in the filepath to your results in via Brightspace before Wednesday 19-11-2025 13:00h.\n",
+ "- List the files in the results directory:\n",
+ "`ls`\n",
+ "\n",
+ "- Hand in the filepath to your results via Brightspace before Wednesday 19-11-2025 13:00h:\n",
"`pwd`"
]
- },
- {
- "cell_type": "markdown",
- "id": "7207b607",
- "metadata": {},
- "source": []
}
],
"metadata": {
From d1f1056a8a74b7128fca31b8a4e7fc7c54ce04ae Mon Sep 17 00:00:00 2001
From: j-atkins <106238905+j-atkins@users.noreply.github.com>
Date: Wed, 12 Nov 2025 14:08:09 +0100
Subject: [PATCH 07/26] add tutorial for set up on jupyter collaborative
workspace
---
.../tutorials/surf_collaborative_setup.ipynb | 97 +++++++++++++++++++
1 file changed, 97 insertions(+)
create mode 100644 docs/user-guide/tutorials/surf_collaborative_setup.ipynb
diff --git a/docs/user-guide/tutorials/surf_collaborative_setup.ipynb b/docs/user-guide/tutorials/surf_collaborative_setup.ipynb
new file mode 100644
index 00000000..bcd8d8b5
--- /dev/null
+++ b/docs/user-guide/tutorials/surf_collaborative_setup.ipynb
@@ -0,0 +1,97 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "98770716",
+ "metadata": {},
+ "source": [
+ "# SURF Resarch Cloud: VirtualShip environment setup\n",
+ "\n",
+ "```\n",
+ "Note: This guide is specific to students who are enrolled at Utrecht University.\n",
+ "```\n",
+ "\n",
+ "In the class, we will use VirtualShip in the cloud (in this case, SURF Research Cloud - called SURF RC from here-on). This has several advantages:\n",
+ "\n",
+ "- You aren't limited to the power of your laptop.\n",
+ "- Everyone can work in the same collaborative environment.\n",
+ "- The environment is pre-configured with all necessary software and dependencies.\n",
+ "\n",
+ "\n",
+ "## 1. Accepting SURF RC invite\n",
+ "\n",
+ "In your student email you'll have an invite from SURF Research Access Management (SRAM) to join a project on SURF RC. Accept this invite.\n",
+ "\n",
+ "## 2. Open the workspace\n",
+ "\n",
+ "Navigate to the [SURF Research Cloud Dashboard](https://portal.live.surfresearchcloud.nl/), and click \"access\" on the shared workspace.\n",
+ "\n",
+ "\n",
+ "## 3. Open Terminal session\n",
+ "\n",
+ "In the Jupyter launcher, you should see an option to open Terminal session. Click this to open Terminal.\n",
+ "\n",
+ "\n",
+ "## 4. Launch VirtualShip environment\n",
+ "\n",
+ "❗️ Before proceeding any further, you should type the following command in the Terminal: `bash` and then hit Enter. This will ensure that you are in a bash shell for the rest of the setup.\n",
+ "\n",
+ "You will see that the Terminal prompt has changed to something like\n",
+ "\n",
+ "```bash\n",
+ "(base) metheuser@mywsp:\n",
+ "```\n",
+ "\n",
+ "This is conda telling you that you are currently in the \"base\" environment.\n",
+ "\n",
+ "From here, you already have another environment set up for you. Running `conda env list` in the Terminal, you should see:\n",
+ "\n",
+ "```bash\n",
+ "conda env list\n",
+ "\n",
+ "# conda environments:\n",
+ "#\n",
+ "base * /etc/miniconda3\n",
+ "virtualship /etc/miniconda3/envs/virtualship\n",
+ "```\n",
+ "\n",
+ "Next, to launch the VirtualShip environment, type the following command in the Terminal and hit Enter:\n",
+ "\n",
+ "```bash\n",
+ "conda activate virtualship\n",
+ "```\n",
+ "\n",
+ "This will activate the VirtualShip conda environment, which has all the necessary dependencies installed. You can confirm that you are in the correct environment by checking that your Terminal prompt now starts with `(virtualship)`.\n",
+ "\n",
+ "With the `virtualship` environment, you now have access to the `virtualship` command in your Terminal, which can be confirmed by running `virtualship --help`.\n",
+ "\n",
+ "## 5. Navigate to the shared storage folder\n",
+ "\n",
+ "We will be working from a shared storage folder in the workspace. To navigate to this folder, type the following command in the Terminal and hit Enter:\n",
+ "\n",
+ "```bash\n",
+ "cd data/virtualship-storage/\n",
+ "```\n",
+ "\n",
+ "Depending on the specific set-up for the course you are taking, if you now enter `ls` to list the contents of the directory, you should see various group folders, for example: `group_1`, `group_2`, ... and so on.\n",
+ "\n",
+ "This is where will be working from for the rest of the course, with the group name for each folder corresponding to your assigned group.\n",
+ "\n",
+ "You are ready to continue with the VirtualShip analysis workflow as described in the course materials and/or VirtualShip [quickstart guide](https://virtualship.readthedocs.io/en/latest/user-guide/quickstart.html)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3926833e",
+ "metadata": {},
+ "source": []
+ }
+ ],
+ "metadata": {
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
From 7371346b6f462a9e03c1e73cfca692f0e423ed32 Mon Sep 17 00:00:00 2001
From: j-atkins <106238905+j-atkins@users.noreply.github.com>
Date: Wed, 12 Nov 2025 14:19:58 +0100
Subject: [PATCH 08/26] add collaborative workspace setup guide to index
---
docs/user-guide/tutorials/index.md | 1 +
.../tutorials/surf_collaborative_setup.ipynb | 47 +++++++++++++------
2 files changed, 34 insertions(+), 14 deletions(-)
diff --git a/docs/user-guide/tutorials/index.md b/docs/user-guide/tutorials/index.md
index 9b181aff..592aba69 100644
--- a/docs/user-guide/tutorials/index.md
+++ b/docs/user-guide/tutorials/index.md
@@ -6,6 +6,7 @@ maxdepth: 1
caption: Tutorials
---
surf_research_cloud_setup.ipynb
+surf_collaborative_setup.ipynb
ADCP_data_tutorial.ipynb
CTD_data_tutorial.ipynb
Drifter_data_tutorial.ipynb
diff --git a/docs/user-guide/tutorials/surf_collaborative_setup.ipynb b/docs/user-guide/tutorials/surf_collaborative_setup.ipynb
index bcd8d8b5..f932850e 100644
--- a/docs/user-guide/tutorials/surf_collaborative_setup.ipynb
+++ b/docs/user-guide/tutorials/surf_collaborative_setup.ipynb
@@ -5,7 +5,7 @@
"id": "98770716",
"metadata": {},
"source": [
- "# SURF Resarch Cloud: VirtualShip environment setup\n",
+ "# SURF Resarch Cloud: Collaborative Workspace Setup Guide\n",
"\n",
"```\n",
"Note: This guide is specific to students who are enrolled at Utrecht University.\n",
@@ -34,9 +34,12 @@
"\n",
"## 4. Launch VirtualShip environment\n",
"\n",
- "❗️ Before proceeding any further, you should type the following command in the Terminal: `bash` and then hit Enter. This will ensure that you are in a bash shell for the rest of the setup.\n",
+ "❗️ Before proceeding any further, you should type the following command in the Terminal and then hit Enter: `bash` ❗️\n",
"\n",
- "You will see that the Terminal prompt has changed to something like\n",
+ "\n",
+ "This will ensure that you are in a bash shell for the rest of the setup.\n",
+ "\n",
+ "You will see that the Terminal prompt has changed to something like:\n",
"\n",
"```bash\n",
"(base) metheuser@mywsp:\n",
@@ -55,27 +58,43 @@
"virtualship /etc/miniconda3/envs/virtualship\n",
"```\n",
"\n",
- "Next, to launch the VirtualShip environment, type the following command in the Terminal and hit Enter:\n",
+ "Next, to launch the VirtualShip environment, type the following command in the Terminal and hit Enter: `conda activate virtualship`\n",
+ "\n",
+ "This will activate the VirtualShip conda environment, which has all the necessary dependencies installed. You can confirm that you are in the correct environment by checking that your Terminal prompt now looks something like:\n",
"\n",
"```bash\n",
- "conda activate virtualship\n",
+ "(virtualship) metheuser@mywsp:\n",
"```\n",
"\n",
- "This will activate the VirtualShip conda environment, which has all the necessary dependencies installed. You can confirm that you are in the correct environment by checking that your Terminal prompt now starts with `(virtualship)`.\n",
+ "With the `virtualship` environment, you now have access to the `virtualship` command in your Terminal.\n",
"\n",
- "With the `virtualship` environment, you now have access to the `virtualship` command in your Terminal, which can be confirmed by running `virtualship --help`.\n",
+ "This can be confirmed by typing the following command in the Terminal and hitting Enter: `virtualship --help`.\n",
"\n",
- "## 5. Navigate to the shared storage folder\n",
- "\n",
- "We will be working from a shared storage folder in the workspace. To navigate to this folder, type the following command in the Terminal and hit Enter:\n",
+ "You should see something like:\n",
"\n",
- "```bash\n",
- "cd data/virtualship-storage/\n",
"```\n",
+ "virtualship --help\n",
+ "\n",
+ "Usage: virtualship [OPTIONS] COMMAND [ARGS]...\n",
+ "\n",
+ "Options:\n",
+ " --version Show the version and exit.\n",
+ " --help Show this message and exit.\n",
+ "\n",
+ "Commands:\n",
+ " fetch Download input data for an expedition.\n",
+ " init Initialize a directory for a new expedition, with an example...\n",
+ " plan Launch UI to help build schedule and ship config files.\n",
+ " run Run the expedition.\n",
+ " ```\n",
+ "\n",
+ "## 5. Navigate to the shared storage folder\n",
+ "\n",
+ "We will be working from a shared storage folder in the workspace. To navigate to this folder, type the following command in the Terminal and hit Enter: `cd data/virtualship-storage/`\n",
"\n",
- "Depending on the specific set-up for the course you are taking, if you now enter `ls` to list the contents of the directory, you should see various group folders, for example: `group_1`, `group_2`, ... and so on.\n",
+ "Depending on the specific set-up for the course you are taking, if you now enter `ls` to list the contents of the directory, you may see various group folders, for example: `group_1`, `group_2`, ... and so on.\n",
"\n",
- "This is where will be working from for the rest of the course, with the group name for each folder corresponding to your assigned group.\n",
+ "If you have been pre-assigned into groups, this is where will be working from for the rest of the course, with the group name for each folder corresponding to your assigned group.\n",
"\n",
"You are ready to continue with the VirtualShip analysis workflow as described in the course materials and/or VirtualShip [quickstart guide](https://virtualship.readthedocs.io/en/latest/user-guide/quickstart.html)."
]
From 6d79d13e9fa5f4012acf4f15ad621dee3a3256db Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Wed, 12 Nov 2025 13:27:36 +0000
Subject: [PATCH 09/26] [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---
.../UU-ocean-of-future/Presentation.html | 1089 ++++++++++-------
.../UU-ocean-of-future/Presentation.qmd | 10 +-
2 files changed, 675 insertions(+), 424 deletions(-)
diff --git a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html
index a1dad3f2..9e12a697 100644
--- a/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html
+++ b/docs/user-guide/teacher-content/UU-ocean-of-future/Presentation.html
@@ -1,343 +1,557 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Ocean observations
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-