1313from __future__ import annotations
1414
1515import enum
16+ import re
1617import typing
1718import warnings
1819from typing import cast
1920
2021import numpy as np
22+ import scipy .io as sio
2123import xarray as xr
2224
2325import parcels ._sgrid as sgrid
@@ -567,16 +569,23 @@ def copernicusmarine_to_sgrid(
567569 return ds
568570
569571
570- def swash_to_sgrid (coord_file , data_file , total_depth ) -> xr .Dataset :
571- """Create an sgrid-compliant xarray.Dataset from a dataset of SWASH netcdf files."""
572- import re
572+ def swash_to_sgrid (data_file : str , coord_file : str , total_depth : float ) -> xr .Dataset :
573+ """Create an sgrid-compliant xarray.Dataset from a dataset of SWASH netcdf files.
573574
574- import pandas as pd
575- import scipy .io as sio
576-
577- import parcels ._sgrid as sgrid
575+ Parameters
576+ ----------
577+ data_file : str
578+ Path to the SWASH data file (MATLAB binary format).
579+ coord_file : str
580+ Path to the SWASH coordinate file (MATLAB binary format).
581+ total_depth : float
582+ Total depth of the water column.
578583
579- ## First load the coordinates and data files (SWASH output - matlab binary format)
584+ Returns
585+ -------
586+ xarray.Dataset
587+ Dataset object following SGRID conventions to be (optionally) modified and passed to a FieldSet constructor.
588+ """
580589 coord = sio .loadmat (coord_file )
581590 x = coord ["Xp" ][0 , :]
582591 y = coord ["Yp" ][:, 0 ]
@@ -588,17 +597,18 @@ def swash_to_sgrid(coord_file, data_file, total_depth) -> xr.Dataset:
588597 time_keys = sorted (
589598 set ((int (m .group (1 )), int (m .group (2 ))) for k in keys for m in [re .search (r"_(\d{6})_(\d{3})$" , k )] if m )
590599 )
591- times = [t [0 ] + t [1 ] / 1000 for t in time_keys ]
600+ times = np . array ( [t [0 ] * 1000 + t [1 ] for t in time_keys ]). astype ( "timedelta64[ms]" )
592601
593602 n_layers = max (int (re .search (r"Vksi_k(\d+)_" , k ).group (1 )) for k in keys if re .search (r"Vksi_k(\d+)_" , k ))
594- n_w_layers = n_layers
595-
596- nx , ny , nt = len ( x ), len ( y ), len ( times )
603+ n_layers_f = n_layers + 1
604+ depth_centers = np . array ([ total_depth * ( i - 0.5 ) / n_layers for i in range ( 1 , n_layers + 1 )], dtype = np . float32 )
605+ depth_interfaces = np . array ([ total_depth * i / n_layers for i in range ( n_layers_f )], dtype = np . float32 )
597606
607+ nt , ny , nx = len (times ), len (y ), len (x )
598608 watlev = np .full ((nt , ny , nx ), np .nan , dtype = np .float32 )
599609 vksi = np .full ((nt , n_layers , ny , nx ), np .nan , dtype = np .float32 )
600610 veta = np .full ((nt , n_layers , ny , nx ), np .nan , dtype = np .float32 )
601- w = np .full ((nt , n_w_layers , ny , nx ), np .nan , dtype = np .float32 )
611+ w = np .full ((nt , n_layers_f , ny , nx ), np .nan , dtype = np .float32 )
602612
603613 for ti , (ts_int , ts_dec ) in enumerate (time_keys ):
604614 ts_str = f"{ ts_int :06d} _{ ts_dec :03d} "
@@ -612,57 +622,29 @@ def swash_to_sgrid(coord_file, data_file, total_depth) -> xr.Dataset:
612622 if m :
613623 veta [ti , int (m .group (1 )) - 1 , :, :] = mat [k ]
614624 m = re .match (rf"w(\d+)_{ ts_str } $" , k )
615- if m and int (m .group (1 )) < n_w_layers :
625+ if m and int (m .group (1 )) < n_layers :
616626 w [ti , int (m .group (1 )), :, :] = mat [k ]
617627
618- t0 = pd .Timestamp ("2026-06-01 00:00:00" )
619- time_dt = np .array ([t0 + pd .to_timedelta (t , unit = "s" ) for t in times ], dtype = "datetime64[ns]" )
620-
621628 ds = xr .Dataset (
622629 {
623- "watlev" : (["time" , "y " , "x " ], watlev ),
624- "U" : (["time" , "depth" , "y " , "x " ], vksi ),
625- "V" : (["time" , "depth" , "y " , "x " ], veta ),
626- "W" : (["time" , "depth_f" , "y " , "x " ], w ),
627- "botlev" : (["y " , "x " ], bot ),
630+ "watlev" : (["time" , "lat " , "lon " ], watlev ),
631+ "U" : (["time" , "depth" , "lat " , "lon " ], vksi ),
632+ "V" : (["time" , "depth" , "lat " , "lon " ], veta ),
633+ "W" : (["time" , "depth_f" , "lat " , "lon " ], w ),
634+ "botlev" : (["lat " , "lon " ], bot ),
628635 },
629636 coords = {
630- "time" : time_dt ,
631- "depth" : np . arange ( 1 , n_layers + 1 ),
632- "depth_f" : np . arange ( 0 , n_w_layers ),
633- "y " : y ,
634- "x " : x ,
637+ "time" : ([ "time" ], times , { "axis" : "T" , "units" : "ms" }) ,
638+ "depth" : ([ "depth" ], depth_centers , { "axis" : "Z" , "units" : "m" , "positive" : "down" } ),
639+ "depth_f" : ([ "depth_f" ], depth_interfaces , { "axis" : "Z" , "units" : "m" , "positive" : "down" } ),
640+ "lat " : ([ "lat" ], y , { "axis" : "Y" , "units" : "m" }) ,
641+ "lon " : ([ "lon" ], x , { "axis" : "X" , "units" : "m" }) ,
635642 },
636643 )
637- ds .attrs .update (source = "SWASH version 11.01ABC" , project = "progWave" , run = "A14" , Conventions = "CF-1.8" )
638-
639- n_layers = ds .sizes ["depth" ]
640- n_layers_f = ds .sizes ["depth_f" ]
641- if n_layers_f != n_layers :
642- raise ValueError (f"Expected depth_f to match depth in length (got { n_layers_f } vs { n_layers } )" )
643-
644- depth_centers = np .array ([total_depth * (i - 0.5 ) / n_layers for i in range (1 , n_layers + 1 )], dtype = np .float32 )
645- depth_interfaces = np .array ([total_depth * i / n_layers for i in range (n_layers_f )], dtype = np .float32 )
646-
647- # Rename x/y -> lon/lat: Parcels' from_sgrid_conventions expects these names
648- # literally, even on a flat/Cartesian mesh (units stay in meters).
649- ds = ds .rename ({"x" : "lon" , "y" : "lat" })
650-
651- ds = ds .assign_coords (
652- {
653- "depth" : ("depth" , depth_centers ),
654- "depth_f" : ("depth_f" , depth_interfaces ),
655- }
656- )
657-
658- ds ["time" ].attrs .update (axis = "T" )
659- ds ["lon" ].attrs .update (axis = "X" , units = "m" )
660- ds ["lat" ].attrs .update (axis = "Y" , units = "m" )
661- ds ["depth" ].attrs .update (axis = "Z" , units = "m" , positive = "down" )
662- ds ["depth_f" ].attrs .update (axis = "Z" , units = "m" , positive = "down" )
663-
664- if "grid" in ds .cf .cf_roles :
665- raise ValueError ("Dataset already has a 'grid' variable (cf_role grid_topology)." )
644+ header = mat ["__header__" ]
645+ if isinstance (header , bytes ):
646+ header = header .decode ("utf-8" )
647+ ds .attrs .update (header = header , version = mat ["__version__" ], globals = mat ["__globals__" ])
666648
667649 ds ["grid" ] = xr .DataArray (
668650 0 ,
0 commit comments