OceanDataStore 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. OceanDataStore/__init__.py +21 -0
  2. OceanDataStore/catalog/__init__.py +12 -0
  3. OceanDataStore/catalog/oceandatacatalog.py +1242 -0
  4. OceanDataStore/catalog/stac/README.md +34 -0
  5. OceanDataStore/catalog/stac/__init__.py +30 -0
  6. OceanDataStore/catalog/stac/create_noc_stac.py +109 -0
  7. OceanDataStore/catalog/stac/npd_era5_collection.py +364 -0
  8. OceanDataStore/catalog/stac/npd_jra55_collection.py +196 -0
  9. OceanDataStore/catalog/stac/ods_obs_collection.py +534 -0
  10. OceanDataStore/catalog/stac/rapid_evo_collection.py +309 -0
  11. OceanDataStore/catalog/stac/template_collection.py +85 -0
  12. OceanDataStore/catalog/stac/utils.py +476 -0
  13. OceanDataStore/cli/__init__.py +34 -0
  14. OceanDataStore/cli/arg_parser.py +182 -0
  15. OceanDataStore/cli/cli.py +203 -0
  16. OceanDataStore/cli/exceptions.py +83 -0
  17. OceanDataStore/cli/icechunk.py +888 -0
  18. OceanDataStore/cli/logging.py +52 -0
  19. OceanDataStore/cli/object_store.py +293 -0
  20. OceanDataStore/cli/utils.py +275 -0
  21. OceanDataStore/cli/zarr.py +870 -0
  22. OceanDataStore/data/ARMOR3D/create_ARMOR3D_P1M-m_monthly_climatology.py +135 -0
  23. OceanDataStore/data/ARMOR3D/download_ARMOR3D_0.125def_P1M-m_1993_2024.py +33 -0
  24. OceanDataStore/data/ARMOR3D/run_create_ARMOR3D_P1M-m_monthly_climatology.slurm +32 -0
  25. OceanDataStore/data/ARMOR3D/run_send_ARMOR3D_P1M-m_climatology_to_os.slurm +32 -0
  26. OceanDataStore/data/ARMOR3D/run_send_ARMOR3D_P1M-m_monthly_to_os.slurm +32 -0
  27. OceanDataStore/data/ARMOR3D/run_update_ARMOR3D_P1m-m_monthly_to_os.slurm +32 -0
  28. OceanDataStore/data/ARMOR3D/send_ARMOR3D_P1m-m_monthly_climatology_to_os.py +99 -0
  29. OceanDataStore/data/ARMOR3D/send_ARMOR3D_P1m-m_monthly_to_os.py +147 -0
  30. OceanDataStore/data/ARMOR3D/update_ARMOR3D_P1m-m_monthly_to_os.py +143 -0
  31. OceanDataStore/data/EN.4.2.2/create_EN4.2.2_analysis_g10_climatology.py +162 -0
  32. OceanDataStore/data/EN.4.2.2/download_EN4.2.2_analysis_g10_data.sh +51 -0
  33. OceanDataStore/data/EN.4.2.2/run_send_EN4.2.2_analysis_g10_climatology_to_os.slurm +32 -0
  34. OceanDataStore/data/EN.4.2.2/run_send_EN4.2.2_analysis_g10_monthly_to_os.slurm +32 -0
  35. OceanDataStore/data/EN.4.2.2/run_update_EN4.2.2_analysis_g10_monthly_to_os.slurm +32 -0
  36. OceanDataStore/data/EN.4.2.2/send_EN4.2.2_analysis_g10_monthly_climatology_to_os.py +76 -0
  37. OceanDataStore/data/EN.4.2.2/send_EN4.2.2_analysis_g10_monthly_to_os.py +165 -0
  38. OceanDataStore/data/EN.4.2.2/update_EN4.2.2_analysis_g10_monthly_to_os.py +161 -0
  39. OceanDataStore/data/ERA5/create_ERA5_daily_climatology.py +110 -0
  40. OceanDataStore/data/ERA5/create_ERA5_daily_mean.py +69 -0
  41. OceanDataStore/data/ERA5/create_ERA5_monthly_mean.py +74 -0
  42. OceanDataStore/data/ERA5/run_create_ERA5_daily_climatology.slurm +54 -0
  43. OceanDataStore/data/ERA5/run_send_ERA5_daily_climatology_to_os.slurm +32 -0
  44. OceanDataStore/data/ERA5/run_send_ERA5_daily_to_os.slurm +32 -0
  45. OceanDataStore/data/ERA5/run_send_ERA5_monthly_to_os.slurm +32 -0
  46. OceanDataStore/data/ERA5/run_update_ERA5_daily_to_os.slurm +32 -0
  47. OceanDataStore/data/ERA5/run_update_ERA5_monthly_to_os.slurm +32 -0
  48. OceanDataStore/data/ERA5/send_ERA5_daily_climatology_to_os.py +159 -0
  49. OceanDataStore/data/ERA5/send_ERA5_daily_to_os.py +141 -0
  50. OceanDataStore/data/ERA5/send_ERA5_monthly_to_os.py +173 -0
  51. OceanDataStore/data/ERA5/update_ERA5_daily_to_os.py +141 -0
  52. OceanDataStore/data/ERA5/update_ERA5_monthly_to_os.py +169 -0
  53. OceanDataStore/data/HadISST/download_HadISST1_data.sh +43 -0
  54. OceanDataStore/data/HadISST/run_send_HadISST1_monthly_to_os.slurm +32 -0
  55. OceanDataStore/data/HadISST/send_HadISST1_monthly_to_os.py +133 -0
  56. OceanDataStore/data/NSIDC/download_NSIDC_monthly_1979_2025_data.sh +54 -0
  57. OceanDataStore/data/NSIDC/process_NSIDC_SSI_Antarctic_data.py +130 -0
  58. OceanDataStore/data/NSIDC/process_NSIDC_SSI_Arctic_data.py +129 -0
  59. OceanDataStore/data/NSIDC/run_send_NSIDC_v4.0_to_OS.slurm +32 -0
  60. OceanDataStore/data/NSIDC/send_NSIDC_SII_v4.0_to_os.py +140 -0
  61. OceanDataStore/data/OISST/create_OISSTv2_daily_climatology.py +83 -0
  62. OceanDataStore/data/OISST/download_oisstv2_data.sh +43 -0
  63. OceanDataStore/data/OISST/run_create_OISSTv2_daily_climatology.slurm +44 -0
  64. OceanDataStore/data/OISST/run_send_OISSTv2_daily_climatology_to_os.slurm +32 -0
  65. OceanDataStore/data/OISST/run_send_OISSTv2_daily_to_os.slurm +32 -0
  66. OceanDataStore/data/OISST/run_send_OISSTv2_monthly_climatology_to_os.slurm +32 -0
  67. OceanDataStore/data/OISST/run_send_OISSTv2_monthly_to_os.slurm +32 -0
  68. OceanDataStore/data/OISST/run_update_OISSTv2_daily_to_os.slurm +32 -0
  69. OceanDataStore/data/OISST/send_OISSTv2_daily_climatology_to_os.py +154 -0
  70. OceanDataStore/data/OISST/send_OISSTv2_daily_ltm_climatology_to_os.py +151 -0
  71. OceanDataStore/data/OISST/send_OISSTv2_daily_to_os.py +142 -0
  72. OceanDataStore/data/OISST/send_OISSTv2_monthly_climatology_to_os.py +150 -0
  73. OceanDataStore/data/OISST/send_OISSTv2_monthly_to_os.py +145 -0
  74. OceanDataStore/data/OISST/update_OISSTv2_daily_to_os.py +142 -0
  75. OceanDataStore/data/OSTIA/create_OSTIA_daily_climatology.py +120 -0
  76. OceanDataStore/data/OSTIA/download_OSTIA_NRT.py +42 -0
  77. OceanDataStore/data/OSTIA/download_OSTIA_REP_1981_2025.py +42 -0
  78. OceanDataStore/data/OSTIA/run_create_OSTIA_daily_climatology.slurm +54 -0
  79. OceanDataStore/data/OSTIA/run_send_OSTIA_daily_climatology_to_os.slurm +32 -0
  80. OceanDataStore/data/OSTIA/run_send_OSTIA_nrt_daily_to_os.slurm +32 -0
  81. OceanDataStore/data/OSTIA/run_send_OSTIA_rep_daily_to_os.slurm +32 -0
  82. OceanDataStore/data/OSTIA/run_update_OSTIA_daily_to_os.slurm +33 -0
  83. OceanDataStore/data/OSTIA/send_OSTIA_daily_climatology_to_os.py +194 -0
  84. OceanDataStore/data/OSTIA/send_OSTIA_nrt_daily_to_os.py +141 -0
  85. OceanDataStore/data/OSTIA/send_OSTIA_rep_daily_to_os.py +145 -0
  86. OceanDataStore/data/OSTIA/update_OSTIA_copernicus_nrt_daily_to_os.py +144 -0
  87. OceanDataStore/data/OSTIA/update_OSTIA_nrt_daily_to_os.py +137 -0
  88. OceanDataStore/data/WOA23/download_WOA23_climatology.sh +41 -0
  89. OceanDataStore/data/WOA23/run_send_WOA23_annual_climatology_to_os.slurm +32 -0
  90. OceanDataStore/data/WOA23/run_send_WOA23_monthly_climatology_to_os.slurm +32 -0
  91. OceanDataStore/data/WOA23/send_WOA23_annual_climatology_to_os.py +263 -0
  92. OceanDataStore/data/WOA23/send_WOA23_monthly_climatology_to_os.py +292 -0
  93. OceanDataStore/data/update_icechunk_repo_attrs.py +76 -0
  94. OceanDataStore/data/update_noc_npd_era5v1_attrs.py +172 -0
  95. OceanDataStore/data/utils.py +506 -0
  96. OceanDataStore/zarr.py +993 -0
  97. oceandatastore-0.3.0.dist-info/METADATA +184 -0
  98. oceandatastore-0.3.0.dist-info/RECORD +104 -0
  99. oceandatastore-0.3.0.dist-info/WHEEL +5 -0
  100. oceandatastore-0.3.0.dist-info/entry_points.txt +2 -0
  101. oceandatastore-0.3.0.dist-info/licenses/LICENSE +201 -0
  102. oceandatastore-0.3.0.dist-info/scm_file_list.json +154 -0
  103. oceandatastore-0.3.0.dist-info/scm_version.json +8 -0
  104. oceandatastore-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,150 @@
1
+ # =========================================================
2
+ # send_OISSTv2_monthly_climatology_to_os.py
3
+ #
4
+ # Script to write OISST v2.1 long-term monthly climatologies
5
+ # to Icechunk repositories in JASMIN cloud object storage.
6
+ #
7
+ # Created By: Ollie Tooth (oliver.tooth@noc.ac.uk)
8
+ # =========================================================
9
+ import logging
10
+
11
+ import numpy as np
12
+ import xarray as xr
13
+ import zarr
14
+
15
+ from OceanDataStore.cli import initialise_logging, send_to_icechunk
16
+ from OceanDataStore.data.utils import (
17
+ compute_cell_area,
18
+ compute_dx,
19
+ compute_dy,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def main():
26
+ # ========== Initialise OceanDataStore Logging ========== #
27
+ initialise_logging()
28
+
29
+ # ========== Send to Icechunk Repository ========== #
30
+ bucket = "oisst"
31
+ exists = False
32
+ store_credentials_json = ".../credentials/jasmin_os_credentials.json"
33
+ branch = "main"
34
+ variable_commits = True
35
+
36
+ # Define climatology period:
37
+ start_yr = 1991
38
+ end_yr = 2020
39
+
40
+ logging.info(f"In Progress: Sending OISSTv2.1 monthly climatology for {start_yr}-{end_yr} to Icechunk...")
41
+ # Open OISSTv2 dataset:
42
+ filepaths = [f"/dssgfs01/scratch/otooth/npd_data/observations/OISST/icec.mon.ltm.{start_yr}-{end_yr}.nc",
43
+ f"/dssgfs01/scratch/otooth/npd_data/observations/OISST/sst.mon.ltm.{start_yr}-{end_yr}.nc"
44
+ ]
45
+ ds = xr.merge([xr.open_dataset(filepath, decode_times=False).drop_vars("valid_yr_count") for filepath in filepaths], compat="no_conflicts")
46
+ # Open OISSTv2 land-sea mask dataset:
47
+ ds_mask = xr.open_dataset("http://psl.noaa.gov/thredds/dodsC/Datasets/noaa.oisst.v2.highres/lsmask.oisst.nc", decode_times=False)
48
+ ds_mask = ds_mask.squeeze(drop=True).rename({"lon": "longitude", "lat": "latitude", "lsmask": "mask"})
49
+ ds_mask = ds_mask.assign_coords(
50
+ longitude=((ds_mask["longitude"] + 180) % 360) - 180
51
+ )
52
+
53
+ # Standardise coordinate dimension names:
54
+ ds = ds.rename({"lon": "longitude", "lat": "latitude", "time": "month"})
55
+
56
+ # Update longitude coordinates to be in the range [-180, 180]:
57
+ ds = ds.assign_coords(
58
+ longitude=((ds["longitude"] + 180) % 360) - 180
59
+ )
60
+ ds = ds.sortby("longitude")
61
+
62
+ # Add month of year coordinate (1-12):
63
+ ds = ds.assign_coords(
64
+ month=np.arange(1, 13)
65
+ )
66
+
67
+ # Rename variables to standard names:
68
+ ds = ds.rename({"sst": "tos",
69
+ "icec": "siconc",
70
+ "climatology_bounds": "time_bnds",
71
+ })
72
+
73
+ # Add standard names and units:
74
+ ds["tos"].attrs["standard_name"] = "sea_surface_temperature"
75
+ ds["siconc"].attrs["standard_name"] = "sea_ice_area_fraction"
76
+ ds["siconc"].attrs["units"] = "1"
77
+
78
+ # Add OISSTv2 land mask:
79
+ ds["mask"] = ds_mask["mask"]
80
+ ds["mask"].attrs.clear()
81
+ ds["mask"] = ds["mask"].assign_attrs({'long_name': "Land-Sea Binary Mask",
82
+ "standard_name": "sea_binary_mask",
83
+ "comment": "1 = sea, 0 = land"
84
+ })
85
+
86
+ # Add horizontal grid cell area:
87
+ ds['dx'] = compute_dx(ds)
88
+ ds['dy'] = compute_dy(ds)
89
+ ds['cell_area'] = compute_cell_area(ds)
90
+
91
+ # Update time bounds to reflect climatological period:
92
+ ds['time_bnds'] = ds['time_bnds'].astype('datetime64[ns]')
93
+ ds['time_bnds'].data[:, 0] = (np.datetime64(f'{start_yr}-01', 'M') + (np.timedelta64(1, 'M') * np.arange(ds['month'].size))).astype('datetime64[ns]')
94
+ ds['time_bnds'].data[:, 1] = (np.datetime64(f'{end_yr}-01', 'M') + (np.timedelta64(1, 'M') * np.arange(ds['month'].size))).astype('datetime64[ns]')
95
+ ds.time_bnds.attrs.clear()
96
+
97
+ # Update global attributes:
98
+ ds.attrs.clear()
99
+ ds = ds.assign_attrs({
100
+ "Conventions": "CF-1.5",
101
+ "title": f"NOAA OISSTv2.1 Monthly Climatology ({start_yr}-{end_yr})",
102
+ "description": f"NOAA 1/4° Monthly Optimum Interpolation Sea Surface Temperature (OISST) version 2.1 monthly sea surface temperature and sea ice fraction climatology ({start_yr}-{end_yr}).",
103
+ "source": "Numerical models: Optimal Interpolation. In-situ observations: ICOADS-D R3.0.2, Argo GDAC. Satellite observations: Advanced Very High Resolution Radiometer (AVHRR).",
104
+ "dataset_type": "observation",
105
+ "product_type": "climatology",
106
+ "product_version": "2.1",
107
+ "institution": "NOAA National Centers for Environmental Information (NCEI)",
108
+ "citation": "Huang, B., C. Liu, V. Banzon, E. Freeman, G. Graham, B. Hankins, T. Smith, and H.-M. Zhang, 2021: Improvements of the Daily Optimum Interpolation Sea Surface Temperature (DOISST) Version 2.1, Journal of Climate, 34, 2923-2939. doi: 10.1175/JCLI-D-20-0166.1",
109
+ "references": "Huang, B., C. Liu, V. Banzon, E. Freeman, G. Graham, B. Hankins, T. Smith, and H.-M. Zhang, 2020: Improvements of the Daily Optimum Interpolation Sea Surface Temperature (DOISST) Version 2.1, Journal of Climate, 34, 2923-2939. doi: 10.1175/JCLI-D-20-0166.1. Banzon, V., Smith, T. M., Chin, T. M., Liu, C., and Hankins, W., 2016: A long-term record of blended satellite and in situ sea-surface temperature for climate monitoring, modeling and environmental studies. Earth Syst. Sci. Data, 8, 165-176, doi:10.5194/essd-8-165-2016. Reynolds, R. W., T. M. Smith, C. Liu, D. B. Chelton, K. S. Casey, and M. G. Schlax, 2007: Daily high-resolution-blended analyses for sea surface temperature. Journal of Climate, 20, 5473-5496, doi:10.1175/JCLI-D-14-00293.1",
110
+ "acknowledgement": "NOAA OI SST V2 High Resolution Dataset data provided by the NOAA PSL, Boulder, Colorado, USA, from their website at https://psl.noaa.gov.",
111
+ "license": "OISST v2.1 data were obtained from https://psl.noaa.gov/data/gridded/data.noaa.oisst.v2.highres.html and are provided under a Creative Commons CC0 1.0 Universal License https://creativecommons.org/publicdomain/zero/1.0/",
112
+ "doi": "10.1175/JCLI-D-20-0166.1",
113
+ "platform": "gr",
114
+ "horizontal_grid_type": "regular rectilinear",
115
+ "horizontal_grid_resolution": "0.25 degree",
116
+ "aggregation": "mean",
117
+ "aggregation_frequency": "monthly",
118
+ "status": "completed",
119
+ "update_frequency": "None",
120
+ "bbox": "[-180.0, 180.0, -90.0, 90.0]",
121
+ })
122
+
123
+ # Optimise chunk sizes for spatial analysis:
124
+ ds = ds.chunk({'month': 1, 'latitude': 720, 'longitude': 1440})
125
+
126
+ # Update variable encodings:
127
+ blosccodec = zarr.codecs.BloscCodec(cname="zstd", clevel=3, shuffle=zarr.codecs.BloscShuffle.shuffle)
128
+ for var in list(ds.data_vars) + list(ds.coords):
129
+ ds[var].encoding['compressors'] = [blosccodec]
130
+
131
+ # Define prefix and commit message based on climatology period:
132
+ prefix = f"oisst_v2.1_{start_yr}_{end_yr}_monthly_climatology"
133
+ commit_message = f"Added OISSTv2.1 Sea Surface Temperature Climatology ({start_yr}-{end_yr})."
134
+
135
+ send_to_icechunk(
136
+ file=ds,
137
+ bucket=bucket,
138
+ object_prefix=prefix,
139
+ store_credentials_json=store_credentials_json,
140
+ exists=exists,
141
+ append_dim='month',
142
+ branch=branch,
143
+ commit_message=commit_message,
144
+ variable_commits=variable_commits,
145
+ dask_config_kwargs=None,
146
+ dask_cluster_kwargs=None,
147
+ )
148
+
149
+ if __name__ == "__main__":
150
+ main()
@@ -0,0 +1,145 @@
1
+ # =========================================================
2
+ # send_OISSTv2_monthly_climatology_to_os.py
3
+ #
4
+ # Script to write OISST v2.1 long-term monthly climatologies
5
+ # to Icechunk repositories in JASMIN cloud object storage.
6
+ #
7
+ # Created By: Ollie Tooth (oliver.tooth@noc.ac.uk)
8
+ # =========================================================
9
+ import logging
10
+
11
+ import numpy as np
12
+ import xarray as xr
13
+ import zarr
14
+
15
+ from OceanDataStore.cli import initialise_logging, send_to_icechunk
16
+ from OceanDataStore.data.utils import (
17
+ compute_cell_area,
18
+ compute_dx,
19
+ compute_dy,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def main():
26
+ # ========== Initialise OceanDataStore Logging ========== #
27
+ initialise_logging()
28
+
29
+ # ========== Send to Icechunk Repository ========== #
30
+ bucket = "oisst"
31
+ exists = False
32
+ store_credentials_json = ".../credentials/jasmin_os_credentials.json"
33
+ branch = "main"
34
+ variable_commits = True
35
+
36
+ logging.info("In Progress: Sending OISSTv2.1 monthly mean time series to Icechunk...")
37
+ # Open OISSTv2 dataset:
38
+ filepaths = ["/dssgfs01/scratch/otooth/npd_data/observations/OISST/icec.mon.mean.nc",
39
+ "/dssgfs01/scratch/otooth/npd_data/observations/OISST/sst.mon.mean.nc"
40
+ ]
41
+ ds = xr.merge([xr.open_dataset(filepath, decode_times=False) for filepath in filepaths], compat="no_conflicts")
42
+ # Open OISSTv2 land-sea mask dataset:
43
+ ds_mask = xr.open_dataset("http://psl.noaa.gov/thredds/dodsC/Datasets/noaa.oisst.v2.highres/lsmask.oisst.nc", decode_times=False)
44
+ ds_mask = ds_mask.squeeze(drop=True).rename({"lon": "longitude", "lat": "latitude", "lsmask": "mask"})
45
+ ds_mask = ds_mask.assign_coords(
46
+ longitude=((ds_mask["longitude"] + 180) % 360) - 180
47
+ )
48
+
49
+ # Standardise coordinate dimension names:
50
+ ds = ds.rename({"lon": "longitude", "lat": "latitude"})
51
+
52
+ # Update longitude coordinates to be in the range [-180, 180]:
53
+ ds = ds.assign_coords(
54
+ longitude=((ds["longitude"] + 180) % 360) - 180
55
+ )
56
+ ds = ds.sortby("longitude")
57
+
58
+ # Update time coordinate to be datetime objects:
59
+ ds = ds.assign_coords(time=(np.datetime64('1800-01-01', 'D') + (np.timedelta64(1, 'D') * ds['time'])).astype('datetime64[ns]'))
60
+ ds['time'].attrs.pop('units', None)
61
+
62
+ # Rename variables to standard names:
63
+ ds = ds.rename({"sst": "tos",
64
+ "icec": "siconc",
65
+ })
66
+
67
+ # Add standard names and units:
68
+ ds["tos"].attrs["standard_name"] = "sea_surface_temperature"
69
+ ds["siconc"].attrs["standard_name"] = "sea_ice_area_fraction"
70
+ ds["siconc"].attrs["units"] = "1"
71
+
72
+ # Add OISSTv2 land mask:
73
+ ds["mask"] = ds_mask["mask"]
74
+ ds["mask"].attrs.clear()
75
+ ds["mask"] = ds["mask"].assign_attrs({'long_name': "Land-Sea Binary Mask",
76
+ "standard_name": "sea_binary_mask",
77
+ "comment": "1 = sea, 0 = land"
78
+ })
79
+
80
+ # Add horizontal grid cell area:
81
+ ds["dx"] = compute_dx(ds)
82
+ ds["dy"] = compute_dy(ds)
83
+ ds['cell_area'] = compute_cell_area(ds)
84
+
85
+ # Add Northern and Southern Hemisphere sea ice area timeseries:
86
+ ds['siarea_NH'] = (ds['siconc'].where(ds['latitude'] > 0) * ds['cell_area']).sum(dim=['latitude', 'longitude'])
87
+ ds['siarea_NH'].attrs = {'long_name': 'Total Northern Hemisphere Sea Ice Area', 'standard_name': 'sea_ice_area', 'units': 'm2'}
88
+
89
+ ds['siarea_SH'] = (ds['siconc'].where(ds['latitude'] < 0) * ds['cell_area']).sum(dim=['latitude', 'longitude'])
90
+ ds['siarea_SH'].attrs = {'long_name': 'Total Southern Hemisphere Sea Ice Area', 'standard_name': 'sea_ice_area', 'units': 'm2'}
91
+
92
+ # Update global attributes:
93
+ ds.attrs.clear()
94
+ ds = ds.assign_attrs({
95
+ "Conventions": "CF-1.5",
96
+ "title": "NOAA OISSTv2.1 Monthly Timeseries",
97
+ "description": "NOAA 1/4° Monthly Optimum Interpolation Sea Surface Temperature (OISST) version 2.1 monthly sea surface temperature and sea ice fraction timeseries.",
98
+ "source": "Numerical models: Optimal Interpolation. In-situ observations: ICOADS-D R3.0.2, Argo GDAC. Satellite observations: Advanced Very High Resolution Radiometer (AVHRR).",
99
+ "dataset_type": "observation",
100
+ "product_type": "timeseries",
101
+ "product_version": "2.1",
102
+ "institution": "NOAA National Centers for Environmental Information (NCEI)",
103
+ "citation": "Huang, B., C. Liu, V. Banzon, E. Freeman, G. Graham, B. Hankins, T. Smith, and H.-M. Zhang, 2021: Improvements of the Daily Optimum Interpolation Sea Surface Temperature (DOISST) Version 2.1, Journal of Climate, 34, 2923-2939. doi: 10.1175/JCLI-D-20-0166.1",
104
+ "references": "Huang, B., C. Liu, V. Banzon, E. Freeman, G. Graham, B. Hankins, T. Smith, and H.-M. Zhang, 2020: Improvements of the Daily Optimum Interpolation Sea Surface Temperature (DOISST) Version 2.1, Journal of Climate, 34, 2923-2939. doi: 10.1175/JCLI-D-20-0166.1. Banzon, V., Smith, T. M., Chin, T. M., Liu, C., and Hankins, W., 2016: A long-term record of blended satellite and in situ sea-surface temperature for climate monitoring, modeling and environmental studies. Earth Syst. Sci. Data, 8, 165-176, doi:10.5194/essd-8-165-2016. Reynolds, R. W., T. M. Smith, C. Liu, D. B. Chelton, K. S. Casey, and M. G. Schlax, 2007: Daily high-resolution-blended analyses for sea surface temperature. Journal of Climate, 20, 5473-5496, doi:10.1175/JCLI-D-14-00293.1",
105
+ "acknowledgement": "NOAA OI SST V2 High Resolution Dataset data provided by the NOAA PSL, Boulder, Colorado, USA, from their website at https://psl.noaa.gov.",
106
+ "license": "OISST v2.1 data were obtained from https://psl.noaa.gov/data/gridded/data.noaa.oisst.v2.highres.html and are provided under a Creative Commons CC0 1.0 Universal License https://creativecommons.org/publicdomain/zero/1.0/",
107
+ "doi": "10.1175/JCLI-D-20-0166.1",
108
+ "platform": "gr",
109
+ "horizontal_grid_type": "regular rectilinear",
110
+ "horizontal_grid_resolution": "0.25 degree",
111
+ "aggregation": "mean",
112
+ "aggregation_frequency": "monthly",
113
+ "status": "ongoing",
114
+ "update_frequency": "quarterly",
115
+ "bbox": "[-180.0, 180.0, -90.0, 90.0]",
116
+ })
117
+
118
+ # Optimise chunk sizes for spatial analysis:
119
+ ds = ds.chunk({'time': 1, 'latitude': 720, 'longitude': 1440})
120
+
121
+ # Update variable encodings:
122
+ blosccodec = zarr.codecs.BloscCodec(cname="zstd", clevel=3, shuffle=zarr.codecs.BloscShuffle.shuffle)
123
+ for var in list(ds.data_vars) + list(ds.coords):
124
+ ds[var].encoding['compressors'] = [blosccodec]
125
+
126
+ # Define prefix and commit message based on climatology period:
127
+ prefix = "oisst_v2.1_monthly"
128
+ commit_message = "Added OISSTv2.1 Sea Surface Temperature & Sea Ice Fraction Monthly Timeseries (1981-2026)."
129
+
130
+ send_to_icechunk(
131
+ file=ds,
132
+ bucket=bucket,
133
+ object_prefix=prefix,
134
+ store_credentials_json=store_credentials_json,
135
+ exists=exists,
136
+ append_dim='time',
137
+ branch=branch,
138
+ commit_message=commit_message,
139
+ variable_commits=variable_commits,
140
+ dask_config_kwargs=None,
141
+ dask_cluster_kwargs=None,
142
+ )
143
+
144
+ if __name__ == "__main__":
145
+ main()
@@ -0,0 +1,142 @@
1
+ # =========================================================
2
+ # update_OISSTv2_daily_to_os.py
3
+ #
4
+ # Script to write OISST v2.1 daily mean timeseries
5
+ # to Icechunk repositories in JASMIN cloud object storage.
6
+ #
7
+ # Created By: Ollie Tooth (oliver.tooth@noc.ac.uk)
8
+ # =========================================================
9
+ import logging
10
+ from pathlib import Path
11
+
12
+ import xarray as xr
13
+ import zarr
14
+
15
+ from OceanDataStore.cli import initialise_logging, update_icechunk
16
+ from OceanDataStore.data.utils import (
17
+ compute_cell_area,
18
+ compute_dx,
19
+ compute_dy,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def main():
26
+ # ========== Initialise OceanDataStore Logging ========== #
27
+ initialise_logging()
28
+
29
+ # ========== Update Icechunk Repository ========== #
30
+ bucket = "oisst"
31
+ store_credentials_json = ".../credentials/jasmin_os_credentials.json"
32
+ branch = "main"
33
+ config_kwargs = {
34
+ "temporary_directory":"/dssgfs01/working/otooth/Software/OceanDataStore/OceanDataStore/data/OISST/",
35
+ "local_directory":"/dssgfs01/working/otooth/Software/OceanDataStore/OceanDataStore/data/OISST/"
36
+ }
37
+ cluster_kwargs = {
38
+ "n_workers" : 15,
39
+ "threads_per_worker" : 1,
40
+ "memory_limit":"6GB"
41
+ }
42
+
43
+ logging.info("In Progress: Updating OISSTv2.1 daily mean time series in Icechunk...")
44
+ # Open OISSTv2 dataset:
45
+ filepaths = []
46
+ base = Path("/dssgfs01/scratch/otooth/npd_data/observations/OISST/daily/")
47
+ for year in range(2026, 2027):
48
+ filepaths.extend(sorted(base.glob(f"sst.day.mean.{year}.nc")))
49
+ ds = xr.open_mfdataset(filepaths,
50
+ combine="by_coords",
51
+ data_vars="all",
52
+ engine="h5netcdf",
53
+ )
54
+
55
+ # Open OISSTv2 land-sea mask dataset:
56
+ ds_mask = xr.open_dataset("http://psl.noaa.gov/thredds/dodsC/Datasets/noaa.oisst.v2.highres/lsmask.oisst.nc", decode_times=False)
57
+ ds_mask = ds_mask.squeeze(drop=True).rename({"lon": "longitude", "lat": "latitude", "lsmask": "mask"})
58
+ ds_mask = ds_mask.assign_coords(
59
+ longitude=((ds_mask["longitude"] + 180) % 360) - 180
60
+ )
61
+
62
+ # Standardise coordinate dimension names:
63
+ ds = ds.rename({"lon": "longitude", "lat": "latitude"})
64
+
65
+ # Update longitude coordinates to be in the range [-180, 180]:
66
+ ds = ds.assign_coords(
67
+ longitude=((ds["longitude"] + 180) % 360) - 180
68
+ )
69
+ ds = ds.sortby("longitude")
70
+
71
+ # Rename variables to standard names:
72
+ ds = ds.rename({"sst": "tos"})
73
+
74
+ # Add standard names and units:
75
+ ds["tos"].attrs["standard_name"] = "sea_surface_temperature"
76
+
77
+ # Add OISSTv2 land mask:
78
+ ds["mask"] = ds_mask["mask"]
79
+ ds["mask"].attrs.clear()
80
+ ds["mask"] = ds["mask"].assign_attrs({"long_name": "Land-Sea Binary Mask",
81
+ "standard_name": "sea_binary_mask",
82
+ "comment": "1 = sea, 0 = land"
83
+ })
84
+
85
+ # Add horizontal grid cell area:
86
+ ds["dx"] = compute_dx(ds)
87
+ ds["dy"] = compute_dy(ds)
88
+ ds['cell_area'] = compute_cell_area(ds)
89
+
90
+ # Update global attributes:
91
+ ds.attrs.clear()
92
+ ds = ds.assign_attrs({
93
+ "Conventions": "CF-1.5",
94
+ "title": "NOAA OISSTv2.1 Daily Timeseries",
95
+ "description": "NOAA 1/4° Daily Optimum Interpolation Sea Surface Temperature (OISST) version 2.1 daily sea surface temperature timeseries.",
96
+ "source": "Numerical models: Optimal Interpolation. In-situ observations: ICOADS-D R3.0.2, Argo GDAC. Satellite observations: Advanced Very High Resolution Radiometer (AVHRR).",
97
+ "dataset_type": "observation",
98
+ "product_type": "timeseries",
99
+ "product_version": "2.1",
100
+ "institution": "NOAA National Centers for Environmental Information (NCEI)",
101
+ "citation": "Huang, B., C. Liu, V. Banzon, E. Freeman, G. Graham, B. Hankins, T. Smith, and H.-M. Zhang, 2021: Improvements of the Daily Optimum Interpolation Sea Surface Temperature (DOISST) Version 2.1, Journal of Climate, 34, 2923-2939. doi: 10.1175/JCLI-D-20-0166.1",
102
+ "references": "Huang, B., C. Liu, V. Banzon, E. Freeman, G. Graham, B. Hankins, T. Smith, and H.-M. Zhang, 2020: Improvements of the Daily Optimum Interpolation Sea Surface Temperature (DOISST) Version 2.1, Journal of Climate, 34, 2923-2939. doi: 10.1175/JCLI-D-20-0166.1. Banzon, V., Smith, T. M., Chin, T. M., Liu, C., and Hankins, W., 2016: A long-term record of blended satellite and in situ sea-surface temperature for climate monitoring, modeling and environmental studies. Earth Syst. Sci. Data, 8, 165-176, doi:10.5194/essd-8-165-2016. Reynolds, R. W., T. M. Smith, C. Liu, D. B. Chelton, K. S. Casey, and M. G. Schlax, 2007: Daily high-resolution-blended analyses for sea surface temperature. Journal of Climate, 20, 5473-5496, doi:10.1175/JCLI-D-14-00293.1",
103
+ "acknowledgement": "NOAA OI SST V2 High Resolution Dataset data provided by the NOAA PSL, Boulder, Colorado, USA, from their website at https://psl.noaa.gov.",
104
+ "license": "OISST v2.1 data were obtained from https://psl.noaa.gov/data/gridded/data.noaa.oisst.v2.highres.html and are provided under a Creative Commons CC0 1.0 Universal License https://creativecommons.org/publicdomain/zero/1.0/",
105
+ "doi": "10.1175/JCLI-D-20-0166.1",
106
+ "platform": "gr",
107
+ "horizontal_grid_type": "regular rectilinear",
108
+ "horizontal_grid_resolution": "0.25 degree",
109
+ "aggregation": "mean",
110
+ "aggregation_frequency": "daily",
111
+ "status": "ongoing",
112
+ "update_frequency": "quarterly",
113
+ "bbox": "[-180.0, 180.0, -90.0, 90.0]",
114
+ })
115
+
116
+ # Optimise chunk sizes for time-series analysis:
117
+ ds = ds.chunk({'time': ds['time'].size, 'latitude': 50, 'longitude': 50})
118
+
119
+ # Update variable encodings:
120
+ blosccodec = zarr.codecs.BloscCodec(cname="zstd", clevel=3, shuffle=zarr.codecs.BloscShuffle.shuffle)
121
+ for var in list(ds.data_vars) + list(ds.coords):
122
+ ds[var].encoding.clear()
123
+ ds[var].encoding['compressors'] = [blosccodec]
124
+
125
+ # Define prefix and commit message based on climatology period:
126
+ prefix = "oisst_v2.1_daily"
127
+ commit_message = "Added OISSTv2.1 Sea Surface Temperature Daily Timeseries (2026-01-2026-06)."
128
+
129
+ update_icechunk(
130
+ file=ds,
131
+ bucket=bucket,
132
+ object_prefix=prefix,
133
+ store_credentials_json=store_credentials_json,
134
+ append_dim='time',
135
+ branch=branch,
136
+ commit_message=commit_message,
137
+ dask_config_kwargs=config_kwargs,
138
+ dask_cluster_kwargs=cluster_kwargs,
139
+ )
140
+
141
+ if __name__ == "__main__":
142
+ main()
@@ -0,0 +1,120 @@
1
+ # =========================================================
2
+ # create_OSTIA_daily_climatology.py
3
+ #
4
+ # Script to calculate daily mean and quantile climatology
5
+ # for OSTIA sea surface temperature data.
6
+ #
7
+ # Created By: Oliver Tooth (oliver.tooth@noc.ac.uk)
8
+ # =========================================================
9
+ import argparse
10
+ import logging
11
+
12
+ import numpy as np
13
+ import xarray as xr
14
+ import zarr
15
+ from dask.distributed import Client
16
+
17
+ from OceanDataStore.cli import initialise_logging
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ def pooled_days(day: int, half_width: int=5, ndays: int=366):
22
+ """
23
+ Return cyclic day window centred on `day`.
24
+ """
25
+ offsets = np.arange(-half_width, half_width + 1)
26
+ return ((day - 1 + offsets) % ndays) + 1
27
+
28
+
29
+ def main(start_year, end_year, data_path="/dssgfs01/working/otooth/data/observations/OSTIA/", output="sst_climatology.zarr"):
30
+
31
+ # ========== Initialise OceanDataStore Logging ========== #
32
+ initialise_logging()
33
+
34
+ # ========== Initialise Dask Client ========== #
35
+ client = Client(n_workers=20, threads_per_worker=1)
36
+ logging.info(f"Dask client initialized: {client}")
37
+
38
+ # ========== Compute Daily Climatology from OSTIA Dataset ========== #
39
+ logging.info(f"Computing OSTIA Daily Climatology from {start_year} to {end_year}")
40
+
41
+ # Open multiple files
42
+ ds_ostia_rep = xr.open_mfdataset([f"{data_path}ostia_global_sst_daily_NA_1990_1999.zarr",
43
+ f"{data_path}ostia_global_sst_daily_NA_2000_2025.zarr"
44
+ ], engine="zarr", parallel=True)
45
+
46
+ ds_ostia_nrt = xr.open_dataset(f"{data_path}ostia_global_sst_daily_NA_nrt_2025_2026.zarr",
47
+ engine="zarr",
48
+ )
49
+ ds_ostia_nrt = ds_ostia_nrt.sel(time=slice("2025-12-19", None))
50
+
51
+ ds = xr.concat([ds_ostia_rep, ds_ostia_nrt], dim="time")
52
+ ds = ds.sel(time=slice(f"{start_year}-01-01", f"{end_year}-12-31"))
53
+ logging.info(f"Completed: Opened OSTIA dataset for years: {start_year} to {end_year}")
54
+
55
+ ds = ds.chunk({
56
+ "time": -1,
57
+ "latitude": 100,
58
+ "longitude": 100
59
+ })
60
+
61
+ # Add Climatological Day of Year (clim_day) coordinate to the dataset
62
+ doy = ds['time'].dt.dayofyear
63
+ is_leap = ds['time'].dt.is_leap_year
64
+ after_feb28 = (~is_leap) & (doy >= 60)
65
+ clim_day = xr.where(after_feb28, doy + 1, doy)
66
+ ds = ds.assign_coords(clim_day=("time", clim_day.data))
67
+
68
+ # Rename variables and transform temperature from Kelvin to Celsius
69
+ ds = ds.rename({"analysed_sst": "tos",
70
+ "analysis_error": "tos_error",
71
+ "sea_ice_fraction": "siconc",
72
+ })
73
+
74
+ ds['tos'] = ds['tos'] - 273.15 # Convert from Kelvin to Celsius
75
+ ds['tos_error'] = ds['tos_error'] - 273.15 # Convert from Kelvin to Celsius
76
+
77
+ # Compute daily climatology (day of year):
78
+ for day in range(1, 367):
79
+ logging.info(f"Calculating Mean, 10th and 90th percentiles for day {day}...")
80
+ pooled = ds['tos'][ds['clim_day'].isin(pooled_days(day=day))]
81
+
82
+ # Build output dataset
83
+ clim = xr.Dataset()
84
+ clim["tos_mean"] = pooled.mean(dim="time", skipna=True)
85
+ clim["tos_p10"] = pooled.quantile(q=0.1, dim="time", skipna=True).astype(np.float32)
86
+ clim["tos_p90"] = pooled.quantile(q=0.9, dim="time", skipna=True).astype(np.float32)
87
+ clim = clim.expand_dims(clim_day=[day])
88
+ logging.info("Completed: Created OSTIA Daily Climatology SST dataset.")
89
+
90
+ clim = clim.chunk({
91
+ "clim_day": 1,
92
+ "latitude": 721,
93
+ "longitude": 1440
94
+ })
95
+
96
+ # Update variable encodings:
97
+ blosccodec = zarr.codecs.BloscCodec(cname="zstd", clevel=3, shuffle=zarr.codecs.BloscShuffle.shuffle)
98
+ for var in list(clim.data_vars) + list(clim.coords):
99
+ clim[var].encoding.clear()
100
+ clim[var].encoding['compressors'] = [blosccodec]
101
+
102
+ # Save output
103
+ logging.info(f"In Progress: Saving climatology to {output}...")
104
+ if day == 1:
105
+ clim.to_zarr(store=output, mode="w", zarr_format=3)
106
+ else:
107
+ clim.to_zarr(store=output, append_dim='clim_day', zarr_format=3)
108
+ logging.info(f"Completed: Saved climatology to {output}")
109
+
110
+
111
+ if __name__ == "__main__":
112
+ parser = argparse.ArgumentParser(description="Compute SST Daily Climatology")
113
+ parser.add_argument("start_year", type=int, help="Start year (e.g. 2000)")
114
+ parser.add_argument("end_year", type=int, help="End year (e.g. 2010)")
115
+ parser.add_argument("--data_path", default=".", help="Directory containing SST files")
116
+ parser.add_argument("--output", default="sst_climatology.zarr", help="Output file")
117
+
118
+ args = parser.parse_args()
119
+
120
+ main(args.start_year, args.end_year, args.data_path, args.output)
@@ -0,0 +1,42 @@
1
+ """
2
+ Download North Atlantic subdomain of OSTIA Global SST dataset from Copernicus Marine.
3
+ This script downloads near-real time data from 2025 onwards.
4
+
5
+ Subdomain is defined as: (-45 degE, 14 degE, 31 degN, 85 degN)
6
+
7
+ Created By: Ollie Tooth
8
+ Created On: 2026-06-27
9
+ Contact: oliver.tooth@noc.ac.uk
10
+
11
+ Virtual Environment: env_ods.
12
+ """
13
+
14
+ # Import the Copernicus Marine API toolbox:
15
+ import copernicusmarine
16
+
17
+ # Define filepath to credentials:
18
+ credentials_fpath = "~/.copernicusmarine/.copernicusmarine-credentials"
19
+ # Define output directory:
20
+ out_fdir = "/dssgfs01/working/otooth/data/observations/OSTIA/"
21
+
22
+ # Define start and end years:
23
+ year_start = 2025
24
+ year_end = 2026
25
+
26
+ # Download the OSTIA Global SST dataset for North Atlantic subdomain:
27
+ print(f"In Progress: Downloading OSTIA Global SST dataset for: {year_start} to {year_end}...")
28
+ copernicusmarine.subset(
29
+ dataset_id="METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2",
30
+ variables=["analysed_sst", "analysis_error", "mask", "sea_ice_fraction"],
31
+ start_datetime=f"{year_start}-01-01T00:00:00",
32
+ end_datetime=f"{year_end}-12-31T23:59:59",
33
+ minimum_longitude=-45,
34
+ maximum_longitude=14,
35
+ minimum_latitude=31,
36
+ maximum_latitude=85,
37
+ credentials_file=credentials_fpath,
38
+ output_directory=out_fdir,
39
+ file_format="zarr",
40
+ output_filename=f"ostia_global_sst_daily_NA_nrt_{year_start}_{year_end}.zarr",
41
+ )
42
+ print(f"Completed: downloading OSTIA Global SST dataset for: {year_start} to {year_end}.")
@@ -0,0 +1,42 @@
1
+ """
2
+ Download North Atlantic subdomain of OSTIA Global SST dataset from Copernicus Marine.
3
+ This script downloads the monthly reprocessed files from 1981 to 2025.
4
+
5
+ Subdomain is defined as: (-45 degE, 14 degE, 31 degN, 85 degN)
6
+
7
+ Created By: Ollie Tooth
8
+ Created On: 2026-06-27
9
+ Contact: oliver.tooth@noc.ac.uk
10
+
11
+ Virtual Environment: env_ods.
12
+ """
13
+
14
+ # Import the Copernicus Marine API toolbox:
15
+ import copernicusmarine
16
+
17
+ # Define filepath to credentials:
18
+ credentials_fpath = "~/.copernicusmarine/.copernicusmarine-credentials"
19
+ # Define output directory:
20
+ out_fdir = "/dssgfs01/working/otooth/data/observations/OSTIA/"
21
+
22
+ # Define start and end years:
23
+ year_start = 2000
24
+ year_end = 2025
25
+
26
+ # Download the OSTIA Global SST dataset for North Atlantic subdomain:
27
+ print(f"In Progress: Downloading OSTIA Global SST dataset for: {year_start} to {year_end}...")
28
+ copernicusmarine.subset(
29
+ dataset_id="METOFFICE-GLO-SST-L4-REP-OBS-SST",
30
+ variables=["analysed_sst", "analysis_error", "mask", "sea_ice_fraction"],
31
+ start_datetime=f"{year_start}-01-01T00:00:00",
32
+ end_datetime=f"{year_end}-12-31T23:59:59",
33
+ minimum_longitude=-45,
34
+ maximum_longitude=14,
35
+ minimum_latitude=31,
36
+ maximum_latitude=85,
37
+ credentials_file=credentials_fpath,
38
+ output_directory=out_fdir,
39
+ file_format="zarr",
40
+ output_filename=f"ostia_global_sst_daily_NA_rep_{year_start}_{year_end}.zarr",
41
+ )
42
+ print(f"Completed: downloading OSTIA Global SST dataset for: {year_start} to {year_end}.")