diff --git a/internal/test_unit/python/comp_dir.py b/internal/test_unit/python/comp_dir.py index 3f6bc6cf64..cc9aa5e6c3 100755 --- a/internal/test_unit/python/comp_dir.py +++ b/internal/test_unit/python/comp_dir.py @@ -14,7 +14,7 @@ raise e sys.path.append(metplus_util_path) -from diff_util import compare_dir +import diff_util as du def comp_dir(truth_dir, output_dir, debug=True, save_diff=True): @@ -43,11 +43,15 @@ def comp_dir(truth_dir, output_dir, debug=True, save_diff=True): """ start_time = dt.datetime.now() + + # Custom rounding overrides for MET unit test output + du.ROUNDING_OVERRIDES['ALAL2010_stat.out'] = 4 + print('******************************') print("Comparing output to truth data") - diff_files = compare_dir(truth_dir, output_dir, - debug=debug, - save_diff=save_diff) + diff_files = du.compare_dir(truth_dir, output_dir, + debug=debug, + save_diff=save_diff) end_time = dt.datetime.now() runtime = end_time - start_time diff --git a/src/libcode/vx_data2d_nc_cf/nc_cf_file.cc b/src/libcode/vx_data2d_nc_cf/nc_cf_file.cc index 00dc6f8fba..2505f074d0 100644 --- a/src/libcode/vx_data2d_nc_cf/nc_cf_file.cc +++ b/src/libcode/vx_data2d_nc_cf/nc_cf_file.cc @@ -1912,6 +1912,20 @@ void NcCfFile::get_grid_mapping_lambert_conformal_conic(const NcVar *grid_mappin return; } + // false_easting, optional + + double false_east = get_nc_var_att_double( + grid_mapping_var, "false_easting", false); + + if(is_bad_data(false_east)) false_east = 0.0; + + // false_northing, optional + + double false_north = get_nc_var_att_double( + grid_mapping_var, "false_northing", false); + + if(is_bad_data(false_north)) false_north = 0.0; + // Look for the x/y dimensions and x/y coordinate variables find_xy_vars(method_name); @@ -1987,10 +2001,11 @@ void NcCfFile::get_grid_mapping_lambert_conformal_conic(const NcVar *grid_mappin get_nc_data(_yCoordVar, y_values.data()); - // Unit conversion + // MET #3374 Support false easting and northing offsets + // Unit conversion and false northing and easting offsets - for (int i = 0; ilevel().is_offset()) { + dimension[k] = nint(vinfo_nc->level().lower()); + continue; + } + + if (dimension[k] != vx_data2d_dim_by_value && dimension[k] != range_flag) continue; + + string dim_name = GET_NC_NAME(get_nc_dim(info->var, k)); + NcVarInfo *var_info = find_var_info_by_dim_name(WrfNc->Var, dim_name, + WrfNc->Nvars); + if (var_info == nullptr) continue; + + if (dimension[k] == vx_data2d_dim_by_value) { + long new_offset = get_index_at_nc_data(var_info->var, + vinfo_nc->dim_value(k), + dim_name, (k == info->t_slot)); + if (new_offset != bad_data_int) dimension[k] = new_offset; + else { + mlog << Warning << "\n" << method_name + << "for \"" << vinfo_nc->req_name() + << "\" variable, the dimension value " + << vinfo_nc->dim_value(k) << " for " << dim_name + << " does not exist\n\n"; + return false; + } + } + else if (dimension[k] == range_flag) { + double lower = vinfo_nc->level().lower(); + double upper = vinfo_nc->level().upper(); + long new_offset = get_index_at_nc_data(var_info->var, lower, upper, + dim_name, (k == info->t_slot)); + if (new_offset != bad_data_int) dimension[k] = new_offset; + else { + mlog << Warning << "\n" << method_name + << "for \"" << vinfo_nc->req_name() + << "\" variable, the dimension value between " + << lower << " and " << upper << " for " << dim_name + << " does not exist\n\n"; + return false; + } + } + } + return true; + } + +//////////////////////////////////////////////////////////////////////// + bool MetNcWrfDataFile::data_plane(VarInfo &vinfo, DataPlane &plane) { bool status = false; double pressure; ConcatString level_str; - VarInfoNcWrf * vinfo_nc = (VarInfoNcWrf *) &vinfo; - NcVarInfo *info = (NcVarInfo *) nullptr; + auto vinfo_nc = (VarInfoNcWrf *) &vinfo; + auto info = (NcVarInfo *) nullptr; // Initialize the data plane plane.clear(); // Read the data WrfNc->get_nc_var_info(vinfo_nc->req_name().c_str(), info); + LongArray dimension = vinfo_nc->dimension(); - int dim_count = dimension.n_elements(); - for (int k=0; kvar, k)); - NcVarInfo *var_info = find_var_info_by_dim_name(WrfNc->Var, dim_name, - WrfNc->Nvars); - if (var_info) { - long new_offset = get_index_at_nc_data(var_info->var, - vinfo_nc->dim_value(k), - dim_name, (k == info->t_slot)); - if (new_offset != bad_data_int) dimension[k] = new_offset; - } - } - } + if (! get_real_dimension(vinfo_nc, info, dimension)) + return false; status = WrfNc->data(vinfo_nc->req_name().c_str(), dimension, plane, pressure, info); @@ -198,7 +240,7 @@ bool MetNcWrfDataFile::data_plane(VarInfo &vinfo, DataPlane &plane) { //////////////////////////////////////////////////////////////////////// int MetNcWrfDataFile::data_plane_array(VarInfo &vinfo, - DataPlaneArray &plane_array) { + DataPlaneArray &plane_array) { int i, i_dim, n_level, status, lower, upper; ConcatString level_str; double pressure, min_level, max_level; diff --git a/src/libcode/vx_data2d_nc_wrf/data2d_nc_wrf.h b/src/libcode/vx_data2d_nc_wrf/data2d_nc_wrf.h index ac29854595..11fc9963b5 100644 --- a/src/libcode/vx_data2d_nc_wrf/data2d_nc_wrf.h +++ b/src/libcode/vx_data2d_nc_wrf/data2d_nc_wrf.h @@ -34,6 +34,8 @@ class MetNcWrfDataFile : public Met2dDataFile { private: void nc_wrf_init_from_scratch(); + bool get_real_dimension(const VarInfoNcWrf *vinfo_nc, const NcVarInfo *info, + LongArray &dimension) const; MetNcWrfDataFile(const MetNcWrfDataFile &); MetNcWrfDataFile & operator=(const MetNcWrfDataFile &); diff --git a/src/libcode/vx_data2d_nc_wrf/var_info_nc_wrf.cc b/src/libcode/vx_data2d_nc_wrf/var_info_nc_wrf.cc index 10c04ca65e..aa23560f55 100644 --- a/src/libcode/vx_data2d_nc_wrf/var_info_nc_wrf.cc +++ b/src/libcode/vx_data2d_nc_wrf/var_info_nc_wrf.cc @@ -209,8 +209,23 @@ void VarInfoNcWrf::set_magic(const ConcatString &nstr, const ConcatString &lstr) as_offset = (*ptr2 != '@'); if (!as_offset) ptr2++; + ptr3 = strchr(ptr2, '-'); + //skip negative sign of the negative value to check the range + if (ptr3 != nullptr && ptr3 == ptr2) ptr3 = strchr((ptr2+1), '-'); + // Check for a range of levels - if((ptr3 = strchr(ptr2, '-')) != nullptr) { + if (ptr3 != nullptr && ptr3 != ptr2) { + *ptr3 = 0; + ptr3++; + if (*ptr3 == '@') { + if (as_offset) { + mlog << Error << "\n" << method_name + << "Can not mix an offset and a value for NetCDF variable \"" + << MagicStr << "\".\n\n"; + exit(1); + } + ptr3++; // to support @vlevel_lower-@vlevel_upper + } // Check if a range has already been supplied if(Dimension.has(range_flag)) { @@ -223,10 +238,11 @@ void VarInfoNcWrf::set_magic(const ConcatString &nstr, const ConcatString &lstr) else { add_dimension(range_flag); Level.set_lower(atoi(ptr2)); - Level.set_upper(atoi(++ptr3)); + Level.set_upper(atoi(ptr3)); // Assume pressure level type for a range of levels Level.set_type(LevelType_Pres); + Level.set_is_offset(as_offset); } } // Single level diff --git a/src/libcode/vx_data2d_ugrid/ugrid_file.cc b/src/libcode/vx_data2d_ugrid/ugrid_file.cc index 9024cdfd01..cbcc84a219 100644 --- a/src/libcode/vx_data2d_ugrid/ugrid_file.cc +++ b/src/libcode/vx_data2d_ugrid/ugrid_file.cc @@ -38,6 +38,7 @@ using namespace netCDF; static const char *def_user_config = "UGridConfig_user"; static const char *def_config_prefix = "UGridConfig_"; static const char *def_config_prefix2 = "MET_BASE/config/UGridConfig_"; +constexpr double lat_epsilon = 0.00001; array DIM_KEYS = { "dim_face", "dim_node", "dim_edge", "dim_time", "dim_vert" @@ -890,6 +891,40 @@ void UGridFile::read_config(ConcatString config_filename) { } +//////////////////////////////////////////////////////////////////////// + +void UGridFile::radian_to_degree(vector &lat_values, const int lat_count) const { + const char *method_name = "UGridFile::radian_to_degree() -> "; + int lat_adjusted = 0; + int lat_adjusted_total = 0; + for (int idx=0; idx 90.0) { + if (!is_eq(lat_values[idx], 90.0, lat_epsilon)) { + mlog << Warning << "\n" << method_name << "adjusted " << lat_values[idx] + << " (delta: " << (lat_values[idx] - 90.0) << ") to 90.0\n\n"; + lat_adjusted++; + } + lat_values[idx] = 90.0; + lat_adjusted_total++; + } + else if (lat_values[idx] < -90.0) { + if (!is_eq(lat_values[idx], -90.0, lat_epsilon)) { + mlog << Warning << "\n" << method_name << "adjusted " << lat_values[idx] + << " (delta: " << (lat_values[idx] + 90.0) << ") to -90.0\n\n"; + lat_adjusted++; + } + lat_values[idx] = -90.0; + lat_adjusted_total++; + } + } + if (lat_adjusted_total > 0) { + mlog << Debug(4) << method_name << "adjusted " << lat_adjusted << " (" + << lat_adjusted_total << ") latitudes\n"; + } +} + + //////////////////////////////////////////////////////////////////////// @@ -927,13 +962,13 @@ void UGridFile::read_netcdf_grid() if (get_var_units(_latVar, units_value)) { if (units_value == "rad" || units_value == "radian") { - mlog << Debug(6) << method_name << "convert " << units_value << " to degree for lat\n"; - for (int idx=0; idx &lat_values, const int lat_count) const; void read_config(ConcatString config_filename); void read_netcdf_grid(); diff --git a/src/libcode/vx_nc_util/nc_utils.cc b/src/libcode/vx_nc_util/nc_utils.cc index e6f77db56d..c95efc1129 100644 --- a/src/libcode/vx_nc_util/nc_utils.cc +++ b/src/libcode/vx_nc_util/nc_utils.cc @@ -1888,10 +1888,19 @@ bool get_nc_data(NcVar *var, ncbyte *data, const LongArray &dims, const LongArra return return_status; } +//////////////////////////////////////////////////////////////////////// + +ConcatString get_value_string(double value, bool is_time) { + ConcatString value_str; + if (is_time && (value > 10000000.)) value_str << unix_to_yyyymmdd_hhmmss(value); + else value_str << value; + return value_str; +} + //////////////////////////////////////////////////////////////////////// // returns matching offset or bad_data_int if not found -int get_index_at_nc_data(NcVar *var, double value, const string dim_name, bool is_time) { +int get_index_at_nc_data(NcVar *var, double value, const string &dim_name, bool is_time) { int offset = bad_data_int; static const char *method_name = "get_index_at_nc_data() -> "; if (IS_VALID_NC_P(var)) { @@ -1948,6 +1957,78 @@ int get_index_at_nc_data(NcVar *var, double value, const string dim_name, bool i return offset; } +//////////////////////////////////////////////////////////////////////// +// returns matching offset or bad_data_int if not found + +int get_index_at_nc_data(NcVar *var, double value_min, double value_max, + const string &dim_name, bool is_time) { + int offset = bad_data_int; + static const char *method_name = "get_index_at_nc_data(min,max) -> "; + if (IS_VALID_NC_P(var)) { + int data_size = get_data_size(var); + vector values(data_size); + + if (get_nc_data(var, values.data())) { + unixtime ut; + int sec_per_unit; + bool no_leap_year = get_att_no_leap_year(var); + ut = sec_per_unit = 0; + if (is_time) { + ConcatString units; + bool has_attr = get_var_units(var, units); + if (has_attr && (!units.empty())) + parse_cf_time_string(units.c_str(), ut, sec_per_unit); + else { + mlog << Warning << "\n" << method_name + << "the time variable \"" << GET_NC_NAME_P(var) + << "\" must contain a \"" + << units_att_name << "\" attribute.\n\n"; + } + } + bool found = false; + // Select the first offset between value_min and value_max + for (int idx=0; idx= value_min && values[idx] <= value_max)) { + found = true; + } + if (!found && is_time) { + unixtime time_value = add_to_unixtime(ut, sec_per_unit, + values[idx], no_leap_year); + if (is_eq(time_value, value_min) + || is_eq(time_value, value_max) + || (time_value >= value_min && time_value <= value_max)) { + found = true; + } + } + if (found) { + offset = idx; + break; + } + } + } + + ConcatString value_max_str = get_value_string(value_max, is_time); + ConcatString value_min_str = get_value_string(value_min, is_time); + if (offset == bad_data_int) + mlog << Debug(7) << method_name << "Not found value between " << value_min_str + << " and " << value_max_str << " at " << GET_NC_NAME_P(var) + << " by dimension name \"" << dim_name << "\"\n"; + else { + ConcatString value_str = get_value_string(values[offset], is_time); + mlog << Debug(7) << method_name << "Found value " << value_str + << " (index=" << offset << ") at " << GET_NC_NAME_P(var) + << " by dimension name \"" << dim_name << "\"\n"; + } + } + else { + mlog << Debug(7) << method_name << "Not found a dimension variable for \"" + << dim_name << "\"\n"; + } + return offset; +} + //////////////////////////////////////////////////////////////////////// bool get_nc_data_to_array(NcVar *var, StringArray *array_buf) { diff --git a/src/libcode/vx_nc_util/nc_utils.h b/src/libcode/vx_nc_util/nc_utils.h index ddf57055d6..0002c955fe 100644 --- a/src/libcode/vx_nc_util/nc_utils.h +++ b/src/libcode/vx_nc_util/nc_utils.h @@ -335,7 +335,8 @@ extern bool get_dim_names(const netCDF::NcFile *nc, StringArray *dimNames); extern netCDF::NcVar get_nc_var_lat(const netCDF::NcFile *nc); extern netCDF::NcVar get_nc_var_lon(const netCDF::NcFile *nc); extern netCDF::NcVar get_nc_var_time(const netCDF::NcFile *nc); -extern int get_index_at_nc_data(netCDF::NcVar *var, double value, const std::string dim_name, bool is_time=false); +extern int get_index_at_nc_data(netCDF::NcVar *var, double value, const std::string &dim_name, bool is_time=false); +extern int get_index_at_nc_data(netCDF::NcVar *var, double value_min, double value_max, const std::string &dim_name, bool is_time=false); extern netCDF::NcFile* open_ncfile(const char * nc_name, bool write = false); // Moved from nc_cf_file.cc diff --git a/src/libcode/vx_regrid/vx_regrid_budget.cc b/src/libcode/vx_regrid/vx_regrid_budget.cc index 4cf389de32..ff20396ed1 100644 --- a/src/libcode/vx_regrid/vx_regrid_budget.cc +++ b/src/libcode/vx_regrid/vx_regrid_budget.cc @@ -32,7 +32,7 @@ DataPlane met_regrid_budget(const DataPlane & from_data, const double delta = 1.0/N; #pragma omp parallel default(none) \ - shared(from_data, from_grid, to_grid, info, to_data) \ + shared(from_data, from_grid, to_grid, info, to_data, delta) \ private(count, value, sum) { diff --git a/src/tools/tc_utils/tc_rmw/tc_rmw.cc b/src/tools/tc_utils/tc_rmw/tc_rmw.cc index e14421d786..9872d44415 100644 --- a/src/tools/tc_utils/tc_rmw/tc_rmw.cc +++ b/src/tools/tc_utils/tc_rmw/tc_rmw.cc @@ -20,6 +20,7 @@ // 003 09/28/22 Prestopnik MET #2227 Remove namspace std and netCDF from header files // 004 04/26/23 Halley Gotway MET #2523 Reorder NetCDF dimensions // 005 03/11/24 Halley Gotway MET #2833 range/azimuth grid +// 006 04/13/26 Halley Gotway MET #3370 check for analysis tracks // //////////////////////////////////////////////////////////////////////// @@ -340,6 +341,12 @@ void process_track_files(const StringArray& files, // Initialize counts tot_read = tot_add = 0; + // Set metadata pointer + StringArray BestTechnique; + BestTechnique.add("BEST"); + BestTechnique.set_ignore_case(true); + line.set_best_technique(&BestTechnique); + // Process input ATCF files for(int i = 0; i < files.n(); i++) { @@ -372,7 +379,8 @@ void process_track_files(const StringArray& files, if(!is_keeper(&line)) continue; // Attempt to add current line to TrackInfoArray - if(tracks.add(line, true, false)) { + // MET #3370 check for analysis tracks + if(tracks.add(line, true, true)) { cur_add++; tot_add++; }