From d97165a8be8894a0d1aabbee4d5f9d5b5ac44c3e Mon Sep 17 00:00:00 2001 From: Mike McCann Date: Wed, 22 Oct 2025 10:23:10 -0700 Subject: [PATCH 1/3] Utility script for seafloor mapping in Monterey Bay in lieu of using XBTs. --- src/data/m1_soundspeed.py | 132 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100755 src/data/m1_soundspeed.py diff --git a/src/data/m1_soundspeed.py b/src/data/m1_soundspeed.py new file mode 100755 index 00000000..43737d6d --- /dev/null +++ b/src/data/m1_soundspeed.py @@ -0,0 +1,132 @@ +#! /usr/bin/env python +""" +Read most recent profile of temperature and practical salinity from the MBARI M1 +mooring in Monterey Bay and return a profile of sound speed as a function of +depth. + +This uses the opendap URL produced on an hourly basis as part of MBARI's SSDS +realtime data system. + +Using Ferret to access the data: +================================ +The most recent profile is retrieved using the SET REGION/L=2156:2156 statement +where the number 2156 is seen as the last index for the L axis (TIME) seen in +the output of the SHOW DATA/VAR statement. Below is a terminal session showing +how to access the data: + +[ssdsadmin@elvis ~]$ ferret + NOAA/PMEL TMAP + FERRET v7.43 (optimized) + Linux 3.10.0-862.11.6.el7.x86_64 64-bit - 09/14/18 + 22-Oct-25 09:20 + +yes? USE "http://dods.mbari.org/opendap/data/ssdsdata/deployments/m1/202507/OS_MBARI-M1_20250724_R_TS.nc" +yes? SHOW DATA/VAR + currently SET data sets: + 1> http://dods.mbari.org/opendap/data/ssdsdata/deployments/m1/202507/OS_MBARI-M1_20250724_R_TS.nc (default) + Hourly Gridded MBARI Mooring M1 Sea Water Temperature and Salinity Observations + name title I J K L + PSAL Hourly sea_water_salinity 1:1 1:1 1:11 1:2156 + 1 on grid GEN1 with -1.E+34 for missing data + X=122.5W(-122.5):121.5W(-121.5) Y=36.3N:37.3N Z=0:325 + PSAL_QC quality flag 1:1 1:1 1:11 1:2156 + on grid GEN1 with -1.E+34 for missing data + X=122.5W(-122.5):121.5W(-121.5) Y=36.3N:37.3N Z=0:325 + TEMP Hourly sea_water_temperature 1:1 1:1 1:11 1:2156 + celsius on grid GEN1 with -1.E+34 for missing data + X=122.5W(-122.5):121.5W(-121.5) Y=36.3N:37.3N Z=0:325 + TEMP_QC quality flag 1:1 1:1 1:11 1:2156 + on grid GEN1 with -1.E+34 for missing data + X=122.5W(-122.5):121.5W(-121.5) Y=36.3N:37.3N Z=0:325 + TIME_QC Quality flag for time axis, 1: ... ... ... 1:2156 + flag on grid GEN2 with -1.E+34 for missing data + + POSITION_QC + Quality flag for Latitude and L 1:1 ... ... ... + on grid GEN3 with -1.E+34 for missing data + X=122.5W(-122.5):121.5W(-121.5) + DEPTH_QC Quality flag for depth axis, 1: ... ... 1:11 ... + on grid GEN4 with -1.E+34 for missing data + Z=0:325 + + time range: 24-JUL-2025 18:30 to 22-OCT-2025 13:30 + +yes? SET REGION/L=2156:2156 +yes? LIST TEMP, PSAL + DATA SET: http://dods.mbari.org/opendap/data/ssdsdata/deployments/m1/202507/OS_MBARI-M1_20250724_R_TS.nc + Hourly Gridded MBARI Mooring M1 Sea Water Temperature and Salinity Observations + DEPTH (m): 0 to 325 + LONGITUDE: 122W(-122) + LATITUDE: 36.8N + TIME: 22-OCT-2025 13:30 + Column 1: TEMP is Hourly sea_water_temperature (celsius) + Column 2: PSAL is Hourly sea_water_salinity (1) + TEMP PSAL +1 / 1: 16.38 33.32 +10 / 2: 16.39 33.32 +20 / 3: 15.43 33.28 +40 / 4: 13.30 33.42 +60 / 5: 11.95 33.51 +80 / 6: 11.33 33.61 +100 / 7: 11.01 33.63 +150 / 8: 10.09 33.81 +200 / 9: 9.67 33.93 +250 / 10: 9.12 34.08 +300 / 11: 7.92 34.09 +yes? quit + + +Using Python to access the data: +================================ +The Xarray library and variety of Python packages provides similar ease-of-use +capability in more modern computational environments. This module provides that +implementation. There are two dependencies that need to be installed via pip or +some other package manager: + gsw + xarray + +e.g. pip install gsw xarray + +__author__ = "Mike McCann" +__copyright__ = "Copyright 2025, Monterey Bay Aquarium Research Institute" +""" # noqa: E501 + +import gsw +import xarray as xr + +# Source for realtime M1 mooring data +url = ( + "http://dods.mbari.org/opendap/data/ssdsdata/deployments/m1/202507/OS_MBARI-M1_20250724_R_TS.nc" +) +ds = xr.open_dataset(url) + +# Select the most recent profile by indexing the TIME dimension +latest = ds.isel(TIME=-1) +temp = latest["TEMP"].to_numpy.flatten() +psal = latest["PSAL"].to_numpy.flatten() +depth = latest["DEPTH"].to_numpy.flatten() + +# Convert practical salinity to absolute salinity using lat and lon of M1 +# mooring from the index data in the dataset +lon = ds["LONGITUDE"].to_numpy.item() +lat = ds["LATITUDE"].to_numpy.item() +abs_sal = gsw.SA_from_SP(psal, depth, lon, lat) + +# Print out a header showing time, lat, lon and data source similar to Ferret output +time_str = str(latest["TIME"].to_numpy) +time_str = time_str.split(".")[0] + " UTC" # Remove fractional seconds +print("Most recent sound speed profile from M1 mooring") # noqa: T201 +print("===============================================") # noqa: T201 +print(f"Data source: {url}") # noqa: T201 +print(f"Title: {ds.title}") # noqa: T201 +print(f"Latitude: {lat:.2f}") # noqa: T201 +print(f"Longitude: {lon:.2f}") # noqa: T201 +print(f"Time: {time_str}") # noqa: T201 +print() # noqa: T201 + +# Calculate sound speed using the Gibbs Seawater (GSW) Oceanographic Toolbox +# Print out the profile of sound speed as a table +soundspeed = gsw.sound_speed(abs_sal, temp, depth) +print(f"{'Depth (m)':>10} {'Sound Speed (m/s)':>20}") # noqa: T201 +for d, c in zip(depth, soundspeed, strict=True): + print(f"{d:10.2f} {c:20.2f}") # noqa: T201 From 7f85fd17890b8c778f00131174021c109fa57e46 Mon Sep 17 00:00:00 2001 From: Mike McCann Date: Wed, 22 Oct 2025 10:53:15 -0700 Subject: [PATCH 2/3] Fixes for ruff and add installation instructions. --- src/data/m1_soundspeed.py | 56 ++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/data/m1_soundspeed.py b/src/data/m1_soundspeed.py index 43737d6d..6a5d8f39 100755 --- a/src/data/m1_soundspeed.py +++ b/src/data/m1_soundspeed.py @@ -85,8 +85,45 @@ gsw xarray -e.g. pip install gsw xarray - +Installation: +------------- +1. Create a directory to hold this script and a virtual environment: + mkdir m1_soundspeed + cd m1_soundspeed +2. Create a virtual environment (optional but recommended): + python3 -m venv venv +3. Activate the virtual environment: + source venv/bin/activate +4. Install the required packages: + pip install gsw xarray +5. Save this script as m1_soundspeed.py +6. Run the script: + python m1_soundspeed.py + +Sample Output: +============== +python m1_soundspeed.py + +Most recent sound speed profile from M1 mooring +----------------------------------------------- +Data source: http://dods.mbari.org/opendap/data/ssdsdata/deployments/m1/202507/OS_MBARI-M1_20250724_R_TS.nc +Title: Hourly Gridded MBARI Mooring M1 Sea Water Temperature and Salinity Observations +Latitude: 36.75 +Longitude: -122.03 +Time: 2025-10-22T14:30:00 UTC + + Depth (m) Sound Speed (m/s) + 1.00 1508.89 + 10.00 1508.96 + 20.00 1505.97 + 40.00 1501.15 + 60.00 1496.70 + 80.00 1494.31 + 100.00 1493.70 + 150.00 1490.97 + 200.00 1490.73 + 250.00 1489.76 + 300.00 1486.44 __author__ = "Mike McCann" __copyright__ = "Copyright 2025, Monterey Bay Aquarium Research Institute" """ # noqa: E501 @@ -102,21 +139,22 @@ # Select the most recent profile by indexing the TIME dimension latest = ds.isel(TIME=-1) -temp = latest["TEMP"].to_numpy.flatten() -psal = latest["PSAL"].to_numpy.flatten() -depth = latest["DEPTH"].to_numpy.flatten() +temp = latest["TEMP"].to_numpy().flatten() +psal = latest["PSAL"].to_numpy().flatten() +depth = latest["DEPTH"].to_numpy().flatten() # Convert practical salinity to absolute salinity using lat and lon of M1 # mooring from the index data in the dataset -lon = ds["LONGITUDE"].to_numpy.item() -lat = ds["LATITUDE"].to_numpy.item() +lon = ds["LONGITUDE"].to_numpy().item() +lat = ds["LATITUDE"].to_numpy().item() abs_sal = gsw.SA_from_SP(psal, depth, lon, lat) # Print out a header showing time, lat, lon and data source similar to Ferret output -time_str = str(latest["TIME"].to_numpy) +time_str = str(latest["TIME"].to_numpy()) time_str = time_str.split(".")[0] + " UTC" # Remove fractional seconds +print() # noqa: T201 print("Most recent sound speed profile from M1 mooring") # noqa: T201 -print("===============================================") # noqa: T201 +print("-----------------------------------------------") # noqa: T201 print(f"Data source: {url}") # noqa: T201 print(f"Title: {ds.title}") # noqa: T201 print(f"Latitude: {lat:.2f}") # noqa: T201 From 80450c7f7473a5ea7b22140ce095576c4532e048 Mon Sep 17 00:00:00 2001 From: Mike McCann Date: Wed, 22 Oct 2025 12:14:43 -0700 Subject: [PATCH 3/3] Add gsw as we need to migrate from seawater. --- pyproject.toml | 1 + uv.lock | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9a21f413..f6684c65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "datashader>=0.18.1", "defusedxml>=0.7.1", "gitpython>=3.1.44", + "gsw>=3.6.20", "hvplot>=0.11.3", "ipympl>=0.9.7", "jupyter>=1.1.1", diff --git a/uv.lock b/uv.lock index e3c41136..82bbff19 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = "==3.12.*" [[package]] @@ -175,6 +175,7 @@ dependencies = [ { name = "datashader" }, { name = "defusedxml" }, { name = "gitpython" }, + { name = "gsw" }, { name = "hvplot" }, { name = "ipympl" }, { name = "jupyter" }, @@ -206,6 +207,7 @@ requires-dist = [ { name = "datashader", specifier = ">=0.18.1" }, { name = "defusedxml", specifier = ">=0.7.1" }, { name = "gitpython", specifier = ">=3.1.44" }, + { name = "gsw", specifier = ">=3.6.20" }, { name = "hvplot", specifier = ">=0.11.3" }, { name = "ipympl", specifier = ">=0.9.7" }, { name = "jupyter", specifier = ">=1.1.1" }, @@ -624,6 +626,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, ] +[[package]] +name = "gsw" +version = "3.6.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/39/edd76e26b0c8b8a6bcee0107cbcee5219673bb59f274b757de9f989a0fb1/gsw-3.6.20.tar.gz", hash = "sha256:e528cd6563fdc09b244387bfebf131b01199c20ac248f4e5b4eaf00cded1abe6", size = 2702713, upload-time = "2025-08-04T18:04:14.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d9/18382b8fe6e8736bad967dd4ed8ab2c2deabbb9f6121d9e41265e7317f24/gsw-3.6.20-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9656dcb42ddeee8134f2bb6d7394928b0b8629634c9e223f9cce7a3c7309597c", size = 2222002, upload-time = "2025-08-04T18:03:34.123Z" }, + { url = "https://files.pythonhosted.org/packages/9f/85/3a9ba4372ac4291e38e887ed8dac44c0385d4b72ee967a7858c4c7a48d96/gsw-3.6.20-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:857a1f0804980186514a0690e0f7dbdffd15a17059649771f3d3a84771e8fb8f", size = 2261350, upload-time = "2025-08-04T18:03:35.481Z" }, + { url = "https://files.pythonhosted.org/packages/dc/36/c3d845de2e453a01f6b1cb099c63ab63c581814d638890c143d064a33a8d/gsw-3.6.20-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b5a143b2993ac150c5b3cb7edf942d1376a20abbc57cc3d8ec4a5a430632890", size = 2400962, upload-time = "2025-08-04T18:03:37.194Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f1/5b6999c89b3ea20cd9ac1169e0cd7c820a881ca97d6b34c7899da28a3d17/gsw-3.6.20-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33ca2560378d1719fa49dcd380ce0c4a261b01cbd2aa865a3c6c99bfb90b5853", size = 2443576, upload-time = "2025-08-04T18:03:38.782Z" }, + { url = "https://files.pythonhosted.org/packages/13/ed/419237d32a704e4b4bbfcdec8129fbb381ccdf2e33a2cc7d1153c1a1eaa0/gsw-3.6.20-cp312-cp312-win_amd64.whl", hash = "sha256:719d1983bd97991e4e44c1c725322269fc7019c29abc7a641e6a676f1a54f54e", size = 2180514, upload-time = "2025-08-04T18:03:40.217Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1387,6 +1405,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/1a/32b7427aaf62fed3d4e4456f874b25ce39373dbddf6cfde9edbcfc2417fc/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb95b11804fe051897d1f2044b05d82a1847bc2549631cdd2f655dde7de77a9c", size = 9377415, upload-time = "2024-10-22T19:00:54.412Z" }, { url = "https://files.pythonhosted.org/packages/fd/bf/5e671495c8bdf6b628e091aa8980793579474a10e51bc6ba302a3af6a778/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d8a848373723f41ef662590b4f5e1832227501c9fd4513e8ad8da58c269977", size = 9260579, upload-time = "2024-10-22T19:00:56.594Z" }, { url = "https://files.pythonhosted.org/packages/d4/57/0a0bcdebcfaf72e96e7bcaa512f80ee096bf71945a3318d38253338e9c25/netCDF4-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:568ea369e00b581302d77fc5fd0b8f78e520c7e08d0b5af5219ba51f3f1cd694", size = 6991523, upload-time = "2024-10-22T19:00:58.97Z" }, + { url = "https://files.pythonhosted.org/packages/84/0a/182bb4fe5639699ba39d558b553b8e6f04fbfea6cf78404c0f21ef149bf7/netcdf4-1.7.2-cp311-abi3-macosx_13_0_x86_64.whl", hash = "sha256:7e81c3c47f2772eab0b93fba8bb05b17b58dce17720e1bed25e9d76551deecd0", size = 2751391, upload-time = "2025-10-13T18:32:22.749Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1f/54ac27c791360f7452ca27ed1cb2917946bbe1ea4337c590a5abcef6332d/netcdf4-1.7.2-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:cb2791dba37fc98fd1ac4e236c97822909f54efbcdf7f1415c9777810e0a28f4", size = 2387513, upload-time = "2025-10-13T18:32:27.499Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5e/9bf3008a9e45c08f4c9fedce4d6f722ef5d970f56a9c5eb375a200dd2b66/netcdf4-1.7.2-cp311-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf11480f6b8a5b246818ffff6b4d90481e51f8b9555b41af0c372eb0aaf8b65f", size = 9621674, upload-time = "2025-10-13T18:32:29.193Z" }, + { url = "https://files.pythonhosted.org/packages/a1/75/46871e85f2bbfb1efe229623d25d7c9daa17e2e968d5235572b2c8bb53e8/netcdf4-1.7.2-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ccc05328a8ff31921b539821791aeb20b054879f3fdf6d1d505bf6422824fec", size = 9453759, upload-time = "2025-10-13T18:32:31.136Z" }, + { url = "https://files.pythonhosted.org/packages/cd/10/c52f12297965938d9b9be666ea1f9d8340c2aea31d6909d90aa650847248/netcdf4-1.7.2-cp311-abi3-win_amd64.whl", hash = "sha256:999bfc4acebf400ed724d5e7329e2e768accc7ee1fa1d82d505da782f730301b", size = 7148514, upload-time = "2025-10-13T18:32:33.121Z" }, ] [[package]]