ROMS Observation Operators - myroms/roms GitHub Wiki
The native ROMS 4D-Var observation file and H(x) operators have been refactored to use multiple hierarchical IODA-type enhanced NetCDF-4 files with first-level grouping. The main objective is to create IODA observation files that can be utilized in both the native ROMS 4D-Var drivers and any ROMS-JEDI data assimilation algorithm. The data assimilation output is also stored in IODA files, organized into new groups for diagnostics and post-processing. Unlike the classic native ROMS NetCDF files, the input IODA files remain unmodified during data assimilation.
Important
Please use the C-preprocessing option IODA_OBS to active using multiple IODA observation files and associated H(x) operators. C-preprocessing removes the legacy, native, single classic ROMS 4D-Var NetCDF file processing. It is obsolete, but we keep it for backward compatibility.
The strategy is to have one or more IODA files for each variable in the control vector, such as SSH, SST, SSS, HF radar velocities, temperature, and salinity, as individual files in a particular data assimilation cycle. An IODA file could include multiple variables that represent vectors (u- and v-momentum) and spectral data with specified coordinates (longitude, latitude, depth, and time). It may also contain multiple scalar variables at precise locations, such as temperature (T) and salinity (S) profiles, or biological profiles for various ecosystem state variables. Additionally, secondary variables may be included to compute a control variable using an elaborate operator. This setup provides significant flexibility to apply time-averaged and/or area-averaged H(x) operators to individual observation streams, including altimetry data (SWOT), microwave SST, satellite-derived SSS (SMAP, SMOS), and HF radar velocity (CODAR).
| Altimetry ADT | SMAP Satellite SSS | HF-Radar Velocity |
|---|---|---|
![]() |
![]() |
![]() |
The processing of the observations and the application of the H(x) operators, which compute the model values at the observation locations, have been completely rewritten in ROMS using Fortran 2003 object classes:
-
roms_obs.F: Includes two CLASS objects,obs_dataandobs_pool, that are used to load and store the input observation data, which are read only once and saved in memory. This reduces I/O overhead and MPI communication in applications running with many processes. Theobs_poolobject is a vector, pool(:), of TYPEobs_dataobservations separated by their type, source, or control vector variable. That is, pool(:) may be a vector the size of the number of input IODA files, giving the user flexibility when configuring the data assimilation run. This module can load native (old) or IODA (new) observation NetCDF files for data assimilation. -
roms_hofx.F: Compute and store$\boldsymbol{\rm{H(X^{s}_{j})}}$ where superscript s indicates background (s=b) or analysis (s=a) state vector. It also computes its transpose HT for adjoint kernel forcing. It has two CLASS objects,obs_filterandobs_hofx. The commands to execute the H(x) operators are compact, usingCALL hofx_run (ng, tile, model)andCALL hofx_finalize (ng, model). The MPI parallel exchanges occur once during the kernel's finalize phase because the H(x) vectors are needed only in the minimization algorithm after running ROMS numerical kernels (NLM, TLM, ADM). This substantially reduces MPI communications. -
roms_vchange.F: It has the CLASS objectobs_VarChato perform the appropriate nonlinear and linear variable changes for the H(x) operators. This capability was not available in legacy processing because all required variable changes were the identity operator.
The processing of all the observations is reported at the beginning of the data assimilation run, which facilitates checking if they are processed correctly:
ROMS_OBS - Processing: ../Data/OBS/usec3km_obs_adt_20190827.nc4
Group: MetaData::dateTime Count = 87710
Group: MetaData::latitude Count = 87710
Group: MetaData::longitude Count = 87710
Group: MetaData::provenance Count = 87710
Group: MetaData::surveyIndex Count = 649
Group: MetaData::surveyTime Count = 649
Group: MetaData::x_grid Count = 87710
Group: MetaData::y_grid Count = 87710
Group: ObsError::absoluteDynamicTopography Count = 87710 ID = 1
Group: ObsValue::absoluteDynamicTopography Count = 87710 ID = 1
- absoluteDynamicTopography
(ValMin = -3.01195E+00 ValMax = 2.15674E+00 ValAvg = 3.73152E-01 CheckSum = 1775003)
(ErrMin = 2.77778E+02 ErrMax = 2.77778E+02 ErrAvg = 2.77778E+02)
DateTime Range: 2019-08-27 00:42:45.00 to 2019-08-29 23:58:19.00
SurveyTime Range: 2019-08-27 00:42:45.00 to 2019-08-29 23:58:19.00
ROMS_OBS - Processing: ../Data/OBS/usec3km_obs_sst_20190827.nc4
Group: MetaData::dateTime Count = 174310
Group: MetaData::latitude Count = 174310
Group: MetaData::longitude Count = 174310
Group: MetaData::provenance Count = 174310
Group: MetaData::surveyIndex Count = 77
Group: MetaData::surveyTime Count = 77
Group: MetaData::x_grid Count = 174310
Group: MetaData::y_grid Count = 174310
Group: ObsError::seaSurfaceTemperature Count = 174310 ID = 6
Group: ObsValue::seaSurfaceTemperature Count = 174310 ID = 6
- seaSurfaceTemperature
(ValMin = 7.90500E+00 ValMax = 3.08168E+01 ValAvg = 2.31506E+01 CheckSum = 2504327)
(ErrMin = 5.10204E-01 ErrMax = 2.61102E+01 ErrAvg = 1.96736E+00)
DateTime Range: 2019-08-27 01:06:45.00 to 2019-08-29 22:30:19.00
SurveyTime Range: 2019-08-27 01:06:45.00 to 2019-08-29 22:30:19.00
ROMS_OBS - Processing: ../Data/OBS/usec3km_obs_sss_20190827.nc4
Group: MetaData::dateTime Count = 52
Group: MetaData::latitude Count = 52
Group: MetaData::longitude Count = 52
Group: MetaData::provenance Count = 52
Group: MetaData::surveyIndex Count = 50
Group: MetaData::surveyTime Count = 50
Group: MetaData::x_grid Count = 52
Group: MetaData::y_grid Count = 52
Group: ObsError::seaSurfaceSalinity Count = 52 ID = 7
Group: ObsValue::seaSurfaceSalinity Count = 52 ID = 7
- seaSurfaceSalinity
(ValMin = 3.17402E+01 ValMax = 3.58166E+01 ValAvg = 3.30407E+01 CheckSum = 642)
(ErrMin = 3.18633E+01 ErrMax = 5.07306E+01 ErrAvg = 4.64497E+01)
DateTime Range: 2019-08-27 01:06:45.00 to 2019-08-29 22:30:19.00
SurveyTime Range: 2019-08-27 01:06:45.00 to 2019-08-29 22:30:19.00
ROMS_OBS - Processing: ../Data/OBS/usec3km_obs_temp_20190827.nc4
Group: MetaData::dateTime Count = 12286
Group: MetaData::depth Count = 12286
Group: MetaData::latitude Count = 12286
Group: MetaData::longitude Count = 12286
Group: MetaData::provenance Count = 12286
Group: MetaData::surveyIndex Count = 409
Group: MetaData::surveyTime Count = 409
Group: MetaData::x_grid Count = 12286
Group: MetaData::y_grid Count = 12286
Group: MetaData::z_grid Count = 12286
Group: ObsError::waterTemperature Count = 12286 ID = 6
Group: ObsValue::waterTemperature Count = 12286 ID = 6
- waterTemperature
(ValMin = 3.33530E+00 ValMax = 2.93459E+01 ValAvg = 1.30433E+01 CheckSum = 168795)
(ErrMin = 6.88618E-02 ErrMax = 1.23457E+02 ErrAvg = 1.08630E+01)
DateTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 23:54:19.00
SurveyTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 23:54:19.00
ROMS_OBS - Processing: ../Data/OBS/usec3km_obs_salt_20190827.nc4
Group: MetaData::dateTime Count = 12197
Group: MetaData::depth Count = 12197
Group: MetaData::latitude Count = 12197
Group: MetaData::longitude Count = 12197
Group: MetaData::provenance Count = 12197
Group: MetaData::surveyIndex Count = 406
Group: MetaData::surveyTime Count = 406
Group: MetaData::x_grid Count = 12197
Group: MetaData::y_grid Count = 12197
Group: MetaData::z_grid Count = 12197
Group: ObsError::salinity Count = 12197 ID = 7
Group: ObsValue::salinity Count = 12197 ID = 7
- salinity
(ValMin = 2.92491E+01 ValMax = 3.68814E+01 ValAvg = 3.36789E+01 CheckSum = 155723)
(ErrMin = 1.77741E+00 ErrMax = 4.00000E+02 ErrAvg = 9.50594E+01)
DateTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 23:54:19.00
SurveyTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 23:54:19.00
ROMS_OBS - Processing: ../Data/OBS/usec3km_obs_uv_codar_20190827.nc4
Group: MetaData::dateTime Count = 10332
Group: MetaData::depth Count = 10332
Group: MetaData::latitude Count = 10332
Group: MetaData::longitude Count = 10332
Group: MetaData::provenance Count = 10332
Group: MetaData::surveyIndex Count = 72
Group: MetaData::surveyTime Count = 72
Group: MetaData::x_grid Count = 10332
Group: MetaData::y_grid Count = 10332
Group: MetaData::z_grid Count = 10332
Group: ObsError::waterZonalVelocity Count = 10332 ID = 4
Group: ObsError::waterMeridionalVelocity Count = 10332 ID = 5
Group: ObsValue::waterZonalVelocity Count = 10332 ID = 4
Group: ObsValue::waterMeridionalVelocity Count = 10332 ID = 5
- waterZonalVelocity
(ValMin = -4.73497E-01 ValMax = 1.07178E+00 ValAvg = -3.81463E-02 CheckSum = 203326)
(ErrMin = 2.50000E+01 ErrMax = 2.50000E+03 ErrAvg = 3.67621E+02)
DateTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 22:54:19.00
SurveyTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 22:54:19.00
- waterMeridionalVelocity
(ValMin = -1.03206E+00 ValMax = 1.58777E+00 ValAvg = -6.93601E-02 CheckSum = 203532)
(ErrMin = 2.50000E+01 ErrMax = 2.50000E+03 ErrAvg = 3.67621E+02)
DateTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 22:54:19.00
SurveyTime Range: 2019-08-27 00:00:00.00 to 2019-08-29 22:54:19.00
OBS_POOL - NC4_CLONE: creating H(x) file, Grid 01: usec3km_obs_adt_20190827_hofx.nc4
OBS_POOL - NC4_CLONE: creating H(x) file, Grid 01: usec3km_obs_sst_20190827_hofx.nc4
OBS_POOL - NC4_CLONE: creating H(x) file, Grid 01: usec3km_obs_sss_20190827_hofx.nc4
OBS_POOL - NC4_CLONE: creating H(x) file, Grid 01: usec3km_obs_temp_20190827_hofx.nc4
OBS_POOL - NC4_CLONE: creating H(x) file, Grid 01: usec3km_obs_salt_20190827_hofx.nc4
OBS_POOL - NC4_CLONE: creating H(x) file, Grid 01: usec3km_obs_uv_codar_20190827_hofx.nc4Specify all IODA observation files in the input rbl4dvar.in script:
! Input observation filename(s), [1:Ngrids].
NOBSFILES == 6
OBSname == ../Data/OBS/usec3km_obs_adt_20190827.nc4 \
../Data/OBS/usec3km_obs_sst_20190827.nc4 \
../Data/OBS/usec3km_obs_sss_20190827.nc4 \
../Data/OBS/usec3km_obs_temp_20190827.nc4 \
../Data/OBS/usec3km_obs_salt_20190827.nc4 \
../Data/OBS/usec3km_obs_uv_codar_20190827.nc4If using an s4dvarin template, we have:
! Input observation filename(s), [1:Ngrids].
NOBSFILES == MyNobsFiles
OBSname == roms_obs.ncThe execution bash script sets and adds the IODA files to use:
## Set observations NetCDF filename(s) in a bash array. Usually, the "_area"
## and "_time" labels refer to area-averaged and time-averaged observations.
## Here, FprefixF='eccofs3km', FprefixC='eccofs6km', and Fsuffix='20190101'.
echo
echo " Observation File(s):"
echo
if [[ ${IODAobs} -eq 1 ]]; then
OBSnameF[${#OBSnameF[@]}]="${ObsDir}/${FprefixF}_obs_adt_${Fsuffix}.nc4"
OBSnameF[${#OBSnameF[@]}]="${ObsDir}/${FprefixF}_obs_sst_${Fsuffix}.nc4"
OBSnameF[${#OBSnameF[@]}]="${ObsDir}/${FprefixF}_obs_sss_${Fsuffix}.nc4"
OBSnameF[${#OBSnameF[@]}]="${ObsDir}/${FprefixF}_obs_temp_${Fsuffix}.nc4"
OBSnameF[${#OBSnameF[@]}]="${ObsDir}/${FprefixF}_obs_salt_${Fsuffix}.nc4"
# OBSnameF[${#OBSnameF[@]}]="${ObsDir}/${FprefixF}_obs_uv_codar_${Fsuffix}.nc4"
OBSnameC[${#OBSnameC[@]}]="${ObsDir}/${FprefixC}_obs_adt_${Fsuffix}.nc4"
OBSnameC[${#OBSnameC[@]}]="${ObsDir}/${FprefixC}_obs_sst_${Fsuffix}.nc4"
OBSnameC[${#OBSnameC[@]}]="${ObsDir}/${FprefixC}_obs_sss_${Fsuffix}.nc4"
OBSnameC[${#OBSnameC[@]}]="${ObsDir}/${FprefixC}_obs_temp_${Fsuffix}.nc4"
OBSnameC[${#OBSnameC[@]}]="${ObsDir}/${FprefixC}_obs_salt_${Fsuffix}.nc4"
# OBSnameC[${#OBSnameC[@]}]="${ObsDir}/${FprefixC}_obs_uv_codar_${Fsuffix}.nc4"
else
OBSnameF="${FprefixF}_roms_obs_${Fsuffix}.nc4"
OBSnameC="${FprefixC}_roms_obs_${Fsuffix}.nc4"
fi
NobsFilesF=${#OBSnameF[@]}
NobsFilesC=${#OBSnameC[@]}
echo " Fine Resolution Grid, NobsFiles = ${NobsFilesF}"
echo
for obs in ${OBSnameF[@]}; do
obsfile=$(basename "$obs")
obspath="${HereDir%/*}/Data/OBS/${obsfile}"
if [ -f "${obspath}" ]; then
echo " OBSnameF: $obs"
else
echo " file: ${obspath} cannot be found."
exit 1
fi
done
echo
echo " Course Resolution Grid, NobsFiles = ${NobsFilesC}"
echo
for obs in ${OBSnameC[@]}; do
obsfile=$(basename "$obs")
obspath="${HereDir%/*}/Data/OBS/${obsfile}"
if [ -f "${obspath}" ]; then
echo " OBSnameC: $obs"
else
echo " file: ${obspath} cannot be found."
exit 1
fi
doneThen the substitute Perl script replaces the template values with the correct IODA files. We must use the latest version of ROMS/Bin/susbtitude Perl script.
$SUBSTITUTE $Inp4DVAR MyNobsFiles ${NobsFiles}
$SUBSTITUTE $Inp4DVAR roms_obs.nc ${OBSname[@]}We can check the log report when running submit_mixres_rbl4dvar.sh in DRYRUN mode (DRYRUN=1):
Running 4D-Var System: Cycle = 1 Outer = 0 Phase = background
Creating 4D-Var Input Script from Template: rbl4dvar_nl.in Outer = 0 Phase = background
(Resolution = 3 km, Fprefix = eccofs3km, Fsuffix = 20190101)
Multi-scale configuration, MultiScale = 1
SVCname = ../../Data/GRD/eccofs3km_Bcorr_xy.nc4
NRMnameM = ../../Data/NRM/eccofs3km_roms_nrm_multiscale_i.nc4
NRMnameI = ../../Data/NRM/eccofs3km_roms_nrm_multiscale_i.nc4
NRMnameB = ../../Data/NRM/eccofs3km_roms_nrm_multiscale_b.nc4
NRMnameF = ../../Data/NRM/eccofs3km_roms_nrm_multiscale_f.nc4
Processing 5 observation files:
OBSname: ../../Data/OBS/eccofs3km_obs_adt_20190101.nc4
OBSname: ../../Data/OBS/eccofs3km_obs_sst_20190101.nc4
OBSname: ../../Data/OBS/eccofs3km_obs_sss_20190101.nc4
OBSname: ../../Data/OBS/eccofs3km_obs_temp_20190101.nc4
OBSname: ../../Data/OBS/eccofs3km_obs_salt_20190101.nc4
Copying and editing ../s4dvar.in into rbl4dvar_nl.in
mpirun -np 12 romsM_nl roms_nl_eccofs_era5_20190101.inThe rb4dvar_nl.in file generated from the template will have:
! Input observation filename(s), [1:Ngrids].
NOBSFILES == 5
OBSname == ../../Data/OBS/eccofs3km_obs_adt_20190101.nc4 \
../../Data/OBS/eccofs3km_obs_sst_20190101.nc4 \
../../Data/OBS/eccofs3km_obs_sss_20190101.nc4 \
../../Data/OBS/eccofs3km_obs_temp_20190101.nc4 \
../../Data/OBS/eccofs3km_obs_salt_20190101.nc4I know it is a little bit complicated, but everything is scripted to avoid confusion and errors.
The Interface for Observation Data Access (IODA) is a standardized data assimilation format that includes input observation and output diagnostic files. The Joint Center for Satellite Data Assimilation (JCSDA) developed it as part of its Joint Effort for Data asimilation Integration (JEDI) framework to facilitate processing of various observation data streams. IODA files are structured as hierarchical NetCDF-4 files that use the underlying HDF5 data model. This structure allows organization similar to subdirectories within a file.
Different groups within an IODA file can contain variables with the same name without conflict. This feature lets you store various data properties, including observation values, observation errors, quality control flags, model H(x) values, background errors, innovations, increments, residuals, and more.
In IODA, an enhanced NetCDF-4 file with extension .nc4, consists of several components, including: dimensions, dimension variables, global attributes, groups, and group variables:
netcdf app_obs_xxx_yyyymmdd.nc4 {
dimensions:
Location = 10332 ;
nvars = 2 ;
survey = 72 ;
variables:
...
// global attributes:
:_ioda_layout = "ObsGroup" ;
...
group: MetaData {
variables:
...
} // group MetaData
group: ObsError {
variables:
...
} // group ObsError
group: ObsValue {
variables:
...
} // group ObsValue
group: PreQC {
variables:
...
} // group PreQC
group: ??? {
variables:
...
} // group ???
}The MetaData Group contains variables that describe each observation's multidimensional coordinates and other characteristics. This group is the top level of the hierarchical structure in the IODA file. Additional variables provide unique functionality in native ROMS 4D-Var and ROMS-JEDI applications without affecting compatibility with the general JEDI building blocks.
| MetaData Variables | Algorithms | Description |
|---|---|---|
| dateTime | JEDI/native | Time for each observation (seconds since reference date-time) |
| depth | JEDI/native | Depth below sea level for each observation (meter; negative) |
| latitude | JEDI/native | Latitude for each observation (degrees_north) |
| longitude | JEDI/native | Longitude for each observation (degrees_east) |
| sequenceNumber | JEDI | Original sequence order to unmap parallel Round Robin IODA processing |
| variables_name | JEDI | UFO/IODA observation standard name (string) |
| dateTimeAverageBegin | native ROMS | Observation start of time-averaged window (seconds since reference date-time) |
| dateTimeAverageEnd | native ROMS | Observation end of time-averaged window (seconds since reference date-time) |
| provenance | native ROMS | Observation origin identifier (integer) |
| spatialAverage | native ROMS | Observation spatial-averaged radius for H(x) operator |
| stateID | native ROMS | Internal ROMS state vector variable indentifier associtated with observation (integer) |
| surveyIndex | native ROMS | Observation survey time indices (integer) as they appear in dateTime |
| surveyTime | native ROMS | Observation survey time (seconds since reference date-time) |
| x_grid | native ROMS | Fractional X-grid location for each observation (floating-point) |
| y_grid | native ROMS | Fractional Y-grid location for each observation (floating-point) |
| y_grid | native ROMS | Fractional Z-grid location for each observation (floating-point) |
Here is an example of the MetaData schema demonstrating the current group variables. The design is expandable, allowing you to add variables as needed for new observational data streams and complex H(x) operators.
group: MetaData {
variables:
int64 dateTime(Location) ;
dateTime:long_name = "elapsed observation time since reference" ;
dateTime:units = "seconds since 2019-08-27T00:00:00Z" ;
dateTime:_FillValue = -2147483643LL ;
int64 dateTimeAverageBegin(Location) ;
dateTimeAverageBegin:long_name = "start of time averaging filter" ;
dateTimeAverageBegin:units = "seconds since 2019-08-27T00:00:00Z" ;
dateTimeAverageBegin:filter = "12-hour half-length averaging" ;
dateTimeAverageBegin:_FillValue = -2147483643LL ;
int64 dateTimeAverageEnd(Location) ;
dateTimeAverageEnd:long_name = "end of time averaging filter" ;
dateTimeAverageEnd:units = "seconds since 2019-08-27T00:00:00Z" ;
dateTimeAverageEnd:filter = "12-hour half-length averaging" ;
dateTimeAverageEnd:_FillValue = -2147483643LL ;
float depth(Location) ;
depth:long_name = "observation depth below sea level" ;
depth:units = "meter" ;
depth:negative = "downwards" ;
depth:_FillValue = 1.e+37f ;
float latitude(Location) ;
latitude:long_name = "observation latitude" ;
latitude:units = "degrees_north" ;
latitude:_FillValue = 1.e+37f ;
float longitude(Location) ;
longitude:long_name = "observation longitude" ;
longitude:units = "degrees_east" ;
longitude:_FillValue = 1.e+37f ;
int provenance(Location) ;
provenance:long_name = "observation origin identifier" ;
provenance:_FillValue = 0 ;
int sequenceNumber(Location) ;
sequenceNumber:long_name = "observation sequence number" ;
sequenceNumber:_FillValue = -1 ;
float spatialAverage(nvars) ;
spatialAverage:long_name = "spatial averaging radius" ;
spatialAverage:units = "meter" ;
spatialAverage:_FillValue = 1.e+37f ;
int stateID(nvars) ;
stateID:long_name = "state variable index" ;
stateID:_FillValue = -1 ;
int surveyIndex(survey) ;
surveyIndex:long_name = "observation survey time indices as they appear in dateTime" ;
surveyIndex:_FillValue = -1 ;
int64 surveyTime(survey) ;
surveyTime:long_name = "observation survey time" ;
surveyTime:units = "seconds since 2019-08-27T00:00:00Z" ;
surveyTime:_FillValue = -2147483643LL ;
string variables_name(nvars) ;
variables_name:long_name = "observation UFO/IODA standard name" ;
string variables_name:_FillValue = "MISSING" ;
float x_grid(Location) ;
x_grid:long_name = "observation fractional x-grid location" ;
x_grid:valid_min = 0.5 ;
x_grid:valid_max = 559.5 ;
x_grid:_FillValue = 1.e+37f ;
float y_grid(Location) ;
y_grid:long_name = "observation fractional y-grid location" ;
y_grid:valid_min = 0.5 ;
y_grid:valid_max = 241.5 ;
y_grid:_FillValue = 1.e+37f ;
float z_grid(Location) ;
z_grid:long_name = "observation fractional z-grid location" ;
z_grid:valid_min = 1.f ;
z_grid:valid_max = 50.f ;
z_grid:_FillValue = 1.e+37f ;
} // group MetaDataNote
- Each observation is characterized by its
locationin space and time (longitude, latitude, depth, dateTime). - All the variables in the MetaData Group are organized and declared in alphabetical order.
- All the observations are assumed to be located at RHO-points, grid cell center (A-grid).
- ROMS active state tracer variables are practical salinity (nondimensional, PSS-78) and potential temperature (Celsius). Keep this in mind when processing the raw data. Modern observations may use absolute salinity (g/kg; TEOS10) and in insitu temperature (Celsius). A practical salinity value of 35 equals an absolute salinity value of about 35.165 g/kg. Absolute salinity values are generally about 0.5 percent higher than practical salinity values. The ROMS equation of state for seawater is based on Jacket and McDougall (1995) and not on TEOS10.
- The data uses single precision (
float), so there's no need for double precision since the observations aren't measured with that accuracy. As a result, the files will be significantly smaller. - Time is measured in seconds from the reference date. It is an
integer(int64, see variables dateTime, dateTimeAverageBegin, dateTimeAverageEnd and surveyTime above). You don't need to store time data as double precision, and using integers avoids rounding errors. The timestep in ROMS data assimilation is not in microseconds anyway. As a result, the file will be smaller and easier to transfer. - ROMS allows multiple time references. I haven't seen any benefit in using, for example, days since 2006-01-01 00:00:00, as we have done with our USEC or ECCOFS applications. It is prone to many rounding errors. We could use seconds from the start of the simulation year or, better yet, seconds since the data-assimilation cycle date to create smaller files and improve compression. It doesn't matter which reference date we choose for the observation files. ROMS will set it correctly!
- Usually the IODA filenames are in the form:
PREFIX_SOURCE_YYYYMMDD.nc4, where the PREFIX is an identifier for the application likeeccofs3km_obs,usec6km_obs,wc13_obs, and so on. The SOURCE is a keyword for the observation type, such asadt,sst,sss,temp,salt,uv_codar, and many other observation labels. We recommend enhancing the SOURCE label with time- and area-averaged attributes, such asadt_time18_area60for 18-hour and 60 km averaging. Finally, YYYYMMDD is the start of the data assimilation cycle using ISO 8601 guidelines for representing calendar dates (four-digit year, two-digit month, and two-digit day). - Repetitive observations should have their provenance multiplied by -1 so we can quickly identify them during processing. Usually, we use repetitive altimetry data that is added at different times but with different (larger) observation errors. This strategy has been deprecated and replaced with area- and time-averaging H(x) operators. The
roms2ioda.mMATLAB script will assign negative provenance to repetitive altimetry data. It will remove these repetitive observations if you specify the area- and time-averaging parameters during conversion. - If the variables dateTimeAverageBegin and dateTimeAverageEnd are present in the MetaData Group, ROMS will apply the time-averaged H(x) operator for each observation location in space and time coordinates.
- If the variable spatialAverage is present in the MetaData Group, ROMS will apply the area-averaged H(x) operator for each observation location in space and time coordinates. It is restricted to surface observations such as ADT, SST, SSS, and HF-Radar velocity.
- The idea is to store observation data in a container with user-defined divisions. For example, we could have containers for annual observations. Then, we can create a script that extracts and generates the necessary IODA files for a specific data assimilation cycle. The user decides whether to save the IODA files. The user or the operational environment determines these protocols.
The IODA-type input and output files consist of multiple hierarchical groups that organize various fields related to observations,
H(x) computations, and data assimilation diagnostics. The table below summarizes all the groups (⬇️ input, ⬆️ output,
or
| Data Group | DA System | Description |
|---|---|---|
| native ROMS, JEDI | Stores the error associated with each observation | |
| native ROMS, JEDI | Holds observational data | |
| native ROMS, JEDI | Stores quality control information flag | |
| ⬆️ EffectiveError0 | JEDI | Stores initial observation effective error after QC steps |
| ⬆️ EffectiveError1 | JEDI | Stores final observation effective error after QC steps |
| ⬆️ EffectiveQC0 | JEDI | Stores initial QC value given by QCflags enumeration object |
| ⬆️ EffectiveQC1 | JEDI | Stores final QC value given by QCflags enumeration object |
| ⬆️ ObsBias0 | JEDI | Stores information about initial/prior observation bias correction |
| ⬆️ ObsBias1 | JEDI | Stores information about final/analysis observation bias correction |
| ⬆️ hofx0 | JEDI | Stores first guess prior at the observation locations, H(Xb) operator |
| ⬆️ hofx1 | JEDI | Stores data assimilation analysis at the observation locations, H(Xa) operator |
| ⬆️ oman | JEDI | Stores observation minus analysis, residual, y - H(Xa) |
| ⬆️ ombg | JEDI | Stores observation minus background/prior, innovation, y - H(Xb) |
| ⬆️ BackgroundError | native ROMS | Stores background/prior error at the observation locations |
| ⬆️ HofxInitial | native ROMS | Stores first guess prior at the observation locations, H(Xb) operator |
| ⬆️ HofxFinal | native ROMS | Stores data assimilation analysis at the observation locations, H(Xa) operator |
| ⬆️ Innovation | native ROMS | Stores observation minus background/prior, y - H(Xb) |
| ⬆️ Increment | native ROMS | Stores analysis minus background/prior, H(Xa) - H(Xb) |
| ⬆️ Residual | native ROMS | Stores observation minus analysis, y - H(Xa) |
Below is an example of an input HF-Radar velocity observations IODA file designed for the native ROMS 4D-Var. This file includes. The CODAR velocities are located at a depth of -2 m and at RHO-points (the grid cell center). Its vector components are in the geographical EAST and North directions. The new ROMS H(x) operators will interpolate the model to a depth of -2 m and apply the required variable changes for staggered C-grid locations and rotation to curvilinear coordinates.
usec6km_obs_uv_codar_20190830.nc4 {
dimensions:
Location = 10332 ;
nvars = 2 ;
survey = 72 ;
variables:
int Location(Location) ;
Location:suggested_chunk_dim = 512 ;
Location:_FillValue = -1 ;
int nvars(nvars) ;
nvars:suggested_chunk_dim = 100 ;
nvars:_FillValue = -1 ;
int survey(survey) ;
survey:suggested_chunk_dim = 100 ;
survey:_FillValue = -1 ;
// global attributes:
:_ioda_layout = "ObsGroup" ;
:_ioda_layout_version = 3 ;
:odb_version = 1 ;
:roms_grid = 279, 120, 50 ;
:date_time = 2019082700. ;
:datetimeReference = "2019-08-27T00:00:00Z" ;
:description = "Native ROMS 4D-Var observations file converted to IODA" ;
:sourceFiles = "OBS/usec6km_roms_obs_20190827.nc4" ;
:history = "Created from Matlab script: create_ioda_obs on Wednesday - August 12, 2026 - 5:55:8.1567 PM" ;
group: MetaData {
variables:
int64 dateTime(Location) ;
dateTime:long_name = "elapsed observation time since reference" ;
dateTime:units = "seconds since 2019-08-27T00:00:00Z" ;
dateTime:_FillValue = -2147483643LL ;
float depth(Location) ;
depth:long_name = "observation depth below sea level" ;
depth:units = "meter" ;
depth:negative = "downwards" ;
depth:_FillValue = 1.e+37f ;
float latitude(Location) ;
latitude:long_name = "observation latitude" ;
latitude:units = "degrees_north" ;
latitude:_FillValue = 1.e+37f ;
float longitude(Location) ;
longitude:long_name = "observation longitude" ;
longitude:units = "degrees_east" ;
longitude:_FillValue = 1.e+37f ;
int provenance(Location) ;
provenance:long_name = "observation origin identifier" ;
provenance:_FillValue = 0 ;
int sequenceNumber(Location) ;
sequenceNumber:long_name = "observation sequence number" ;
sequenceNumber:_FillValue = -1 ;
int stateID(nvars) ;
stateID:long_name = "state variable index" ;
stateID:_FillValue = -1 ;
int surveyIndex(survey) ;
surveyIndex:long_name = "observation survey time indices as they appear in dateTime" ;
surveyIndex:_FillValue = -1 ;
int64 surveyTime(survey) ;
surveyTime:long_name = "observation survey time" ;
surveyTime:units = "seconds since 2019-08-27T00:00:00Z" ;
surveyTime:_FillValue = -2147483643LL ;
string variables_name(nvars) ;
variables_name:long_name = "observation UFO/IODA standard name" ;
string variables_name:_FillValue = "MISSING" ;
float x_grid(Location) ;
x_grid:long_name = "observation fractional x-grid location" ;
x_grid:valid_min = 0.5 ;
x_grid:valid_max = 279.5 ;
x_grid:_FillValue = 1.e+37f ;
float y_grid(Location) ;
y_grid:long_name = "observation fractional y-grid location" ;
y_grid:valid_min = 0.5 ;
y_grid:valid_max = 120.5 ;
y_grid:_FillValue = 1.e+37f ;
float z_grid(Location) ;
z_grid:long_name = "observation fractional z-grid location" ;
z_grid:valid_min = 1.f ;
z_grid:valid_max = 50.f ;
z_grid:_FillValue = 1.e+37f ;
} // group MetaData
group: ObsError {
variables:
float waterZonalVelocity(Location) ;
waterZonalVelocity:long_name = "observation error standard deviation" ;
waterZonalVelocity:units = "m s-1" ;
waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
waterZonalVelocity:_FillValue = 1.e+37f ;
float waterMeridionalVelocity(Location) ;
waterMeridionalVelocity:long_name = "observation error standard deviation" ;
waterMeridionalVelocity:units = "m s-1" ;
waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime";
waterMeridionalVelocity:_FillValue = 1.e+37f ;
} // group ObsError
group: ObsValue {
variables:
float waterZonalVelocity(Location) ;
waterZonalVelocity:long_name = "observation value" ;
waterZonalVelocity:units = "m s-1" ;
waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
waterZonalVelocity:_FillValue = 1.e+37f ;
float waterMeridionalVelocity(Location) ;
waterMeridionalVelocity:long_name = "observation value" ;
waterMeridionalVelocity:units = "m s-1" ;
waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime";
waterMeridionalVelocity:_FillValue = 1.e+37f ;
} // group ObsValue
group: PreQC {
variables:
int waterZonalVelocity(Location) ;
waterZonalVelocity:long_name = "observation preset quality control filter identifier" ;
waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
waterZonalVelocity:_FillValue = -1 ;
int waterMeridionalVelocity(Location) ;
waterMeridionalVelocity:long_name = "observation preset quality control filter identifier" ;
waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime";
waterMeridionalVelocity:_FillValue = -1 ;
} // group PreQC
}Caution
In IODA, the observation error is represented by its standard deviation and has the same units as the observation value. In contrast, in the legacy native ROMS 4D-Var file, the observation error is expressed as variance in squared units.
The table below displays the names of native ROMS 4D-Var and ROMS-JEDI observations in input and output IODA files and their relationship to the ROMS control vector. Note that the State ID has a zero digit, as in 60 and 70, indicating a surface value for a 3D variable. In ROMS-JEDI, these variables are defined in test/Utility/obsop_name_map.yaml.
| Short Name | State ID | Standard Name | IODA Variable Name |
|---|---|---|---|
| ssh, SSH | 1 | sea_surface_height_above_geoid | absoluteDynamicTopography |
| u, uocn | 4 | eastward_sea_water_velocity | waterZonalVelocity |
| v, vocn | 5 | northward_sea_water_velocity | waterMeridionalVelocity |
| sst, SST | 6, 60 | sea_surface_temperature | seaSurfaceTemperature |
| tocn, temp | 6 | sea_water_temperature | waterTemperature |
| sss, SSS | 7, 70 | sea_surface_salinity | seaSurfaceSalinity |
| socn, salt | 7 | sea_water_salinity | salinity |
Integrating sea surface height (SSH) into ocean models is complex because it requires specialized processing operators. The zeta (𝜁) state variable in ROMS represents the free surface. This variable is a time-varying, physical boundary condition at the top of the numerical grid and is influenced by waves, tides, and currents. It can be interpreted as the sea-level anomaly relative to an unspecified geopotential datum. Conversely, SSH is an instantaneous measurement typically obtained from satellite altimeters. This measurement is relative to a reference ellipsoid and provides essential information about the Geoid, ocean dynamics, and seawater steric effects resulting from variations in temperature and salinity (density).
The Geoid is an undulating surface that represents the level the ocean would reach if it were influenced only by Earth's gravity and rotation, without effects from currents or winds. The SSH can also be calculated with respect to the Geoid, referred to as Absolute Dynamic Topography (ADT), as shown in the diagram below.
Therefore, the evolution of 𝜁(x,y,t) represents the instantaneous SSH, but several corrections to altimetry data are required to assimilate it correctly in a particular application.
In native ROMS 4D-Var, the Sea Level Anomaly (SLA) derived from altimetry represents the change in sea level after removing tidal effects. The Absolute Dynamic Topography (ADT) is calculated using ADT = MDT + SLA, where MDT stands for Mean Dynamic Topography. This value excludes tidal signals because it represents the average sea surface elevation above the Geoid.
For regional applications, such as along the U.S. East Coast, MDT is computed using the method described by Levin et al., 2018. During the data assimilation cycle in ROMS, tidal forcing is incorporated as lateral boundary conditions. Previously, ADT tracks were repeated to create an average representing a specific area and time, as illustrated below. However, this approach was suboptimal. It has since been improved by utilizing the spatialAverage, dateTimeAverageBegin, and
dateTimeAverageEnd variables in the IODA observation file.
Several MATLAB scripts in the roms_matlab repository process IODA files and convert a legacy, single native ROMS 4D-Var file into multiple IODA files. These scripts are well documented, so use the help command for any function below to see its arguments.
-
create_ioda_obs.m: It creates an IODA enhanced NetCDF-4 file from provided observation data structure. -
ioda_add_filter.m: It duplicates an input IODA enhanced NetCDF-4 observation file and adds the required variables in theMetaDataGroup for area- and/or time-averaging processing by the H(x) operators in a ROMS data assimilation application. -
ioda_ijkpos.m: It calculates IODAMetaDataobservation fractional grid locations (x_grid, y_grid, z_grid) in terms of ROMS (I, J, K) coordinates. The purpose of this computation is to facilitate the processing of H(x) operators in ROMS for curvilinear applications. -
ioda_ijpos.m: It calculates IODAMetaDataobservation fractional grid locations (x_grid, y_grid) in terms of ROMS (I, J) coordinates. The purpose of this computation is to facilitate the processing of H(x) operators in ROMS for curvilinear applications. -
ioda_metadata.m: It initializes the metadata structure, M, for IODA enhanced NetCDF-4 files. It sets the names of the data assimilation control variables, the area-averaged, and the time-averaged parameters to empty values. Users must assign these values later. This function is designed to be used before calling theioda_add_filter.mandroms2ioda.mconverters, as the resulting structure M is passed as an argument to those functions. -
ioda_read.m: It reads the IODA observation NetCDF-4 file and stores all the variables in a structure, S. -
ioda_superobs.m: It checks the provided IODA observation data and creates super-observations when there is more than one measurement of the same state variable per grid cell. At input, Sinp is either an IODA observation NetCDF-4 file or data structure. -
ioda_write.m: It writes an observation data structure into an IODA observation NetCDF-4 file. -
plot_ioda.m: It plots data fields from an IODA NetCDF-4 file. -
roms2ioda.m: It converts a legacy, single, native ROMS 4D-Var file into multiple IODA enhanced NetCDF-4 files.
Important
The MATLAB script roms2ioda relies on several additional functions, including get_roms_grid, obs_k2z, and obs_read, which are part of the official ROMS MATLAB repository. To avoid redundancy, these secondary functions are not included in this directory. However, you can find all the necessary scripts in the subdirectory ioda within the official repository, available here. We recommend using the official ROMS MATLAB repository, as the functions needed for the conversion will be automatically included in your MATLAB path when you launch the application.
To convert from a legacy, single, native 4D-Var* file into multiple IODA enhanced NetCDF-4 files, follow these steps:
- Use the Matlab script
ioda_metadatato set up the IODA metadata structure, denoted as M. This structure should contain information regarding the DA cycle length (cycle_length, hours), the area-averaged parameter (radius, in kilometers), the time-averaged parameter (time_window, half-value in hours), the NetCDF-4 names of the data assimilation control variables (ioda_vname), and the standard name (standard_name). Initially, the averaging parameters are empty.
>> M=ioda_metadata(72, true); % 72-hours (3-days) data assimilation cycle and reporting
IODA NetCDF-4 file Metadata Structure:
name: 'SSH'
cycle_length: 72
radius: []
time_window: []
ioda_vname: 'absoluteDynamicTopography'
standard_name: 'absolute_dynamic_topography'
name: 'SST'
cycle_length: 72
radius: []
time_window: []
ioda_vname: 'seaSurfaceTemperature'
standard_name: 'sea_surface_temperature'
name: 'SSS'
cycle_length: 72
radius: []
time_window: []
ioda_vname: 'seaSurfaceSalinity'
standard_name: 'sea_surface_salinity'
name: 'uv_CODAR'
cycle_length: 72
radius: []
time_window: []
ioda_vname: {'waterZonalVelocity' 'waterMeridionalVelocity'}
standard_name: {'eastward_sea_water_velocity' 'meridional_sea_water_velocity'}
name: 'ptemp'
cycle_length: 72
radius: []
time_window: []
ioda_vname: 'waterPotentialTemperature'
standard_name: 'sea_water_potential_temperature'
name: 'temp'
cycle_length: 72
radius: []
time_window: []
ioda_vname: 'waterTemperature'
standard_name: 'sea_water_temperature'
name: 'salt'
cycle_length: 72
radius: []
time_window: []
ioda_vname: 'salinity'
standard_name: 'sea_water_salinity'
name cycle_length radius time_window ioda_vname standard_name
____________ ____________ ____________ ____________ _____________________________ ___________________________________
{'SSH' } 72 {0×0 double} {0×0 double} {'absoluteDynamicTopography'} {'absolute_dynamic_topography' }
{'SST' } 72 {0×0 double} {0×0 double} {'seaSurfaceTemperature' } {'sea_surface_temperature' }
{'SSS' } 72 {0×0 double} {0×0 double} {'seaSurfaceSalinity' } {'sea_surface_salinity' }
{'uv_CODAR'} 72 {0×0 double} {0×0 double} {1×2 cell } {1×2 cell }
{'ptemp' } 72 {0×0 double} {0×0 double} {'waterPotentialTemperature'} {'sea_water_potential_temperature'}
{'temp' } 72 {0×0 double} {0×0 double} {'waterTemperature' } {'sea_water_temperature' }
{'salt' } 72 {0×0 double} {0×0 double} {'salinity' } {'sea_water_salinity' }- Execute the
roms2iodascript to convert from native to multiple IODA files. Note that it creates the potential temperature file using the sw_press and sw_ptmp functions with a constant salinity of 35.0. This file is not used, but it is created so the user knows the ROMS state variable is potential temperature, not insitu temperature.
>> ObsData = 'USEC/Data/OBS/usec3km_roms_obs_20190827.nc'; % single native classic NetCDF file
>> HisName = 'USEC/Data/INI/usec3km_roms_ini_20190827.nc'; % ROMS history NetCDF for geometry information
>> roms2ioda(ObsData, HisName, 'usec3km_obs', '20190827', M);
*** Creating observations file: usec3km_obs_adt_20190827.nc4
*** Writing observations file: usec3km_obs_adt_20190827.nc4
*** Creating observations file: usec3km_obs_sst_20190827.nc4
*** Writing observations file: usec3km_obs_sst_20190827.nc4
*** Creating observations file: usec3km_obs_sss_20190827.nc4
*** Writing observations file: usec3km_obs_sss_20190827.nc4
*** Creating observations file: usec3km_obs_temp_20190827.nc4
*** Writing observations file: usec3km_obs_temp_20190827.nc4
*** Creating observations file: usec3km_obs_ptemp_20190827.nc4
*** Writing observations file: usec3km_obs_ptemp_20190827.nc4
*** Creating observations file: usec3km_obs_salt_20190827.nc4
*** Writing observations file: usec3km_obs_salt_20190827.nc4
*** Creating observations file: usec3km_obs_uv_codar_20190827.nc4
*** Writing observations file: usec3km_obs_uv_codar_20190827.nc4- To create a specific IODA file with information about area-averaging and/or time-averaging for the respective H(x) averaging operators, use the function
ioda_add_filter.m. Here, we specify the half-length averaging time in hours for each datum. Thus, we recommend that such an IODA file contain a single control variable. Time averaging will be truncated for observations near the start or end of the data assimilation cycle.
>> M(strcmp({M.name}, 'SSH')).radius = 30; % 30 km averaging radius
>> M(strcmp({M.name}, 'SSH')).time_window = 18; % 36 hours averaging time-window centered at observation time
% |<------------ 36 h ------------->|
% |---------- dateTime(n) ----------|
% |<-----18 h----->|<-----18 h----->|
>> disp( struct2table(M) ); % Check that parameters in the metadata structure are correct
name cycle_length radius time_window ioda_vname standard_name
____________ ____________ ____________ ____________ _____________________________ ___________________________________
{'SSH' } 72 {[ 30]} {[ 18]} {'absoluteDynamicTopography'} {'absolute_dynamic_topography' }
{'SST' } 72 {0×0 double} {0×0 double} {'seaSurfaceTemperature' } {'sea_surface_temperature' }
{'SSS' } 72 {0×0 double} {0×0 double} {'seaSurfaceSalinity' } {'sea_surface_salinity' }
{'uv_CODAR'} 72 {0×0 double} {0×0 double} {1×2 cell } {1×2 cell }
{'ptemp' } 72 {0×0 double} {0×0 double} {'waterPotentialTemperature'} {'sea_water_potential_temperature'}
{'temp' } 72 {0×0 double} {0×0 double} {'waterTemperature' } {'sea_water_temperature' }
{'salt' } 72 {0×0 double} {0×0 double} {'salinity' } {'sea_water_salinity' }
>> ioda_add_filter('usec3km_obs_adt_20190827.nc4', 'usec3km_obs_adt_area30_time36_20190827.nc4',M);
*** Creating observations file: usec3km_obs_adt_area30_time36_20190827.nc4
*** Writing IODA observations file: usec3km_obs_adt_area30_time36_20190827.nc4Check your IODA file for the correct averaging parameters before running the data assimilation system:
% ncdump -v spatialAverage usec3km_adt_area30_time36_20190827.nc4
netcdf usec3km_obs_adt_area30_time36_20190827 {
...
group: MetaData {
variables:
int64 dateTime(Location) ;
dateTime:long_name = "elapsed observation time since reference" ;
dateTime:units = "seconds since 2019-08-27T00:00:00Z" ;
dateTime:_FillValue = -2147483643LL ;
int64 dateTimeAverageBegin(Location) ;
dateTimeAverageBegin:long_name = "start of time averaging filter" ;
dateTimeAverageBegin:units = "seconds since 2019-08-27T00:00:00Z" ;
dateTimeAverageBegin:filter = "18-hour half-length averaging" ;
dateTimeAverageBegin:_FillValue = -2147483643LL ;
int64 dateTimeAverageEnd(Location) ;
dateTimeAverageEnd:long_name = "end of time averaging filter" ;
dateTimeAverageEnd:units = "seconds since 2019-08-27T00:00:00Z" ;
dateTimeAverageEnd:filter = "18-hour half-length averaging" ;
dateTimeAverageEnd:_FillValue = -2147483643LL ;
float latitude(Location) ;
latitude:long_name = "observation latitude" ;
latitude:units = "degrees_north" ;
latitude:_FillValue = 1.e+37f ;
float longitude(Location) ;
longitude:long_name = "observation longitude" ;
longitude:units = "degrees_east" ;
longitude:_FillValue = 1.e+37f ;
int provenance(Location) ;
provenance:long_name = "observation origin identifier" ;
provenance:negative = "repetitive observation at different times and error" ;
provenance:_FillValue = 0 ;
int sequenceNumber(Location) ;
sequenceNumber:long_name = "observation sequence number" ;
sequenceNumber:_FillValue = -1 ;
float spatialAverage(nvars) ;
spatialAverage:long_name = "spatial averaging radius" ;
spatialAverage:units = "meter" ;
spatialAverage:_FillValue = 1.e+37f ;
int stateID(nvars) ;
stateID:long_name = "state variable index" ;
stateID:_FillValue = -1 ;
int surveyIndex(survey) ;
surveyIndex:long_name = "observation survey time indices as they appear in dateTime" ;
surveyIndex:_FillValue = -1 ;
int64 surveyTime(survey) ;
surveyTime:long_name = "observation survey time" ;
surveyTime:units = "seconds since 2019-08-27T00:00:00Z" ;
surveyTime:_FillValue = -2147483643LL ;
string variables_name(nvars) ;
variables_name:long_name = "observation UFO/IODA standard name" ;
string variables_name:_FillValue = "MISSING" ;
float x_grid(Location) ;
x_grid:long_name = "observation fractional x-grid location" ;
x_grid:valid_min = 0.5 ;
x_grid:valid_max = 560 ;
x_grid:_FillValue = 1.e+37f ;
float y_grid(Location) ;
y_grid:long_name = "observation fractional y-grid location" ;
y_grid:valid_min = 0.5 ;
y_grid:valid_max = 242 ;
y_grid:_FillValue = 1.e+37f ;
data:
spatialAverage = 30000 ;
} // group MetaData


