From 5e8cc05eab16d12e4f2d06c640d9a5e8ba86f2e7 Mon Sep 17 00:00:00 2001 From: johnhg Date: Fri, 1 Oct 2021 15:49:02 -0600 Subject: [PATCH 1/2] Feature 1823 global (#1928) * Per #1823, no actual code changes. Just making code header formatting consistent. This issue will require modifying these classes to wrap global grids. * Per #1823, more reformatting of headers for consistency. No code changes. * Per #1823, more formatting. * Per #1823, updates to the vx_util library. But note that this is just the mechanics, passing around the is_global boolean. I still need to actually update the logic to do the wrapping. I also need to update the rest of the codebase for these changes. Perhaps the grid template info should be a struct instead of several args? * Per #1823, move _isGlobal into the GridTemplate base class since that's where it's applied to the logic... which I still need to do! * Per #1823, this is a clear and obvious bugfix. The interp.vld_thresh setting was NOT getting set properly in Point-Stat and Ensemble-Stat. Need to add a call to set_interp_thresh instead of using the default >0 threshold. * Per #1823, update the GridTemplate member functions to wrap X for global grids. * Per #1823, update the interp_xy and interp_bilin functions to handle global grids where we wrap x. * Per #1823, add LatLonGrid isGlobal member and set it right when the grid is created. * Per #1823, update compute_interp() and get_interp_points() functions to handle the is_global flag. * Per #1823, remove the unused fractional_coverage_square() function rather than updating it to handle global grids. * Per #1823, update application code to make updated calls to DataPlane utility functions for point_stat, grid_stat, ensemble_stat, and shift_data_plane. * Per #1823, update PairDataPoint and PairDataEnsemble classes for global grids. * Per #1823, update regridding logic to only skip points off the grid in the x-direction for non-global grids. * Per #1823, include the interpolation method/width in the log message for skipping observations due to a bad interpolated forecast value. * Per #1823, added a positive_modulo(int, int) utility function to mimic the modulo functionality within python. This is needed to wrap negative X indices back to positive for global grids. * Per #1823, rename is_global related variables to wrap_lon instead. The point here is whether or not the longitudes span 0 to 360 and has nothing to do with the extent of the latitudes. Using is_global incorrectly implies that the latitudes span -90 to 90. * Per #1823, fix typo. * Per #1823, add a new call to regrid_data_plane that in met version 10.0.0 has a vertical stripe of missing data that is gone in this feature branch. * Per #1823, switch the regrid_data_plane call to use existing input data rather than having to add new inputs. In MET version 10.0.0, this results in a vertical line at the wrap longitude point. But feature 1823 fixes and that demonstrates that smoothing across the wrap point works well. * Per #1823, print a log message when a global grid has a longitude spacing that does not evenly divide 360. * Per #1823, working on the log message. * Per #1823, change to debug 4. * Per #1823, just whitespace * Per #1823, fix spacing. * Per #1823, correct halfheight to halfwidth. --- met/src/basic/vx_math/nint.cc | 10 + met/src/basic/vx_math/nint.h | 12 +- met/src/basic/vx_util/CircularTemplate.cc | 236 ++--- met/src/basic/vx_util/CircularTemplate.h | 130 +-- met/src/basic/vx_util/GridOffset.cc | 78 +- met/src/basic/vx_util/GridOffset.h | 83 +- met/src/basic/vx_util/GridPoint.cc | 88 +- met/src/basic/vx_util/GridPoint.h | 77 +- met/src/basic/vx_util/GridTemplate.cc | 963 +++++++++--------- met/src/basic/vx_util/GridTemplate.h | 304 +++--- met/src/basic/vx_util/RectangularTemplate.cc | 176 ++-- met/src/basic/vx_util/RectangularTemplate.h | 158 +-- met/src/basic/vx_util/data_plane_util.cc | 170 +--- met/src/basic/vx_util/data_plane_util.h | 9 +- met/src/basic/vx_util/interp_util.cc | 89 +- met/src/basic/vx_util/interp_util.h | 15 +- met/src/libcode/vx_grid/gaussian_grid.cc | 2 +- met/src/libcode/vx_grid/gaussian_grid.h | 2 +- met/src/libcode/vx_grid/goes_grid.cc | 2 +- met/src/libcode/vx_grid/goes_grid.h | 2 +- met/src/libcode/vx_grid/grid_base.cc | 6 +- met/src/libcode/vx_grid/grid_base.h | 6 +- met/src/libcode/vx_grid/latlon_grid.cc | 57 +- met/src/libcode/vx_grid/latlon_grid.h | 14 +- met/src/libcode/vx_grid/lc_grid.cc | 2 +- met/src/libcode/vx_grid/lc_grid.h | 2 +- met/src/libcode/vx_grid/merc_grid.cc | 2 +- met/src/libcode/vx_grid/merc_grid.h | 2 +- met/src/libcode/vx_grid/rot_latlon_grid.cc | 26 +- met/src/libcode/vx_grid/rot_latlon_grid.h | 10 +- met/src/libcode/vx_grid/st_grid.cc | 2 +- met/src/libcode/vx_grid/st_grid.h | 2 +- met/src/libcode/vx_regrid/vx_regrid.cc | 23 +- met/src/libcode/vx_regrid/vx_regrid_budget.cc | 2 +- met/src/libcode/vx_statistics/apply_mask.cc | 2 +- met/src/libcode/vx_statistics/pair_base.cc | 10 +- met/src/libcode/vx_statistics/pair_base.h | 2 + .../vx_statistics/pair_data_ensemble.cc | 11 +- .../vx_statistics/pair_data_ensemble.h | 2 +- .../libcode/vx_statistics/pair_data_point.cc | 26 +- .../tools/core/ensemble_stat/ensemble_stat.cc | 13 +- .../ensemble_stat/ensemble_stat_conf_info.cc | 1 + met/src/tools/core/grid_stat/grid_stat.cc | 20 +- met/src/tools/core/point_stat/point_stat.cc | 9 +- .../core/point_stat/point_stat_conf_info.cc | 1 + .../shift_data_plane/shift_data_plane.cc | 2 +- test/xml/unit_regrid.xml | 20 +- 47 files changed, 1224 insertions(+), 1657 deletions(-) diff --git a/met/src/basic/vx_math/nint.cc b/met/src/basic/vx_math/nint.cc index f027903500..d013916ca3 100644 --- a/met/src/basic/vx_math/nint.cc +++ b/met/src/basic/vx_math/nint.cc @@ -46,3 +46,13 @@ return ( a ); //////////////////////////////////////////////////////////////////////// +int positive_modulo(int i, int n) + +{ + +return ( i % n + n ) % n; + +} + + +//////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_math/nint.h b/met/src/basic/vx_math/nint.h index 3c466858d6..712823ecba 100644 --- a/met/src/basic/vx_math/nint.h +++ b/met/src/basic/vx_math/nint.h @@ -11,8 +11,8 @@ //////////////////////////////////////////////////////////////////////// -#ifndef __PS_DITHER_NINT_H__ -#define __PS_DITHER_NINT_H__ +#ifndef __VX_MATH_NINT_H__ +#define __VX_MATH_NINT_H__ //////////////////////////////////////////////////////////////////////// @@ -24,7 +24,13 @@ extern int nint(double); //////////////////////////////////////////////////////////////////////// -#endif // __PS_DITHER_NINT_H__ +extern int positive_modulo(int, int); + + +//////////////////////////////////////////////////////////////////////// + + +#endif // __VX_MATH_NINT_H__ //////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/CircularTemplate.cc b/met/src/basic/vx_util/CircularTemplate.cc index a49f40c62f..2b2bac65d6 100644 --- a/met/src/basic/vx_util/CircularTemplate.cc +++ b/met/src/basic/vx_util/CircularTemplate.cc @@ -1,47 +1,25 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 -// ** University Corporation for Atmospheric Research (UCAR) -// ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -// RCS info: dixon $ -// $Locker: $ -// $Date: 2016/03/03 18:19:27 $ -// $Id: CircularTemplate.cc,v 1.6 2016/03/03 18:19:27 dixon Exp $ -// $Revision: 1.6 $ -// $State: Exp $ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ -/********************************************************************* - * CircularTemplate.cc: class implementing a circular template to be - * applied on gridded data. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - *********************************************************************/ +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 +// ** University Corporation for Atmospheric Research (UCAR) +// ** National Center for Atmospheric Research (NCAR) +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: CircularTemplate.cc +// +// Description: +// Class implementing a Circular template to be +// applied on gridded data. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// 001 09-07-21 Halley Gotway Add wrap_lon. +// +/////////////////////////////////////////////////////////////////////////////// #include @@ -56,126 +34,92 @@ using namespace std; +/////////////////////////////////////////////////////////////////////////////// -/********************************************************************** - * Constructor - */ +CircularTemplate::CircularTemplate(const int width, bool wrap_lon) : + GridTemplate(), _width(width) { -CircularTemplate::CircularTemplate(const int width) : - GridTemplate(), - _width(width) -{ + _wrapLon = wrap_lon; - //width of 2 is not supported - if (width == 2){ - mlog << Error << "\nCircularTemplate::CircularTemplate() -> " - << "unsupported width of " << width - << " for circles.\n\n"; + // width of 2 is not supported + if (width == 2) { + mlog << Error << "\nCircularTemplate::CircularTemplate() -> " + << "unsupported width of " << width << " for circles.\n\n"; exit(1); } - - bool evenWidth = ((width % 2) == 0); - // if the width is even, that means we are dealing with a point interpolation - // because grid interpolation has to be odd. - // for an ODD WIDTH the reference point is the same as the center point and is the nearest grid point - - // for an EVEN WIDTH, we move the "reference" point, to the lower left grid point, - // this means offsets are stored relative to the lower left corner of the true center. - // but we find distances based on the the true center location when determining if an - // offset is within the circle. - - double radius = (width-1)/2.0; + bool evenWidth = ((width % 2) == 0); + + // if the width is even, that means we are dealing with a point interpolation + // because grid interpolation has to be odd. + // for an ODD WIDTH the reference point is the same as the center point and is the nearest grid point + // for an EVEN WIDTH, we move the "reference" point, to the lower left grid point, + // this means offsets are stored relative to the lower left corner of the true center. + // but we find distances based on the the true center location when determining if an + // offset is within the circle. + + double radius = (width-1)/2.0; - // Create the offsets list. - - // If the radius is small, then just make the - // template cover the current grid square. - // radius < 1 no longer supported. - /* - if (radius < 1.0) - { - _addOffset(0, 0); - return; - } - */ + // Create the offsets list. - // need to increase the area we look at if the width is even, because - // some valid offset points will actually be farther from the reference point - // than the radius, because the reference point is offset from the true - // center of the circle. - int maxOffset = static_cast(floor(radius)); - if (evenWidth){ - maxOffset++; - } + // Need to increase the area we look at if the width is even, because + // some valid offset points will actually be farther from the reference point + // than the radius, because the reference point is offset from the true + // center of the circle. + + int maxOffset = static_cast(floor(radius)); + if(evenWidth) maxOffset++; - int minOffset = static_cast(floor(-1 * radius)); + int minOffset = static_cast(floor(-1 * radius)); - for (int y = minOffset; y <= maxOffset; y++) - { - for (int x = minOffset; x <= maxOffset; x++) - { - double double_x = (double)x; - double double_y = (double)y; - - if (evenWidth){ - // if width is even, the reference point is actually shifted 1/2 a grid spacing down and to the left, - // from the true center of the circle. - // - // so when we calculate distance, we need to subtract .5 so that the distance reflects the distance from the center - // of the circle, instead of the distance from the reference. - // - // for example - a circle with width == 4. The reference point is the lower left corner of the center square. - // the point directly below that is at (0,-1), but it's actually (-.5, -1.5) from the center of the circle. - // - // another example - same circle. The point directly to the right of the reference point is (1,0), but it's - // actually (.5,-.5) from the center. + for(int y = minOffset; y <= maxOffset; y++) { + for(int x = minOffset; x <= maxOffset; x++) { + double double_x = (double)x; + double double_y = (double)y; + + if(evenWidth) { + // if width is even, the reference point is actually shifted 1/2 a grid spacing down and to the left, + // from the true center of the circle. + // + // so when we calculate distance, we need to subtract .5 so that the distance reflects the distance from the center + // of the circle, instead of the distance from the reference. + // + // for example - a circle with width == 4. The reference point is the lower left corner of the center square. + // the point directly below that is at (0,-1), but it's actually (-.5, -1.5) from the center of the circle. + // + // another example - same circle. The point directly to the right of the reference point is (1,0), but it's + // actually (.5,-.5) from the center. - double_x -= 0.5; - double_y -= 0.5; - } - double distance= sqrt((double_x * double_x) + (double_y * double_y)); - - if (distance <= radius) - { - _addOffset(x, y); - } - - } /* endfor - x */ - - } /* endfor - y */ - - _setEdgeOffsets(); - -} + double_x -= 0.5; + double_y -= 0.5; + } + double distance= sqrt((double_x * double_x) + (double_y * double_y)); + + if(distance <= radius) _addOffset(x, y); + } // end for x + } // end for y -/********************************************************************** - * Destructor - */ + _setEdgeOffsets(); -CircularTemplate::~CircularTemplate(void) -{ - // Do nothing } - -/********************************************************************** - * printOffsetList() - Print the offset list to the given stream. This - * is used for debugging. - */ +/////////////////////////////////////////////////////////////////////////////// -void CircularTemplate::printOffsetList(FILE *stream) -{ - fprintf(stream, "\n\n"); - fprintf(stream, "Circular template with width %d grid spaces:\n", - _width); - - GridTemplate::printOffsetList(stream); +CircularTemplate::~CircularTemplate(void) { + // Do nothing } +/////////////////////////////////////////////////////////////////////////////// + +void CircularTemplate::printOffsetList(FILE *stream) { + fprintf(stream, "\n\n"); + fprintf(stream, "Circular template:"); + fprintf(stream, " width = %d\n", _width); + fprintf(stream, " wrap_lon = %d\n", _wrapLon); + fprintf(stream, " grid points:\n"); + GridTemplate::printOffsetList(stream); +} -/********************************************************************** - * Private Member Functions * - **********************************************************************/ +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/CircularTemplate.h b/met/src/basic/vx_util/CircularTemplate.h index fc92c7db8b..835aa99ed5 100644 --- a/met/src/basic/vx_util/CircularTemplate.h +++ b/met/src/basic/vx_util/CircularTemplate.h @@ -1,53 +1,30 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 -// ** University Corporation for Atmospheric Research (UCAR) -// ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -/* RCS info - * $Author: dixon $ - * $Locker: $ - * $Date: 2016/03/03 19:21:31 $ - * $Id: CircularTemplate.hh,v 1.5 2016/03/03 19:21:31 dixon Exp $ - * $Revision: 1.5 $ - * $State: Exp $ - */ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ - -/************************************************************************ - * CircularTemplate.hh: class implementing a circular template to be - * applied on gridded data. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - ************************************************************************/ - -#ifndef CircularTemplate_HH -#define CircularTemplate_HH +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 +// ** University Corporation for Atmospheric Research (UCAR) +// ** National Center for Atmospheric Research (NCAR) +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: CircularTemplate.h +// +// Description: +// Class implementing a Circular template to be +// applied on gridded data. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// 001 09-07-21 Halley Gotway Add wrap_lon. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __CIRCULAR_TEMPLATE_H__ +#define __CIRCULAR_TEMPLATE_H__ + +/////////////////////////////////////////////////////////////////////////////// #include @@ -57,46 +34,39 @@ #include #include +/////////////////////////////////////////////////////////////////////////////// -class CircularTemplate : public GridTemplate -{ - public: +class CircularTemplate : public GridTemplate { - // Constructor + public: - CircularTemplate(int width = 0); - - // Destructor + CircularTemplate(int width, bool wrap_lon); + virtual ~CircularTemplate(void); - virtual ~CircularTemplate(void); + void printOffsetList(FILE *stream); - // Print the offset list to the given stream. This is used for debugging. + // Access methods - void printOffsetList(FILE *stream); - - // Access methods + int getWidth(void) const { + return _width; + } - int getWidth(void) const - { - return _width; - } - - - virtual const char* getClassName(void) const{ - return _className(); - } + virtual const char* getClassName(void) const { + return _className(); + } - private: + private: - int _width; - - // Return the class name for error messages. - static const char* _className(void) - { - return("CircleTemplate"); - } + int _width; + // Return the class name for error messages. + static const char* _className(void) { + return("CircleTemplate"); + } }; +/////////////////////////////////////////////////////////////////////////////// + +#endif // __CIRCULAR_TEMPLATE_H__ -#endif +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/GridOffset.cc b/met/src/basic/vx_util/GridOffset.cc index 4ae65fdf57..f21fd39636 100644 --- a/met/src/basic/vx_util/GridOffset.cc +++ b/met/src/basic/vx_util/GridOffset.cc @@ -1,48 +1,24 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) // ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -// RCS info -// $Author: dixon $ -// $Locker: $ -// $Date: 2016/03/03 18:19:27 $ -// $Id: GridOffset.cc,v 1.6 2016/03/03 18:19:27 dixon Exp $ -// $Revision: 1.6 $ -// $State: Exp $ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ -/********************************************************************* - * GridOffset.cc: class implementing grid points as described as offsets - * from an origin. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - *********************************************************************/ +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: GridOffset.cc +// +// Description: +// Class implementing grid index points as described as +// offsets from an origin. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// +/////////////////////////////////////////////////////////////////////////////// #include @@ -53,9 +29,7 @@ using namespace std; -/********************************************************************** - * Constructors - */ +/////////////////////////////////////////////////////////////////////////////// GridOffset::GridOffset(int cur_x_offset, int cur_y_offset) { @@ -63,6 +37,7 @@ GridOffset::GridOffset(int cur_x_offset, int cur_y_offset) this->y_offset = cur_y_offset; } +/////////////////////////////////////////////////////////////////////////////// GridOffset::GridOffset(const GridOffset& rhs) { @@ -70,6 +45,7 @@ GridOffset::GridOffset(const GridOffset& rhs) y_offset = rhs.y_offset; } +/////////////////////////////////////////////////////////////////////////////// GridOffset::GridOffset(const GridOffset* rhs) { @@ -77,16 +53,10 @@ GridOffset::GridOffset(const GridOffset* rhs) y_offset = rhs->y_offset; } - -/********************************************************************** - * Destructor - */ +/////////////////////////////////////////////////////////////////////////////// GridOffset::~GridOffset(void) { } - -/********************************************************************** - * Private Member Functions * - **********************************************************************/ +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/GridOffset.h b/met/src/basic/vx_util/GridOffset.h index 8e4826011b..dd04a36bcc 100644 --- a/met/src/basic/vx_util/GridOffset.h +++ b/met/src/basic/vx_util/GridOffset.h @@ -1,56 +1,34 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 -// ** University Corporation for Atmospheric Research (UCAR) -// ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -/* RCS info - * $Author: dixon $ - * $Locker: $ - * $Date: 2016/03/03 19:21:31 $ - * $Id: GridOffset.hh,v 1.5 2016/03/03 19:21:31 dixon Exp $ - * $Revision: 1.5 $ - * $State: Exp $ - */ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ - -/************************************************************************ - * GridOffset.hh: class implementing grid index points as described as - * offsets from an origin. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - ************************************************************************/ - -#ifndef GridOffset_HH -#define GridOffset_HH +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 +// ** University Corporation for Atmospheric Research (UCAR) +// ** National Center for Atmospheric Research (NCAR) +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: GridOffset.h +// +// Description: +// Class implementing grid index points as described as +// offsets from an origin. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __GRID_OFFSET_H__ +#define __GRID_OFFSET_H__ + +/////////////////////////////////////////////////////////////////////////////// #include +/////////////////////////////////////////////////////////////////////////////// + using namespace std; class GridOffset @@ -74,5 +52,8 @@ class GridOffset }; +/////////////////////////////////////////////////////////////////////////////// + +#endif // __GRID_OFFSET_H__ -#endif +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/GridPoint.cc b/met/src/basic/vx_util/GridPoint.cc index 82581f2aeb..bbba9ee87a 100644 --- a/met/src/basic/vx_util/GridPoint.cc +++ b/met/src/basic/vx_util/GridPoint.cc @@ -1,47 +1,23 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) // ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -// RCS info -// $Author: dixon $ -// $Locker: $ -// $Date: 2016/03/03 18:19:27 $ -// $Id: GridPoint.cc,v 1.7 2016/03/03 18:19:27 dixon Exp $ -// $Revision: 1.7 $ -// $State: Exp $ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ -/********************************************************************* - * GridPoint.cc: class implementing grid index points. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - *********************************************************************/ +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: GridPoint.cc +// +// Description: +// Class implementing grid index points. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// +/////////////////////////////////////////////////////////////////////////////// #include @@ -52,42 +28,39 @@ #include using namespace std; -/********************************************************************** - * Constructors - */ +/////////////////////////////////////////////////////////////////////////////// GridPoint::GridPoint(int cur_x, int cur_y) { setPoint(cur_x, cur_y); } +/////////////////////////////////////////////////////////////////////////////// GridPoint::GridPoint(GridPoint *point) { setPoint(point); } +/////////////////////////////////////////////////////////////////////////////// GridPoint::GridPoint(GridPoint *point, GridOffset *offset) { setPoint(point, offset); } - -/********************************************************************** - * Destructor - */ +/////////////////////////////////////////////////////////////////////////////// GridPoint::~GridPoint(void) { } - -/********************************************************************** - * rotate() - Rotate the point about the origin by the given angle. - * The angle value must be given in degrees. - */ - +/////////////////////////////////////////////////////////////////////////////// +// +// Rotate the point about the origin by the given angle. +// The angle value must be given in degrees. +// +/////////////////////////////////////////////////////////////////////////////// void GridPoint::rotate(const double angle) { @@ -103,7 +76,4 @@ void GridPoint::rotate(const double angle) y = (int)(new_y + 0.5); } - -/********************************************************************** - * Private Member Functions * - **********************************************************************/ +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/GridPoint.h b/met/src/basic/vx_util/GridPoint.h index 84876e37fe..9e66aceb0e 100644 --- a/met/src/basic/vx_util/GridPoint.h +++ b/met/src/basic/vx_util/GridPoint.h @@ -1,57 +1,35 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) // ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -/* RCS info - * $Author: dixon $ - * $Locker: $ - * $Date: 2016/03/03 19:21:31 $ - * $Id: GridPoint.hh,v 1.10 2016/03/03 19:21:31 dixon Exp $ - * $Revision: 1.10 $ - * $State: Exp $ - */ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ - -/************************************************************************ - * GridPoint.hh: class implementing grid index points. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - ************************************************************************/ - -#ifndef GridPoint_HH -#define GridPoint_HH +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: GridPoint.h +// +// Description: +// Class implementing grid index points. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __GRID_POINT_H__ +#define __GRID_POINT_H__ + +/////////////////////////////////////////////////////////////////////////////// #include #include "GridOffset.h" +/////////////////////////////////////////////////////////////////////////////// + class GridPoint { public: @@ -161,5 +139,8 @@ class GridPoint }; +/////////////////////////////////////////////////////////////////////////////// + +#endif // __GRID_POINT_H__ -#endif +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/GridTemplate.cc b/met/src/basic/vx_util/GridTemplate.cc index f3b83b3252..486ff28b44 100644 --- a/met/src/basic/vx_util/GridTemplate.cc +++ b/met/src/basic/vx_util/GridTemplate.cc @@ -1,48 +1,25 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) // ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -// RCS info -// $Author: dixon $ -// $Locker: $ -// $Date: 2016/03/03 18:19:27 $ -// $Id: GridTemplate.cc,v 1.14 2016/03/03 18:19:27 dixon Exp $ -// $Revision: 1.14 $ -// $State: Exp $ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ -/********************************************************************* - * GridTemplate.cc: class implementing a template to be applied to - * gridded data. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - *********************************************************************/ +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: GridTemplate.cc +// +// Description: +// Class implementing a template to be applied to +// gridded data. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// 001 09-07-21 Halley Gotway Add wrap_lon. +// +/////////////////////////////////////////////////////////////////////////////// #include #include @@ -51,6 +28,7 @@ #include #include "vx_log.h" +#include "nint.h" #include "GridTemplate.h" #include "GridOffset.h" @@ -60,18 +38,19 @@ using namespace std; -/********************************************************************** - * Constructors - */ +/////////////////////////////////////////////////////////////////////////////// -GridTemplate::GridTemplate(void) -{ - // Do nothing +GridTemplate::GridTemplate(void) : + _wrapLon(false) { + // Do nothing } -GridTemplate::GridTemplate(const GridTemplate& rhs) -{ - vector< GridOffset* >::const_iterator offset_iter; +/////////////////////////////////////////////////////////////////////////////// + +GridTemplate::GridTemplate(const GridTemplate& rhs) { + _wrapLon = rhs._wrapLon; + + vector::const_iterator offset_iter; for (offset_iter = rhs._offsetList.begin(); offset_iter != rhs._offsetList.end(); ++offset_iter) @@ -83,536 +62,558 @@ GridTemplate::GridTemplate(const GridTemplate& rhs) _pointInGridReturn = rhs._pointInGridReturn; } -/********************************************************************** - * Destructor - */ +/////////////////////////////////////////////////////////////////////////////// -GridTemplate::~GridTemplate(void) -{ - // Reclaim the space for the offset list +GridTemplate::~GridTemplate(void) { - vector< GridOffset* >::iterator list_iter; - for (list_iter = _offsetList.begin(); list_iter != _offsetList.end(); + // Reclaim the space for the offset list + vector::iterator list_iter; + for(list_iter = _offsetList.begin(); list_iter != _offsetList.end(); ++list_iter) - delete *list_iter; - - _offsetList.erase(_offsetList.begin(), _offsetList.end()); + delete *list_iter; + _offsetList.erase(_offsetList.begin(), _offsetList.end()); } -/********************************************************************** - * getFirstInGrid() - Get the first template grid point within the given - * grid. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Get the first template grid point within the given grid. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// GridPoint *GridTemplate::getFirstInGrid( - const int &base_x, const int &base_y, - const int &nx, const int &ny) const -{ - // Set the grid information + const int &base_x, const int &base_y, + const int &nx, const int &ny) const { - setGrid(base_x, base_y, nx, ny); + // Set the grid information, applying the global wrap logic + setGrid((_wrapLon ? base_x % nx : base_x), base_y, nx, ny); - // Send back the first point + // Send back the first point - return getNextInGrid(); + return getNextInGrid(); } -/********************************************************************** - * getNextInGrid() - Get the next template grid point within the grid. - * Returns NULL when there are no more points in the - * grid. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ - -GridPoint *GridTemplate::getNextInGrid(void) const -{ - while (_pointInGridIterator != _offsetList.end()) - { - GridOffset *offset = *_pointInGridIterator; - - _pointInGridIterator++; - - _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; - _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; - - if (_pointInGridReturn.x >= 0 && - _pointInGridReturn.x < _pointInGridNumX && - _pointInGridReturn.y >= 0 && - _pointInGridReturn.y < _pointInGridNumY) - { - return &_pointInGridReturn; - } - - } - - return (GridPoint *)NULL; +/////////////////////////////////////////////////////////////////////////////// +// +// Get the next template grid point within the grid. +// Returns NULL when there are no more points in the grid. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// + +GridPoint *GridTemplate::getNextInGrid(void) const { + + while (_pointInGridIterator != _offsetList.end()) { + GridOffset *offset = *_pointInGridIterator; + + _pointInGridIterator++; + + _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; + _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; + + // Apply global wrap logic + _pointInGridReturn.x = (_wrapLon ? + positive_modulo(_pointInGridReturn.x, _pointInGridNumX) : + _pointInGridReturn.x); + + if(_pointInGridReturn.x >= 0 && + _pointInGridReturn.x < _pointInGridNumX && + _pointInGridReturn.y >= 0 && + _pointInGridReturn.y < _pointInGridNumY) { + return &_pointInGridReturn; + } + } // end while + + return (GridPoint *)NULL; } -/********************************************************************** - * getFirst() - Get the first template grid point without checking - * the grid bounds. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Get the first template grid point without checking the grid +// bounds. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// GridPoint *GridTemplate::getFirst(const int &base_x, const int &base_y, - const int &nx, const int &ny) const -{ - // Set the grid information - setGrid(base_x, base_y, nx, ny); + const int &nx, const int &ny) const { + + // Set the grid information, applying the global wrap logic + setGrid((_wrapLon ? positive_modulo(base_x, nx) : base_x), base_y, nx, ny); - // Send back the first point - return getNext(); + // Send back the first point + return getNext(); } -/********************************************************************** - * getNext() - Get the next template grid point without checking the - * grid bounds. Returns NULL when there are no more points. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Get the next template grid point without checking the grid bounds. +// Returns NULL when there are no more points. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// -GridPoint *GridTemplate::getNext(void) const -{ - GridPoint *next_point = (GridPoint *)NULL; - if (_pointInGridIterator != _offsetList.end()) - { - GridOffset *offset = *_pointInGridIterator; +GridPoint *GridTemplate::getNext(void) const { - _pointInGridIterator++; + GridPoint *next_point = (GridPoint *)NULL; + if(_pointInGridIterator != _offsetList.end()) { + GridOffset *offset = *_pointInGridIterator; - _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; - _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; + _pointInGridIterator++; - next_point = &_pointInGridReturn; + _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; + _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; - } + // Apply global wrap logic + _pointInGridReturn.x = (_wrapLon ? + positive_modulo(_pointInGridReturn.x, _pointInGridNumX) : + _pointInGridReturn.x); + + next_point = &_pointInGridReturn; + } - return next_point; + return next_point; } -/********************************************************************** - * getFirstInLftEdge() - Get the first template grid point in the left - * column. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Get the first template grid point in the left column. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// -GridPoint *GridTemplate::getFirstInLftEdge(void) const -{ - // Reset the iterator and send back the first point +GridPoint *GridTemplate::getFirstInLftEdge(void) const { - _pointInLftEdgeIterator = _offsetLftEdge.begin(); + // Reset the iterator and send back the first point + _pointInLftEdgeIterator = _offsetLftEdge.begin(); - return getNextInLftEdge(); + return getNextInLftEdge(); } -/********************************************************************** - * getNextInLftEdge() - Get the next template grid point in the first - * column. Returns NULL when there are no more - * points in the first column. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ - -GridPoint *GridTemplate::getNextInLftEdge(void) const -{ - while (_pointInLftEdgeIterator != _offsetLftEdge.end()) - { - GridOffset *offset = *_pointInLftEdgeIterator; - - _pointInLftEdgeIterator++; - - _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; - _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; - - if (_pointInGridReturn.x >= 0 && - _pointInGridReturn.x < _pointInGridNumX && - _pointInGridReturn.y >= 0 && - _pointInGridReturn.y < _pointInGridNumY) - { - return &_pointInGridReturn; - } - - } - - return (GridPoint *)NULL; +/////////////////////////////////////////////////////////////////////////////// +// +// Get the next template grid point in the first column. +// Returns NULL when there are no more points in the first column. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// + +GridPoint *GridTemplate::getNextInLftEdge(void) const { + + while(_pointInLftEdgeIterator != _offsetLftEdge.end()) { + + GridOffset *offset = *_pointInLftEdgeIterator; + + _pointInLftEdgeIterator++; + + _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; + _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; + + // Apply global wrap logic + _pointInGridReturn.x = (_wrapLon ? + positive_modulo(_pointInGridReturn.x, _pointInGridNumX) : + _pointInGridReturn.x); + + if(_pointInGridReturn.x >= 0 && + _pointInGridReturn.x < _pointInGridNumX && + _pointInGridReturn.y >= 0 && + _pointInGridReturn.y < _pointInGridNumY) { + return &_pointInGridReturn; + } + } // end while + + return (GridPoint *)NULL; } -/********************************************************************** - * getFirstInTopEdge() - Get the first template grid point in the - * top row. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Get the first template grid point in the top row. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// -GridPoint *GridTemplate::getFirstInTopEdge(void) const -{ - // Reset the iterator and send back the first point +GridPoint *GridTemplate::getFirstInTopEdge(void) const { - _pointInTopEdgeIterator = _offsetTopEdge.begin(); + // Reset the iterator and send back the first point + _pointInTopEdgeIterator = _offsetTopEdge.begin(); - return getNextInTopEdge(); + return getNextInTopEdge(); } -/********************************************************************** - * getNextInTopEdge() - Get the next template grid point in the top - * row. Returns NULL when there are no more - * points in the top row. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ - -GridPoint *GridTemplate::getNextInTopEdge(void) const -{ - while (_pointInTopEdgeIterator != _offsetTopEdge.end()) - { - GridOffset *offset = *_pointInTopEdgeIterator; - - _pointInTopEdgeIterator++; - - _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; - _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; - - if (_pointInGridReturn.x >= 0 && - _pointInGridReturn.x < _pointInGridNumX && - _pointInGridReturn.y >= 0 && - _pointInGridReturn.y < _pointInGridNumY) - { - return &_pointInGridReturn; - } - - } - - return (GridPoint *)NULL; +/////////////////////////////////////////////////////////////////////////////// +// +// Get the next template grid point in the top row. +// Returns NULL when there are no more points in the top row. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// + +GridPoint *GridTemplate::getNextInTopEdge(void) const { + + while(_pointInTopEdgeIterator != _offsetTopEdge.end()) { + GridOffset *offset = *_pointInTopEdgeIterator; + + _pointInTopEdgeIterator++; + + _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; + _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; + + // Apply global wrap logic + _pointInGridReturn.x = (_wrapLon ? + positive_modulo(_pointInGridReturn.x, _pointInGridNumX) : + _pointInGridReturn.x); + + if(_pointInGridReturn.x >= 0 && + _pointInGridReturn.x < _pointInGridNumX && + _pointInGridReturn.y >= 0 && + _pointInGridReturn.y < _pointInGridNumY) { + return &_pointInGridReturn; + } + } // end while + + return (GridPoint *)NULL; } -/********************************************************************** - * getFirstInRgtEdge() - Get the first template grid point in the - * right column. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Get the first template grid point in the right column. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// -GridPoint *GridTemplate::getFirstInRgtEdge(void) const -{ - // Reset the iterator and send back the first point +GridPoint *GridTemplate::getFirstInRgtEdge(void) const { - _pointInRgtEdgeIterator = _offsetRgtEdge.begin(); + // Reset the iterator and send back the first point + _pointInRgtEdgeIterator = _offsetRgtEdge.begin(); - return getNextInRgtEdge(); + return getNextInRgtEdge(); } -/********************************************************************** - * getNextInRgtEdge() - Get the next template grid point in the right - * column. Returns NULL when there are no more - * points in the right column. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ - -GridPoint *GridTemplate::getNextInRgtEdge(void) const -{ - while (_pointInRgtEdgeIterator != _offsetRgtEdge.end()) - { - GridOffset *offset = *_pointInRgtEdgeIterator; - - _pointInRgtEdgeIterator++; - - _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; - _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; - - if (_pointInGridReturn.x >= 0 && - _pointInGridReturn.x < _pointInGridNumX && - _pointInGridReturn.y >= 0 && - _pointInGridReturn.y < _pointInGridNumY) - { - return &_pointInGridReturn; - } - - } - - return (GridPoint *)NULL; +/////////////////////////////////////////////////////////////////////////////// +// +// Get the next template grid point in the right column. +// Returns NULL when there are no more points in the right column. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// + +GridPoint *GridTemplate::getNextInRgtEdge(void) const { + + while(_pointInRgtEdgeIterator != _offsetRgtEdge.end()) { + GridOffset *offset = *_pointInRgtEdgeIterator; + + _pointInRgtEdgeIterator++; + + _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; + _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; + + // Apply global wrap logic + _pointInGridReturn.x = (_wrapLon ? + positive_modulo(_pointInGridReturn.x, _pointInGridNumX) : + _pointInGridReturn.x); + + if(_pointInGridReturn.x >= 0 && + _pointInGridReturn.x < _pointInGridNumX && + _pointInGridReturn.y >= 0 && + _pointInGridReturn.y < _pointInGridNumY) { + return &_pointInGridReturn; + } + } // end while + + return (GridPoint *)NULL; } -/********************************************************************** - * getFirstInBotEdge() - Get the first template grid point in the - * bottom row. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Get the first template grid point in the bottom row. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// -GridPoint *GridTemplate::getFirstInBotEdge(void) const -{ - // Reset the iterator and send back the first point +GridPoint *GridTemplate::getFirstInBotEdge(void) const { - _pointInBotEdgeIterator = _offsetBotEdge.begin(); + // Reset the iterator and send back the first point + _pointInBotEdgeIterator = _offsetBotEdge.begin(); - return getNextInBotEdge(); + return getNextInBotEdge(); } -/********************************************************************** - * getNextInBotEdge() - Get the next template grid point in the bottom - * row. Returns NULL when there are no more points - * in the bottom row. - * - * Returns a pointer to a static object which must NOT be deleted by the - * calling routine. - */ - -GridPoint *GridTemplate::getNextInBotEdge(void) const -{ - while (_pointInBotEdgeIterator != _offsetBotEdge.end()) - { - GridOffset *offset = *_pointInBotEdgeIterator; - - _pointInBotEdgeIterator++; - - _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; - _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; - - if (_pointInGridReturn.x >= 0 && - _pointInGridReturn.x < _pointInGridNumX && - _pointInGridReturn.y >= 0 && - _pointInGridReturn.y < _pointInGridNumY) - { - return &_pointInGridReturn; - } - - } - - return (GridPoint *)NULL; +/////////////////////////////////////////////////////////////////////////////// +// +// Get the next template grid point in the bottom row. +// Returns NULL when there are no more points in the bottom row. +// Initialize the grid dimensions and base location. +// +// Returns a pointer to a static object which must NOT be deleted +// by the calling routine. +// +/////////////////////////////////////////////////////////////////////////////// + +GridPoint *GridTemplate::getNextInBotEdge(void) const { + + while(_pointInBotEdgeIterator != _offsetBotEdge.end()) { + GridOffset *offset = *_pointInBotEdgeIterator; + + _pointInBotEdgeIterator++; + + _pointInGridReturn.x = _pointInGridBase.x + offset->x_offset; + _pointInGridReturn.y = _pointInGridBase.y + offset->y_offset; + + // Apply global wrap logic + _pointInGridReturn.x = (_wrapLon ? + positive_modulo(_pointInGridReturn.x, _pointInGridNumX) : + _pointInGridReturn.x); + + if(_pointInGridReturn.x >= 0 && + _pointInGridReturn.x < _pointInGridNumX && + _pointInGridReturn.y >= 0 && + _pointInGridReturn.y < _pointInGridNumY) { + return &_pointInGridReturn; + } + } // end while + + return (GridPoint *)NULL; } -/********************************************************************** - * setGrid() - Initialize the grid dimensions and base location. - * - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Initialize the grid dimensions and base location. +// +/////////////////////////////////////////////////////////////////////////////// void GridTemplate::setGrid(const int &base_x, const int &base_y, - const int &nx, const int &ny) const -{ - // Set up the iterators and save the grid information + const int &nx, const int &ny) const { - _pointInGridIterator = _offsetList.begin(); + // Set up the iterators and save the grid information + _pointInGridIterator = _offsetList.begin(); - _pointInLftEdgeIterator = _offsetLftEdge.begin(); - _pointInRgtEdgeIterator = _offsetRgtEdge.begin(); - _pointInTopEdgeIterator = _offsetTopEdge.begin(); - _pointInBotEdgeIterator = _offsetBotEdge.begin(); + _pointInLftEdgeIterator = _offsetLftEdge.begin(); + _pointInRgtEdgeIterator = _offsetRgtEdge.begin(); + _pointInTopEdgeIterator = _offsetTopEdge.begin(); + _pointInBotEdgeIterator = _offsetBotEdge.begin(); - _pointInGridBase.x = base_x; - _pointInGridBase.y = base_y; + _pointInGridBase.x = base_x; + _pointInGridBase.y = base_y; - _pointInGridNumX = nx; - _pointInGridNumY = ny; + _pointInGridNumX = nx; + _pointInGridNumY = ny; - return; + return; } -/********************************************************************** - * incBaseX() - Increment the base_x location and reset the offset - * iterators. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Increment the base_x location and reset the offset iterators. +// +/////////////////////////////////////////////////////////////////////////////// + +void GridTemplate::incBaseX(const int &x_inc) const { -void GridTemplate::incBaseX(const int &x_inc) const -{ + _pointInGridBase.x += x_inc; - _pointInGridBase.x += x_inc; + // Apply global wrap logic + _pointInGridBase.x = (_wrapLon ? + positive_modulo(_pointInGridBase.x, _pointInGridNumX) : + _pointInGridBase.x); - if (_pointInGridBase.x < 0 || + if(_pointInGridBase.x < 0 || _pointInGridBase.x >= _pointInGridNumX ) { - mlog << Error << "\nGridTemplate::incBaseX() -> " - << "x (" << _pointInGridBase.x << ") out of range (0, " - << _pointInGridNumX << ").\n\n"; - exit(1); - } + mlog << Error << "\nGridTemplate::incBaseX() -> " + << "x (" << _pointInGridBase.x << ") out of range (0, " + << _pointInGridNumX << ").\n\n"; + exit(1); + } - return; + return; } -/********************************************************************** - * incBaseY() - Increment the base_y location and reset the offset - * iterators. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Increment the base_y location and reset the offset iterators. +// +/////////////////////////////////////////////////////////////////////////////// -void GridTemplate::incBaseY(const int &y_inc) const -{ +void GridTemplate::incBaseY(const int &y_inc) const { - _pointInGridBase.y += y_inc; + _pointInGridBase.y += y_inc; - if (_pointInGridBase.y < 0 || + if(_pointInGridBase.y < 0 || _pointInGridBase.y >= _pointInGridNumY ) { - mlog << Error << "\nGridTemplate::incBaseY() -> " - << "y (" << _pointInGridBase.y << ") out of range (0, " - << _pointInGridNumY << ").\n\n"; - exit(1); - } + mlog << Error << "\nGridTemplate::incBaseY() -> " + << "y (" << _pointInGridBase.y << ") out of range (0, " + << _pointInGridNumY << ").\n\n"; + exit(1); + } - return; + return; } -/********************************************************************** - * printOffsetList() - Print the offset list to the given stream. This - * is used for debugging. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Print the offset list to the given stream. +// This is used for debugging. +// +/////////////////////////////////////////////////////////////////////////////// -void GridTemplate::printOffsetList(FILE *stream) -{ - vector< GridOffset* >::iterator ol_iterator; +void GridTemplate::printOffsetList(FILE *stream) { + vector< GridOffset* >::iterator ol_iterator; - for (ol_iterator = _offsetList.begin(); + for(ol_iterator = _offsetList.begin(); ol_iterator != _offsetList.end(); - ol_iterator++) - { - GridOffset *offset = *ol_iterator; - - double x = (double)offset->x_offset; - double y = (double)offset->y_offset; - - double distance = sqrt((x * x) + (y * y)); + ol_iterator++) { + GridOffset *offset = *ol_iterator; - fprintf(stream, " %4d %4d %f\n", - offset->x_offset, offset->y_offset, distance); + double x = (double)offset->x_offset; + double y = (double)offset->y_offset; - } + double distance = sqrt((x * x) + (y * y)); + fprintf(stream, " %4d %4d %f\n", + offset->x_offset, offset->y_offset, distance); + } } -/********************************************************************** - * Private Member Functions * - **********************************************************************/ - -/********************************************************************** - * _addOffset() - Add the given offset to the offset list. - */ +/////////////////////////////////////////////////////////////////////////////// +// +// Add the given offset to the offset list. +// +/////////////////////////////////////////////////////////////////////////////// -void GridTemplate::_addOffset(int x_offset, int y_offset) -{ - GridOffset *offset = new GridOffset(x_offset, y_offset); +void GridTemplate::_addOffset(int x_offset, int y_offset) { + GridOffset *offset = new GridOffset(x_offset, y_offset); - _offsetList.push_back(offset); + _offsetList.push_back(offset); - return; + return; } -/********************************************************************** - * _setEdgeOffsets() - Process the current offset list and find the - * edges. - */ - -void GridTemplate::_setEdgeOffsets() -{ - vector< GridOffset* >::iterator v_iterator; - map< int, GridOffset* >::iterator m_iterator; - map< int, GridOffset* > min_x_by_y; - map< int, GridOffset* > max_x_by_y; - map< int, GridOffset* > min_y_by_x; - map< int, GridOffset* > max_y_by_x; - int x, y; - - // Loop over the offsets. - // For each row, find the min/max col. - // For each col, find the min/max row. - - for (v_iterator = _offsetList.begin(); +/////////////////////////////////////////////////////////////////////////////// +// +// Process the current offset list and find the edges. +// +/////////////////////////////////////////////////////////////////////////////// + +void GridTemplate::_setEdgeOffsets() { + vector::iterator v_iterator; + map::iterator m_iterator; + map min_x_by_y; + map max_x_by_y; + map min_y_by_x; + map max_y_by_x; + int x, y; + + // Loop over the offsets. + // For each row, find the min/max col. + // For each col, find the min/max row. + + for(v_iterator = _offsetList.begin(); v_iterator != _offsetList.end(); - v_iterator++) - { - GridOffset *offset = *v_iterator; - x = offset->x_offset; - y = offset->y_offset; - - // Min x for each y - if(min_x_by_y.count(y) == 0) { min_x_by_y[y] = offset; } - else if(x < min_x_by_y[y]->x_offset) { min_x_by_y[y] = offset; } - - // Max x for each y - if(max_x_by_y.count(y) == 0) { max_x_by_y[y] = offset; } - else if(x > max_x_by_y[y]->x_offset) { max_x_by_y[y] = offset; } - - // Min y for each x - if(min_y_by_x.count(x) == 0) { min_y_by_x[x] = offset; } - else if(y < min_y_by_x[x]->y_offset) { min_y_by_x[x] = offset; } - - // Max y for each x - if(max_y_by_x.count(x) == 0) { max_y_by_x[x] = offset; } - else if(y > max_y_by_x[x]->y_offset) { max_y_by_x[x] = offset; } - - } + v_iterator++) { + GridOffset *offset = *v_iterator; + x = offset->x_offset; + y = offset->y_offset; + + // Min x for each y + if(min_x_by_y.count(y) == 0) { min_x_by_y[y] = offset; } + else if(x < min_x_by_y[y]->x_offset) { min_x_by_y[y] = offset; } + + // Max x for each y + if(max_x_by_y.count(y) == 0) { max_x_by_y[y] = offset; } + else if(x > max_x_by_y[y]->x_offset) { max_x_by_y[y] = offset; } + + // Min y for each x + if(min_y_by_x.count(x) == 0) { min_y_by_x[x] = offset; } + else if(y < min_y_by_x[x]->y_offset) { min_y_by_x[x] = offset; } + + // Max y for each x + if(max_y_by_x.count(x) == 0) { max_y_by_x[x] = offset; } + else if(y > max_y_by_x[x]->y_offset) { max_y_by_x[x] = offset; } + } - // Store min_x_by_y map as _offsetLftEdge vector - for (m_iterator = min_x_by_y.begin(); + // Store min_x_by_y map as _offsetLftEdge vector + for(m_iterator = min_x_by_y.begin(); m_iterator != min_x_by_y.end(); - m_iterator++) - { - _offsetLftEdge.push_back(m_iterator->second); - } + m_iterator++) { + _offsetLftEdge.push_back(m_iterator->second); + } - // Store max_x_by_y map as _offsetRgtEdge vector - for (m_iterator = max_x_by_y.begin(); + // Store max_x_by_y map as _offsetRgtEdge vector + for(m_iterator = max_x_by_y.begin(); m_iterator != max_x_by_y.end(); - m_iterator++) - { - _offsetRgtEdge.push_back(m_iterator->second); - } + m_iterator++) { + _offsetRgtEdge.push_back(m_iterator->second); + } - // Store max_y_by_x map as _offsetTopEdge vector - for (m_iterator = max_y_by_x.begin(); + // Store max_y_by_x map as _offsetTopEdge vector + for(m_iterator = max_y_by_x.begin(); m_iterator != max_y_by_x.end(); - m_iterator++) - { - _offsetTopEdge.push_back(m_iterator->second); - } + m_iterator++) { + _offsetTopEdge.push_back(m_iterator->second); + } - // Store min_y_by_x map as _offsetBotEdge vector - for (m_iterator = min_y_by_x.begin(); + // Store min_y_by_x map as _offsetBotEdge vector + for(m_iterator = min_y_by_x.begin(); m_iterator != min_y_by_x.end(); - m_iterator++) - { - _offsetBotEdge.push_back(m_iterator->second); - } + m_iterator++) { + _offsetBotEdge.push_back(m_iterator->second); + } - return; + return; } -//////////////////////////////////////////////////////////////// -// GridTemplateFactory -//////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Code for class GridTemplateFactory. +// +/////////////////////////////////////////////////////////////////////////////// + GridTemplateFactory::GridTemplateFactory() { enum_to_string.resize(GridTemplate_NUM_TEMPLATES); enum_to_string[GridTemplate_None] = ""; enum_to_string[GridTemplate_Square] = "SQUARE"; enum_to_string[GridTemplate_Circle] = "CIRCLE"; - //enum_to_string[GridTemplate_Rectangle] = "RECTANGLE"; } +/////////////////////////////////////////////////////////////////////////////// + GridTemplateFactory::~GridTemplateFactory() { enum_to_string.clear(); } -///////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// // log & exit on failure +// +/////////////////////////////////////////////////////////////////////////////// + GridTemplateFactory::GridTemplates GridTemplateFactory::string2Enum(string target) { - for (unsigned int ix = 0; ix < GridTemplate_NUM_TEMPLATES; ix++){ - if (enum_to_string[ix] == target){ + for(unsigned int ix = 0; ix < GridTemplate_NUM_TEMPLATES; ix++) { + if(enum_to_string[ix] == target) { return static_cast(ix); } } @@ -621,13 +622,15 @@ GridTemplateFactory::GridTemplates GridTemplateFactory::string2Enum(string targe exit(1); } - -///////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// // log & exit on failure -string GridTemplateFactory::enum2String(GridTemplates target) { +// +/////////////////////////////////////////////////////////////////////////////// - if( static_cast(target) > enum_to_string.size() - 1){ +string GridTemplateFactory::enum2String(GridTemplates target) { + if(static_cast(target) > enum_to_string.size() - 1) { mlog << Error << "\nGridTemplateFactory::enum2String() -> " << "target out of range " << target << " > " << (static_cast(enum_to_string.size()) - 1) @@ -637,26 +640,36 @@ string GridTemplateFactory::enum2String(GridTemplates target) { return enum_to_string[static_cast(target)]; } -///////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// // Caller assumes ownership of the returned pointer. -GridTemplate* GridTemplateFactory::buildGT(string gt, int width) { - return buildGT(string2Enum(gt), width); +// +/////////////////////////////////////////////////////////////////////////////// + +GridTemplate* GridTemplateFactory::buildGT(string gt, int width, bool wrap_lon) { + return buildGT(string2Enum(gt), width, wrap_lon); } -///////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// // Caller assumes ownership of the returned pointer. -GridTemplate* GridTemplateFactory::buildGT(GridTemplates gt, int width) { +// +/////////////////////////////////////////////////////////////////////////////// + +GridTemplate* GridTemplateFactory::buildGT(GridTemplates gt, int width, bool wrap_lon) { switch (gt) { case(GridTemplate_Square): - return new RectangularTemplate(width,width); + return new RectangularTemplate(width, width, wrap_lon); case(GridTemplate_Circle): - return new CircularTemplate(width); + return new CircularTemplate(width, wrap_lon); default: mlog << Error << "\nbuildGT() -> " - << "Unexpected gt value of " << gt << ".\n\n"; + << "Unexpected GridTemplates value (" << gt << ").\n\n"; exit(1); } } + +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/GridTemplate.h b/met/src/basic/vx_util/GridTemplate.h index 28cfffec62..fd356c5f36 100644 --- a/met/src/basic/vx_util/GridTemplate.h +++ b/met/src/basic/vx_util/GridTemplate.h @@ -1,53 +1,30 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) // ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -/* RCS info - * $Author: dixon $ - * $Locker: $ - * $Date: 2016/03/03 19:21:31 $ - * $Id: GridTemplate.hh,v 1.9 2016/03/03 19:21:31 dixon Exp $ - * $Revision: 1.9 $ - * $State: Exp $ - */ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ - -/************************************************************************ - * GridTemplate.hh: class implementing a template to be applied to - * gridded data. - * - * RAP, NCAR, Boulder CO - * - * January 1999 - * - * Nancy Rehak - * - ************************************************************************/ - -#ifndef GridTemplate_HH -#define GridTemplate_HH +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: GridTemplate.h +// +// Description: +// Class implementing a template to be applied to +// gridded data. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-99 Rehak Initial version. +// 001 09-07-21 Halley Gotway Add wrap_lon. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __GRID_TEMPLATE_H__ +#define __GRID_TEMPLATE_H__ + +/////////////////////////////////////////////////////////////////////////////// #include #include @@ -55,177 +32,154 @@ #include "GridOffset.h" #include "GridPoint.h" -using namespace std; - -class GridTemplate -{ - public: - - // Constructors - - GridTemplate(void); - GridTemplate(const GridTemplate& rhs); - - // Destructor - - virtual ~GridTemplate(void); +/////////////////////////////////////////////////////////////////////////////// - // Methods for iterating through the template within the grid centered - // on the given point. To use these methods, first call getFirstInGrid() - // to get the first point. Then call getNextInGrid() to get all remaining - // points until a (GridPoint *)NULL is returned. Any time getFirstInGrid() - // is called, the iteration will be cleared and will start over again. - // - // base_x and base_y give the coordinates of the point around which the - // template is to be applied. - // - // These routines return a point to a static object which must NOT be - // deleted by the calling routine. +using namespace std; - GridPoint *getFirstInGrid(const int &base_x, const int &base_y, - const int &nx, const int &ny) const; - GridPoint *getNextInGrid(void) const; +class GridTemplate { - GridPoint *getFirst(const int &base_x, const int &base_y, - const int &nx, const int &ny) const; - GridPoint *getNext(void) const; + public: - GridPoint *getFirstInLftEdge(void) const; - GridPoint *getNextInLftEdge(void) const; + GridTemplate(void); + GridTemplate(const GridTemplate& rhs); + virtual ~GridTemplate(void); - GridPoint *getFirstInRgtEdge(void) const; - GridPoint *getNextInRgtEdge(void) const; + // Methods for iterating through the template within the grid centered + // on the given point. To use these methods, first call getFirstInGrid() + // to get the first point. Then call getNextInGrid() to get all remaining + // points until a (GridPoint *)NULL is returned. Any time getFirstInGrid() + // is called, the iteration will be cleared and will start over again. + // + // base_x and base_y give the coordinates of the point around which the + // template is to be applied. + // + // These routines return a point to a static object which must NOT be + // deleted by the calling routine. - GridPoint *getFirstInTopEdge(void) const; - GridPoint *getNextInTopEdge(void) const; + GridPoint *getFirstInGrid(const int &base_x, const int &base_y, + const int &nx, const int &ny) const; + GridPoint *getNextInGrid(void) const; - GridPoint *getFirstInBotEdge(void) const; - GridPoint *getNextInBotEdge(void) const; + GridPoint *getFirst(const int &base_x, const int &base_y, + const int &nx, const int &ny) const; + GridPoint *getNext(void) const; - void setGrid(const int &base_x, const int &base_y, - const int &nx, const int &ny) const; + GridPoint *getFirstInLftEdge(void) const; + GridPoint *getNextInLftEdge(void) const; - void incBaseX(const int &x_inc) const; - void incBaseY(const int &y_inc) const; + GridPoint *getFirstInRgtEdge(void) const; + GridPoint *getNextInRgtEdge(void) const; - // Printing methods + GridPoint *getFirstInTopEdge(void) const; + GridPoint *getNextInTopEdge(void) const; - void printOffsetList(FILE *stream); + GridPoint *getFirstInBotEdge(void) const; + GridPoint *getNextInBotEdge(void) const; - // Access methods + void setGrid(const int &base_x, const int &base_y, + const int &nx, const int &ny) const; - inline void addOffset(const GridOffset &offset) - { - _offsetList.push_back(new GridOffset(offset.x_offset, - offset.y_offset)); - } + void incBaseX(const int &x_inc) const; + void incBaseY(const int &y_inc) const; - inline void addOffset(const int x_offset, const int y_offset) - { - _offsetList.push_back(new GridOffset(x_offset, y_offset)); - } + // Printing methods - int size(void) const - { - return _offsetList.size(); - } + void printOffsetList(FILE *stream); - int getNumPts(void) const - { - return _offsetList.size(); - } + // Access methods - virtual const char* getClassName(void) const = 0; + inline void addOffset(const GridOffset &offset) { + _offsetList.push_back(new GridOffset(offset.x_offset, + offset.y_offset)); + } - virtual int getWidth() const = 0; + inline void addOffset(const int x_offset, const int y_offset) { + _offsetList.push_back(new GridOffset(x_offset, y_offset)); + } - protected: + int size(void) const { + return _offsetList.size(); + } - // The offsets that make up the circle + int getNumPts(void) const { + return _offsetList.size(); + } - vector< GridOffset* > _offsetList; + int getWrapLon(void) const { + return _wrapLon; + } - // The offsets that define the first and last rows and columns + virtual const char* getClassName(void) const = 0; - vector< GridOffset* > _offsetLftEdge; // not allocated - vector< GridOffset* > _offsetRgtEdge; // not allocated - vector< GridOffset* > _offsetTopEdge; // not allocated - vector< GridOffset* > _offsetBotEdge; // not allocated + virtual int getWidth() const = 0; - // Iterator for finding points within a grid + protected: - mutable vector< GridOffset* >::const_iterator _pointInGridIterator; - mutable vector< GridOffset* >::const_iterator _pointInLftEdgeIterator; - mutable vector< GridOffset* >::const_iterator _pointInRgtEdgeIterator; - mutable vector< GridOffset* >::const_iterator _pointInTopEdgeIterator; - mutable vector< GridOffset* >::const_iterator _pointInBotEdgeIterator; + bool _wrapLon; - mutable GridPoint _pointInGridBase; - mutable int _pointInGridNumX; - mutable int _pointInGridNumY; - mutable GridPoint _pointInGridReturn; + // The offsets that make up the circle + vector _offsetList; - // Add the given offset to the offset list + // The offsets that define the first and last rows and columns + vector _offsetLftEdge; // not allocated + vector _offsetRgtEdge; // not allocated + vector _offsetTopEdge; // not allocated + vector _offsetBotEdge; // not allocated - void _addOffset(int x_offset, int y_offset); + // Iterator for finding points within a grid + mutable vector::const_iterator _pointInGridIterator; + mutable vector::const_iterator _pointInLftEdgeIterator; + mutable vector::const_iterator _pointInRgtEdgeIterator; + mutable vector::const_iterator _pointInTopEdgeIterator; + mutable vector::const_iterator _pointInBotEdgeIterator; - // Determine the offsets for the First/Last Row/Column + mutable GridPoint _pointInGridBase; + mutable int _pointInGridNumX; + mutable int _pointInGridNumY; + mutable GridPoint _pointInGridReturn; - void _setEdgeOffsets(); + // Add the given offset to the offset list + void _addOffset(int x_offset, int y_offset); + // Determine the offsets for the First/Last Row/Column + void _setEdgeOffsets(); }; +/////////////////////////////////////////////////////////////////////////////// + // The factory knows about child classes and can create them for you class GridTemplateFactory { - public: - - // constructor - GridTemplateFactory(); + public: - // destructor - ~GridTemplateFactory(); - // do not assign specific values to these enumes. - // other code requires them to start at zero and increase by 1 - // make sure GridTemplate_NUM_TEMPLATES is always last. - // TODO: rename this Shape - enum GridTemplates { - GridTemplate_None, - GridTemplate_Square, - GridTemplate_Circle, - //GridTemplate_Rectangle, - GridTemplate_NUM_TEMPLATES - }; + GridTemplateFactory(); + ~GridTemplateFactory(); - // String corresponding to the enumerated values above - vector enum_to_string; + // do not assign specific values to these enumes. + // other code requires them to start at zero and increase by 1 + // make sure GridTemplate_NUM_TEMPLATES is always last. + enum GridTemplates { + GridTemplate_None, + GridTemplate_Square, + GridTemplate_Circle, + GridTemplate_NUM_TEMPLATES + }; -///////////////////////////////////////////////////////////////// -// exit on failure - GridTemplates string2Enum(string target); + // String corresponding to the enumerated values above + vector enum_to_string; -////////////////////////////////////////////////////////////// -// exit on failure - string enum2String(GridTemplates gt); + GridTemplates string2Enum(string target); + string enum2String(GridTemplates gt); -///////////////////////////////////////////////////////////////// -// Caller assumes ownership of the returned pointer. - GridTemplate* buildGT(string gt, int width); + GridTemplate* buildGT(string gt, int width, bool wrap_lon); + GridTemplate* buildGT(GridTemplates gt, int width, bool wrap_lon); -///////////////////////////////////////////////////////////////// -// Caller assumes ownership of the returned pointer. - GridTemplate* buildGT(GridTemplates gt, int width); - - //FUTURE DEV NOTE: - // If we ever decide to support rectangles or elipses, we are going to - // need to modify these methods to take more than just a single width & shape - // to define the grid template. I suggest building a structure/union that holds - // all the defining characteristics of a grid template (shape, height, width, radius, etc.) - // and passing that around everywhere that we currently pass around a shape & width. +}; +/////////////////////////////////////////////////////////////////////////////// -}; +#endif // __GRID_TEMPLATE_H__ -#endif +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/RectangularTemplate.cc b/met/src/basic/vx_util/RectangularTemplate.cc index 59c06901a0..7368e2125c 100644 --- a/met/src/basic/vx_util/RectangularTemplate.cc +++ b/met/src/basic/vx_util/RectangularTemplate.cc @@ -1,48 +1,25 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) // ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -// RCS info -// $Author: dixon $ -// $Locker: $ -// $Date: 2016/03/03 18:19:27 $ -// $Id: RectangularTemplate.cc,v 1.4 2016/03/03 18:19:27 dixon Exp $ -// $Revision: 1.4 $ -// $State: Exp $ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ -/********************************************************************* - * RectangularTemplate.cc: class implementing a Rectangular template - * to be applied on gridded data. - * - * RAP, NCAR, Boulder CO - * - * January 2007 - * - * Dan Megenhardt - * - *********************************************************************/ +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: RectangularTemplate.cc +// +// Description: +// Class implementing a Rectangular template to be +// applied on gridded data. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-07 Megenhardt Initial version. +// 001 09-07-21 Halley Gotway Add wrap_lon. +// +/////////////////////////////////////////////////////////////////////////////// #include @@ -52,82 +29,63 @@ #include #include #include + using namespace std; -/********************************************************************** - * Constructor - */ - -RectangularTemplate::RectangularTemplate(int height, int width) : - GridTemplate(), - _height(height), - _width(width) -{ - - /* - CONVENTION: - If width is even, we push the center to the lower left corner - and have the "extra" in the right & above. - */ - - bool evenWidth = ((width % 2) == 0); - bool evenHeight = ((height % 2) == 0); - - // equivalent of w/2 for even & (w-1)/2 for odd - int halfwidth = width / 2; - int halfheight = height / 2; - - int xmin = halfwidth; - int ymin = halfheight; - - if (evenWidth){ - xmin--; - } - if (evenHeight){ - ymin--; - } - - // Create the offsets list - for (int y = -ymin; y <= halfheight; y++) - { - for (int x = -xmin; x <= halfheight; x++) - { - _addOffset(x, y); - } /* endfor x = 0 */ - } /* endfor y = 0*/ - - _setEdgeOffsets(); -} +/////////////////////////////////////////////////////////////////////////////// +RectangularTemplate::RectangularTemplate(int height, int width, bool wrap_lon) : + GridTemplate(), _height(height), _width(width) { -/********************************************************************** - * Destructor - */ + _wrapLon = wrap_lon; -RectangularTemplate::~RectangularTemplate(void) -{ - // Do nothing -} + // + // CONVENTION: + // If width is even, we push the center to the lower left corner + // and have the "extra" in the right & above. + // + bool evenWidth = ((width % 2) == 0); + bool evenHeight = ((height % 2) == 0); -/********************************************************************** - * printOffsetList() - Print the offset list to the given stream. This - * is used for debugging. - */ + // equivalent of w/2 for even & (w-1)/2 for odd + int halfwidth = width / 2; + int halfheight = height / 2; -void RectangularTemplate::printOffsetList(FILE *stream) -{ - fprintf(stream, "\n\n"); - fprintf(stream, "Rectangular template:"); - fprintf(stream, " height = %d\n", _height); - fprintf(stream, " width = %d\n", _width); + int xmin = halfwidth; + int ymin = halfheight; - fprintf(stream, " grid points:\n"); + if(evenWidth) xmin--; + if(evenHeight) ymin--; + + // Create the offsets list + for(int y = -ymin; y <= halfheight; y++) { + for(int x = -xmin; x <= halfwidth; x++) { + _addOffset(x, y); + } // end for x + } // end for y + + _setEdgeOffsets(); - GridTemplate::printOffsetList(stream); } +/////////////////////////////////////////////////////////////////////////////// + +RectangularTemplate::~RectangularTemplate(void) { + // Do nothing +} + +/////////////////////////////////////////////////////////////////////////////// + +void RectangularTemplate::printOffsetList(FILE *stream) { + fprintf(stream, "\n\n"); + fprintf(stream, "Rectangular template:"); + fprintf(stream, " height = %d\n", _height); + fprintf(stream, " width = %d\n", _width); + fprintf(stream, " wrap_lon = %d\n", _wrapLon); + fprintf(stream, " grid points:\n"); + + GridTemplate::printOffsetList(stream); +} -/********************************************************************** - * Private Member Functions * - **********************************************************************/ +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/RectangularTemplate.h b/met/src/basic/vx_util/RectangularTemplate.h index b45dcb6a77..507286adbc 100644 --- a/met/src/basic/vx_util/RectangularTemplate.h +++ b/met/src/basic/vx_util/RectangularTemplate.h @@ -1,57 +1,30 @@ -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -// ** Copyright UCAR (c) 1990 - 2021 +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) // ** National Center for Atmospheric Research (NCAR) -// ** Boulder, Colorado, USA -// ** BSD licence applies - redistribution and use in source and binary -// ** forms, with or without modification, are permitted provided that -// ** the following conditions are met: -// ** 1) If the software is modified to produce derivative works, -// ** such modified software should be clearly marked, so as not -// ** to confuse it with the version available from UCAR. -// ** 2) Redistributions of source code must retain the above copyright -// ** notice, this list of conditions and the following disclaimer. -// ** 3) Redistributions in binary form must reproduce the above copyright -// ** notice, this list of conditions and the following disclaimer in the -// ** documentation and/or other materials provided with the distribution. -// ** 4) Neither the name of UCAR nor the names of its contributors, -// ** if any, may be used to endorse or promote products derived from -// ** this software without specific prior written permission. -// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS -// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ - -/* RCS info - * $Author: dixon $ - * $Locker: $ - * $Date: 2016/03/03 19:21:31 $ - * $Id: RectangularTemplate.hh,v 1.3 2016/03/03 19:21:31 dixon Exp $ - * $Revision: 1.3 $ - * $State: Exp $ - */ - -/**-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**/ - -/************************************************************************ - * RectangularTemplate.hh: class implementing a Rectangular template to be - * applied on gridded data. - * - * RAP, NCAR, Boulder CO - * - * January 2007 - * - * Dan Megenhardt - * - ************************************************************************/ - -#ifndef RectangularTemplate_HH -#define RectangularTemplate_HH - -/* - **************************** includes ********************************** - */ +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: RectangularTemplate.h +// +// Description: +// Class implementing a Rectangular template to be +// applied on gridded data. +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 01-01-07 Megenhardt Initial version. +// 001 09-07-21 Halley Gotway Add wrap_lon. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __RECTANGULAR_TEMPLATE_H__ +#define __RECTANGULAR_TEMPLATE_H__ + +/////////////////////////////////////////////////////////////////////////////// #include @@ -61,71 +34,44 @@ #include #include -/* - ******************************* defines ******************************** - */ +/////////////////////////////////////////////////////////////////////////////// -/* - ******************************* structures ***************************** - */ +class RectangularTemplate : public GridTemplate { -/* - ************************* global variables ***************************** - */ + public: -/* - ***************************** function prototypes ********************** - */ + RectangularTemplate(int height, int width, bool wrap_lon); + virtual ~RectangularTemplate(void); -/* - ************************* class definitions **************************** - */ + void printOffsetList(FILE *stream); -class RectangularTemplate : public GridTemplate -{ - public: + // Access methods - // Constructor - RectangularTemplate(int height, int width); + int getHeight(void) const { + return _height; + } - // Destructor + int getWidth(void) const { + return _width; + } - virtual ~RectangularTemplate(void); + const char* getClassName(void) const { + return RectangularTemplate::_className(); + } - // Print the offset list to the given stream. This is used for debugging. + private: - void printOffsetList(FILE *stream); - - // Access methods - - int getHeight(void) const - { - return _height; - } - - int getWidth(void) const - { - return _width; - } - - const char* getClassName(void) const{ - return RectangularTemplate::_className(); - } - - private: - - // The box dimensions - - int _height; - int _width; - - // Return the class name for error messages. - static const char* _className(void) - { - return("RectangularTemplate"); - } + int _height; + int _width; + // Return the class name for error messages. + static const char* _className(void) { + return("RectangularTemplate"); + } }; +/////////////////////////////////////////////////////////////////////////////// + +#endif // __RECTANGULAR_TEMPLATE_H__ -#endif +/////////////////////////////////////////////////////////////////////////////// diff --git a/met/src/basic/vx_util/data_plane_util.cc b/met/src/basic/vx_util/data_plane_util.cc index 0004abe0f8..38b5469c5a 100644 --- a/met/src/basic/vx_util/data_plane_util.cc +++ b/met/src/basic/vx_util/data_plane_util.cc @@ -95,7 +95,7 @@ void rescale_probability(DataPlane &dp) { void smooth_field(const DataPlane &dp, DataPlane &smooth_dp, InterpMthd mthd, int width, const GridTemplateFactory::GridTemplates shape, - double t, const GaussianInfo &gaussian) { + bool wrap_lon, double t, const GaussianInfo &gaussian) { double v = 0.0; int x, y; @@ -107,10 +107,11 @@ void smooth_field(const DataPlane &dp, DataPlane &smooth_dp, // build the grid template GridTemplateFactory gtf; - GridTemplate* gt = gtf.buildGT(shape, width); + GridTemplate* gt = gtf.buildGT(shape, width, wrap_lon); mlog << Debug(3) - << "Smoothing field using the " << interpmthd_to_string(mthd) + << "Smoothing " << (wrap_lon ? "global" : "non-global") + << " field using the " << interpmthd_to_string(mthd) << "(" << gt->size() << ") " << gt->getClassName() << " interpolation method.\n"; @@ -185,10 +186,10 @@ void smooth_field(const DataPlane &dp, DataPlane &smooth_dp, DataPlane smooth_field(const DataPlane &dp, InterpMthd mthd, int width, const GridTemplateFactory::GridTemplates shape, - double t, const GaussianInfo &gaussian) { + bool wrap_lon, double t, const GaussianInfo &gaussian) { DataPlane smooth_dp; - smooth_field(dp, smooth_dp, mthd, width, shape, t, gaussian); + smooth_field(dp, smooth_dp, mthd, width, shape, wrap_lon, t, gaussian); return(smooth_dp); } @@ -202,7 +203,7 @@ DataPlane smooth_field(const DataPlane &dp, void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, int width, const GridTemplateFactory::GridTemplates shape, - SingleThresh t, double vld_t) { + bool wrap_lon, SingleThresh t, double vld_t) { GridPoint *gp = NULL; int x, y; int n_vld = 0; @@ -218,7 +219,7 @@ void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, // Build the grid template GridTemplateFactory gtf; - GridTemplate* gt = gtf.buildGT(shape, width); + GridTemplate* gt = gtf.buildGT(shape, width, wrap_lon); mlog << Debug(3) << "Computing fractional coverage field using the " @@ -291,161 +292,6 @@ void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, return; } -//////////////////////////////////////////////////////////////////////// -// -// Convert the DataPlane field to the corresponding fractional coverage -// using the threshold critea specified. -// -//////////////////////////////////////////////////////////////////////// - -void fractional_coverage_square(const DataPlane &dp, DataPlane &frac_dp, - int width, SingleThresh t, double vld_t) { - int i, j, k, n, x, y, x_ll, y_ll, y_ur, xx, yy, half_width; - double v; - int count_vld = 0; - int count_thr = 0; - NumArray box_na; - - mlog << Debug(3) - << "Computing fractional coverage field using the " - << t.get_str() << " threshold and the " - << interpmthd_to_string(InterpMthd_Nbrhd) - << "(" << width*width << ") interpolation method.\n"; - - // Check that width is set to 1 or greater - if(width < 1) { - mlog << Error << "\nfractional_coverage_square() -> " - << "width must be set to a value of 1 or greater.\n\n"; - exit(1); - } - - // Initialize the fractional coverage field - frac_dp = dp; - frac_dp.set_constant(bad_data_double); - - // Compute the box half-width - half_width = (width - 1)/2; - - // Initialize the box - for(i=0; i= dp.nx() || - yy < 0 || yy >= dp.ny()) { - k = bad_data_int; - } - // Check v to see if it meets the threshold criteria - else { - v = dp.get(xx, yy); - if(is_bad_data(v)) k = bad_data_int; - else if(t.check(v)) k = 1; - else k = 0; - } - box_na.set(n, k); - - // Increment the counts - if(!is_bad_data(k)) { - count_vld += 1; - count_thr += k; - } - - } // end for j - } // end for i - } // end if - - // Otherwise, update one row of the box - else { - - // Compute the row of the neighborhood box to be updated - j = (y - 1) % width; - - for(i=0; i= dp.nx() || - yy < 0 || yy >= dp.ny()) { - k = bad_data_int; - } - // Check v to see if it meets the threshold criteria - else { - v = dp.get(xx, yy); - if(is_bad_data(v)) k = bad_data_int; - else if(t.check(v)) k = 1; - else k = 0; - } - box_na.set(n, k); - - // Increment the counts - if(!is_bad_data(k)) { - count_vld += 1; - count_thr += k; - } - - } // end for i - } // end else - - // Check whether enough valid grid points were found - if((double) count_vld/(width*width) < vld_t || - count_vld == 0) { - v = bad_data_double; - } - // Compute the fractional coverage - else { - v = (double) count_thr/count_vld; - } - - // Store the fractional coverage value - frac_dp.set(v, x, y); - - } // end for y - } // end for x - - return; - -} - //////////////////////////////////////////////////////////////////////// // // Select points inside the mask and write them to a NumArray. diff --git a/met/src/basic/vx_util/data_plane_util.h b/met/src/basic/vx_util/data_plane_util.h index 401a22c2f7..2531cb80f1 100644 --- a/met/src/basic/vx_util/data_plane_util.h +++ b/met/src/basic/vx_util/data_plane_util.h @@ -43,19 +43,16 @@ extern void rescale_probability(DataPlane &); extern void smooth_field(const DataPlane &dp, DataPlane &smooth_dp, InterpMthd mthd, int width, const GridTemplateFactory::GridTemplates shape, - double t, const GaussianInfo &gaussian); + bool wrap_lon, double t, const GaussianInfo &gaussian); extern DataPlane smooth_field(const DataPlane &dp, InterpMthd mthd, int width, const GridTemplateFactory::GridTemplates shape, - double t, const GaussianInfo &gaussian); + bool wrap_lon, double t, const GaussianInfo &gaussian); extern void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, int width, const GridTemplateFactory::GridTemplates shape, - SingleThresh t, double vld_t); - -extern void fractional_coverage_square(const DataPlane &dp, DataPlane &frac_dp, - int width, SingleThresh t, double vld_t); + bool wrap_lon, SingleThresh t, double vld_t); extern void apply_mask(const DataPlane &, const MaskPlane &, NumArray &); extern void apply_mask(DataPlane &, const MaskPlane &); diff --git a/met/src/basic/vx_util/interp_util.cc b/met/src/basic/vx_util/interp_util.cc index 1f1de88bea..ac37311a9e 100644 --- a/met/src/basic/vx_util/interp_util.cc +++ b/met/src/basic/vx_util/interp_util.cc @@ -18,7 +18,6 @@ using namespace std; #include #include -//#include "interp_mthd.h" #include "interp_util.h" #include "GridTemplate.h" #include "RectangularTemplate.h" @@ -739,26 +738,30 @@ double interp_nbrhd(const DataPlane &dp, const GridTemplate >, int x, int y, // //////////////////////////////////////////////////////////////////////// -double interp_bilin(const DataPlane &dp, double obs_x, double obs_y, - const MaskPlane *mp) { - int x, y; +double interp_bilin(const DataPlane &dp, bool wrap_lon, + double obs_x, double obs_y, const MaskPlane *mp) { + int x, xp1, y; double bilin_v, dx, dy; double wtsw, wtse, wtnw, wtne; x = nint(floor(obs_x)); y = nint(floor(obs_y)); + // Apply global wrap logic to x and x+1 + x = (wrap_lon ? positive_modulo(x, dp.nx()) : x); + xp1 = (wrap_lon ? positive_modulo(x+1, dp.nx()) : x+1); + // Check the optional mask if(mp) { if(!(*mp)(x, y ) || - !(*mp)(x+1, y ) || + !(*mp)(xp1, y ) || !(*mp)(x, y+1) || - !(*mp)(x+1, y+1)) return(bad_data_double); + !(*mp)(xp1, y+1)) return(bad_data_double); } // Compute dx and dy - dx = obs_x - x; - dy = obs_y - y; + dx = obs_x - nint(floor(obs_x)); + dy = obs_y - nint(floor(obs_y)); // Compute weights for 4 corner points wtsw = (1.0-dx) * (1.0-dy); @@ -767,7 +770,7 @@ double interp_bilin(const DataPlane &dp, double obs_x, double obs_y, wtne = dx * dy; // On the grid boundary, check each corner point - if(x < 0 || x+1 >= dp.nx() || y < 0 || y+1 >= dp.ny()) { + if(x < 0 || xp1 >= dp.nx() || y < 0 || y+1 >= dp.ny()) { // Initialize weighted sum bilin_v = 0.0; @@ -784,12 +787,12 @@ double interp_bilin(const DataPlane &dp, double obs_x, double obs_y, // Southeast Corner if(!is_bad_data(bilin_v) && !is_eq(wtse, 0.0)) { - if(x+1 < 0 || x+1 >= dp.nx() || y < 0 || y >= dp.ny()) + if(xp1 < 0 || xp1 >= dp.nx() || y < 0 || y >= dp.ny()) bilin_v = bad_data_double; - else if(is_bad_data(dp.get(x+1, y))) + else if(is_bad_data(dp.get(xp1, y))) bilin_v = bad_data_double; else - bilin_v += wtse * dp.get(x+1, y); + bilin_v += wtse * dp.get(xp1, y); } // Northwest Corner @@ -804,27 +807,27 @@ double interp_bilin(const DataPlane &dp, double obs_x, double obs_y, // Northeast Corner if(!is_bad_data(bilin_v) && !is_eq(wtne, 0.0)) { - if(x+1 < 0 || x+1 >= dp.nx() || y+1 < 0 || y+1 >= dp.ny()) + if(xp1 < 0 || xp1 >= dp.nx() || y+1 < 0 || y+1 >= dp.ny()) bilin_v = bad_data_double; - else if(is_bad_data(dp.get(x+1, y+1))) + else if(is_bad_data(dp.get(xp1, y+1))) bilin_v = bad_data_double; else - bilin_v += wtne * dp.get(x+1, y+1); + bilin_v += wtne * dp.get(xp1, y+1); } } // On the grid interior, compute weighted average else { if(is_bad_data(dp.get(x, y )) || - is_bad_data(dp.get(x+1, y )) || + is_bad_data(dp.get(xp1, y )) || is_bad_data(dp.get(x, y+1)) || - is_bad_data(dp.get(x+1, y+1))) { + is_bad_data(dp.get(xp1, y+1))) { bilin_v = bad_data_double; } else { bilin_v = wtsw * dp.get(x, y) + - wtse * dp.get(x+1, y) + + wtse * dp.get(xp1, y) + wtnw * dp.get(x, y+1) + - wtne * dp.get(x+1, y+1); + wtne * dp.get(xp1, y+1); } } @@ -837,10 +840,13 @@ double interp_bilin(const DataPlane &dp, double obs_x, double obs_y, // //////////////////////////////////////////////////////////////////////// -double interp_xy(const DataPlane &dp, int x, int y, +double interp_xy(const DataPlane &dp, bool wrap_lon, int x, int y, const MaskPlane *mp) { double v; + // Apply global wrap logic + x = (wrap_lon ? positive_modulo(x, dp.nx()) : x); + // Check the optional mask if(mp) { if(!(*mp)(x, y)) return(bad_data_double); @@ -932,8 +938,8 @@ double compute_sfc_interp(const DataPlane &dp, double obs_elv, double obs_v, const InterpMthd mthd, const int width, const GridTemplateFactory::GridTemplates shape, - double interp_thresh, const SurfaceInfo &sfc_info, - bool is_land_obs) { + bool wrap_lon, double interp_thresh, + const SurfaceInfo &sfc_info, bool is_land_obs) { double v = bad_data_double; int x = nint(obs_x); int y = nint(obs_y); @@ -945,7 +951,7 @@ double compute_sfc_interp(const DataPlane &dp, } GridTemplateFactory gtf; - const GridTemplate* gt = gtf.buildGT(shape,width); + const GridTemplate* gt = gtf.buildGT(shape, width, wrap_lon); MaskPlane sfc_mask = compute_sfc_mask(*gt, x, y, sfc_info, is_land_obs, obs_elv); @@ -979,11 +985,11 @@ double compute_sfc_interp(const DataPlane &dp, break; case(InterpMthd_Bilin): // Bilinear interpolation - v = interp_bilin(dp, obs_x, obs_y, &sfc_mask); + v = interp_bilin(dp, wrap_lon, obs_x, obs_y, &sfc_mask); break; case(InterpMthd_Nearest): // Nearest Neighbor - v = interp_xy(dp, x, y, &sfc_mask); + v = interp_xy(dp, wrap_lon, x, y, &sfc_mask); break; case(InterpMthd_Best): // Best Match @@ -991,19 +997,19 @@ double compute_sfc_interp(const DataPlane &dp, break; case(InterpMthd_Upper_Left): // Upper Left corner of the grid box - v = interp_xy(dp, floor(obs_x), ceil(obs_y), &sfc_mask); + v = interp_xy(dp, wrap_lon, floor(obs_x), ceil(obs_y), &sfc_mask); break; case(InterpMthd_Upper_Right): // Upper Right corner of the grid box - v = interp_xy(dp, ceil(obs_x), ceil(obs_y), &sfc_mask); + v = interp_xy(dp, wrap_lon, ceil(obs_x), ceil(obs_y), &sfc_mask); break; case(InterpMthd_Lower_Right): // Lower Right corner of the grid box - v = interp_xy(dp, ceil(obs_x), floor(obs_y), &sfc_mask); + v = interp_xy(dp, wrap_lon, ceil(obs_x), floor(obs_y), &sfc_mask); break; case(InterpMthd_Lower_Left): // Lower Left corner of the grid box - v = interp_xy(dp, floor(obs_x), floor(obs_y), &sfc_mask); + v = interp_xy(dp, wrap_lon, floor(obs_x), floor(obs_y), &sfc_mask); break; case(InterpMthd_Geog_Match): // Geography Match for surface point verification @@ -1088,9 +1094,11 @@ double compute_horz_interp(const DataPlane &dp, double obs_x, double obs_y, double obs_v, const InterpMthd mthd, const int width, const GridTemplateFactory::GridTemplates shape, - double interp_thresh, const SingleThresh *cat_thresh) { + bool wrap_lon, double interp_thresh, + const SingleThresh *cat_thresh) { return(compute_horz_interp(dp, obs_x, obs_y, obs_v, bad_data_double, - bad_data_double, mthd, width, shape, interp_thresh, cat_thresh)); + bad_data_double, mthd, width, shape, wrap_lon, + interp_thresh, cat_thresh)); } //////////////////////////////////////////////////////////////////////// @@ -1100,19 +1108,20 @@ double compute_horz_interp(const DataPlane &dp, double obs_v, double cmn, double csd, const InterpMthd mthd, const int width, const GridTemplateFactory::GridTemplates shape, - double interp_thresh, const SingleThresh *cat_thresh) { + bool wrap_lon, double interp_thresh, + const SingleThresh *cat_thresh) { double v = bad_data_double; int x = nint(obs_x); int y = nint(obs_y); // if width is even, push center to lower left point instead of nearest - if((width % 2 ) == 0) { + if((width % 2) == 0) { x = static_cast(floor(obs_x)); y = static_cast(floor(obs_y)); } GridTemplateFactory gtf; - const GridTemplate* gt = gtf.buildGT(shape,width); + const GridTemplate* gt = gtf.buildGT(shape, width, wrap_lon); // Compute the interpolated value for the fields above and below switch(mthd) { @@ -1149,11 +1158,11 @@ double compute_horz_interp(const DataPlane &dp, break; case(InterpMthd_Bilin): // Bilinear interpolation - v = interp_bilin(dp, obs_x, obs_y); + v = interp_bilin(dp, wrap_lon, obs_x, obs_y); break; case(InterpMthd_Nearest): // Nearest Neighbor - v = interp_xy(dp, x, y); + v = interp_xy(dp, wrap_lon, x, y); break; case(InterpMthd_Best): // Best Match @@ -1161,19 +1170,19 @@ double compute_horz_interp(const DataPlane &dp, break; case(InterpMthd_Upper_Left): // Upper Left corner of the grid box - v = interp_xy(dp, floor(obs_x), ceil(obs_y)); + v = interp_xy(dp, wrap_lon, floor(obs_x), ceil(obs_y)); break; case(InterpMthd_Upper_Right): // Upper Right corner of the grid box - v = interp_xy(dp, ceil(obs_x), ceil(obs_y)); + v = interp_xy(dp, wrap_lon, ceil(obs_x), ceil(obs_y)); break; case(InterpMthd_Lower_Right): // Lower Right corner of the grid box - v = interp_xy(dp, ceil(obs_x), floor(obs_y)); + v = interp_xy(dp, wrap_lon, ceil(obs_x), floor(obs_y)); break; case(InterpMthd_Lower_Left): // Lower Left corner of the grid box - v = interp_xy(dp, floor(obs_x), floor(obs_y)); + v = interp_xy(dp, wrap_lon, floor(obs_x), floor(obs_y)); break; case(InterpMthd_Geog_Match): // Geography Match for surface point verification diff --git a/met/src/basic/vx_util/interp_util.h b/met/src/basic/vx_util/interp_util.h index 4369557be4..c8e6dc3f21 100644 --- a/met/src/basic/vx_util/interp_util.h +++ b/met/src/basic/vx_util/interp_util.h @@ -17,6 +17,7 @@ // Mod# Date Name Description // ---- ---- ---- ----------- // 000 11-17-08 Halley Gotway +// 001 09-07-21 Halley Gotway Add wrap_lon. // /////////////////////////////////////////////////////////////////////////////// @@ -84,8 +85,8 @@ extern double interp_geog_match(const DataPlane &, const GridTemplate >, dou extern double interp_nbrhd (const DataPlane &, const GridTemplate >, int x, int y, double t, const SingleThresh *, double cmn, double csd, const MaskPlane *mp = 0); -extern double interp_bilin (const DataPlane &, double obs_x, double obs_y, const MaskPlane *mp = 0); -extern double interp_xy (const DataPlane &, int x, int y, const MaskPlane *mp = 0); +extern double interp_bilin (const DataPlane &, bool wrap_lon, double obs_x, double obs_y, const MaskPlane *mp = 0); +extern double interp_xy (const DataPlane &, bool wrap_lon, int x, int y, const MaskPlane *mp = 0); extern double interp_best (const DataPlane &dp, const GridTemplate >, int x, int y, double obs_v, double t, const MaskPlane *mp = 0); @@ -102,8 +103,8 @@ extern double compute_sfc_interp(const DataPlane &dp, double obs_elv, double obs_v, const InterpMthd mthd, const int width, const GridTemplateFactory::GridTemplates shape, - double interp_thresh, const SurfaceInfo &sfc_info, - bool is_land_obs); + bool wrap_lon, double interp_thresh, + const SurfaceInfo &sfc_info, bool is_land_obs); extern MaskPlane compute_sfc_mask(const GridTemplate >, int x, int y, const SurfaceInfo &sfc_info, @@ -113,14 +114,16 @@ extern double compute_horz_interp(const DataPlane &dp, double obs_x, double obs_y, double obs_v, const InterpMthd mthd, const int width, const GridTemplateFactory::GridTemplates shape, - double interp_thresh, const SingleThresh *cat_thresh = 0); + bool wrap_lon, double interp_thresh, + const SingleThresh *cat_thresh = 0); extern double compute_horz_interp(const DataPlane &dp, double obs_x, double obs_y, double obs_v, double cmn, double csd, const InterpMthd mthd, const int width, const GridTemplateFactory::GridTemplates shape, - double interp_thresh, const SingleThresh *cat_thresh = 0); + bool wrap_lon, double interp_thresh, + const SingleThresh *cat_thresh = 0); extern double compute_vert_pinterp(double, double, double, double, double); extern double compute_vert_zinterp(double, double, double, double, double); diff --git a/met/src/libcode/vx_grid/gaussian_grid.cc b/met/src/libcode/vx_grid/gaussian_grid.cc index 6a175077a4..61edbbb7f8 100644 --- a/met/src/libcode/vx_grid/gaussian_grid.cc +++ b/met/src/libcode/vx_grid/gaussian_grid.cc @@ -431,7 +431,7 @@ return ( 0.0 ); //////////////////////////////////////////////////////////////////////// -bool GaussianGrid::is_global() const +bool GaussianGrid::wrap_lon() const { diff --git a/met/src/libcode/vx_grid/gaussian_grid.h b/met/src/libcode/vx_grid/gaussian_grid.h index 404b546097..1825f853e0 100644 --- a/met/src/libcode/vx_grid/gaussian_grid.h +++ b/met/src/libcode/vx_grid/gaussian_grid.h @@ -81,7 +81,7 @@ class GaussianGrid : public GridRep { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); diff --git a/met/src/libcode/vx_grid/goes_grid.cc b/met/src/libcode/vx_grid/goes_grid.cc index f7cc8e96fa..71e6ff61d5 100644 --- a/met/src/libcode/vx_grid/goes_grid.cc +++ b/met/src/libcode/vx_grid/goes_grid.cc @@ -376,7 +376,7 @@ return ( 0.0 ); //////////////////////////////////////////////////////////////////////// -bool GoesImagerGrid::is_global() const +bool GoesImagerGrid::wrap_lon() const { diff --git a/met/src/libcode/vx_grid/goes_grid.h b/met/src/libcode/vx_grid/goes_grid.h index a43869b1f7..291428fa33 100644 --- a/met/src/libcode/vx_grid/goes_grid.h +++ b/met/src/libcode/vx_grid/goes_grid.h @@ -72,7 +72,7 @@ class GoesImagerGrid : public GridRep { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); diff --git a/met/src/libcode/vx_grid/grid_base.cc b/met/src/libcode/vx_grid/grid_base.cc index 7b1fa99790..ece0332ce1 100644 --- a/met/src/libcode/vx_grid/grid_base.cc +++ b/met/src/libcode/vx_grid/grid_base.cc @@ -957,19 +957,19 @@ return ( rep->rot_grid_to_earth(x, y) ); //////////////////////////////////////////////////////////////////////// -bool Grid::is_global() const +bool Grid::wrap_lon() const { if ( !rep ) { - mlog << Error << "\nGrid::is_global() const -> empty grid!\n\n"; + mlog << Error << "\nGrid::wrap_lon() const -> empty grid!\n\n"; exit ( 1 ); } -return ( rep->is_global() ); +return ( rep->wrap_lon() ); } diff --git a/met/src/libcode/vx_grid/grid_base.h b/met/src/libcode/vx_grid/grid_base.h index c2894f0df1..5a225f6275 100644 --- a/met/src/libcode/vx_grid/grid_base.h +++ b/met/src/libcode/vx_grid/grid_base.h @@ -136,7 +136,7 @@ class GridInterface { // pure abstract class for grid public interface virtual double rot_grid_to_earth(int x, int y) const = 0; - virtual bool is_global() const = 0; + virtual bool wrap_lon() const = 0; virtual void shift_right(int) = 0; @@ -168,7 +168,7 @@ class GridRep : public GridInterface { virtual double rot_grid_to_earth(int x, int y) const = 0; - virtual bool is_global() const = 0; + virtual bool wrap_lon() const = 0; virtual void shift_right(int) = 0; @@ -246,7 +246,7 @@ class Grid : public GridInterface { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); diff --git a/met/src/libcode/vx_grid/latlon_grid.cc b/met/src/libcode/vx_grid/latlon_grid.cc index 6176424ed1..11e779cc66 100644 --- a/met/src/libcode/vx_grid/latlon_grid.cc +++ b/met/src/libcode/vx_grid/latlon_grid.cc @@ -1,5 +1,3 @@ - - // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* // ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) @@ -9,8 +7,6 @@ // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* - - //////////////////////////////////////////////////////////////////////// @@ -77,6 +73,8 @@ lat_ll = lon_ll = 0.0; delta_lat = delta_lon = 0.0; +wrapLon = false; + memset(&Data, 0, sizeof(Data)); return; @@ -120,7 +118,29 @@ Name = data.name; Data = data; + // + // wrap longitudes when: + // - all 360 degrees are included + // - 360 is divisible by delta_lon + // +bool lon_rng_flag = fabs((Nx + 1)*delta_lon) >= 360.0; +double lon_div = 360.0 / delta_lon; +bool lon_div_flag = is_eq(lon_div, floor(lon_div)); + +wrapLon = (lon_rng_flag && lon_div_flag); + + // + // inform users about unexpected delta longitudes + // + +if ( lon_rng_flag && !lon_div_flag ) { + + mlog << Debug(4) << "Cannot wrap longitudes since 360 is not " + << "evenly divisible by delta_lon = " + << delta_lon << ".\n"; + +} return; @@ -133,6 +153,7 @@ return; void LatLonGrid::latlon_to_xy(double lat, double lon, double &x, double &y) const { + double n; y = (lat - lat_ll)/delta_lat; @@ -249,6 +270,8 @@ out << prefix << "lon_ll = " << lon_ll << "\n"; out << prefix << "delta_lat_ll = " << delta_lat << "\n"; out << prefix << "delta_lon_ll = " << delta_lon << "\n"; +out << prefix << "wrapLon = " << bool_to_string(wrapLon) << "\n"; + // // done // @@ -282,6 +305,8 @@ snprintf(junk, sizeof(junk), " lon_ll: %.3f", lon_ll); a << junk; snprintf(junk, sizeof(junk), " delta_lat: %.3f", delta_lat); a << junk; snprintf(junk, sizeof(junk), " delta_lon: %.3f", delta_lon); a << junk; +snprintf(junk, sizeof(junk), " wrapLon: %s", bool_to_string(wrapLon)); a << junk; + // // done // @@ -328,33 +353,13 @@ return ( 0.0 ); //////////////////////////////////////////////////////////////////////// -bool LatLonGrid::is_global() const - -{ - -const double lon_range = fabs((Nx + 1)*delta_lon); -const double lat_range = fabs((Ny + 1)*delta_lat); - -const bool full_range_lat = (lat_range >= 180.0); -const bool full_range_lon = (lon_range >= 360.0); - -const bool answer = full_range_lat && full_range_lon; - -return ( answer ); - -} - - -//////////////////////////////////////////////////////////////////////// - - void LatLonGrid::shift_right(int N) { if ( N == 0 ) return; -if ( ! is_global() ) { +if ( ! wrapLon ) { mlog << Error << "\n\n LatLonGrid::shift_right(int) -> " @@ -450,5 +455,3 @@ return; //////////////////////////////////////////////////////////////////////// - - diff --git a/met/src/libcode/vx_grid/latlon_grid.h b/met/src/libcode/vx_grid/latlon_grid.h index 4f0f01cb5b..21c86d6e2c 100644 --- a/met/src/libcode/vx_grid/latlon_grid.h +++ b/met/src/libcode/vx_grid/latlon_grid.h @@ -1,5 +1,3 @@ - - // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* // ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) @@ -9,8 +7,6 @@ // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* - - //////////////////////////////////////////////////////////////////////// @@ -46,6 +42,8 @@ class LatLonGrid : public GridRep { int Nx; int Ny; + bool wrapLon; + ConcatString Name; LatLonData Data; @@ -79,7 +77,7 @@ class LatLonGrid : public GridRep { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); @@ -91,7 +89,8 @@ class LatLonGrid : public GridRep { //////////////////////////////////////////////////////////////////////// -inline double LatLonGrid::scale_km() const { return ( -1.0 ); } +inline double LatLonGrid::scale_km() const { return ( -1.0 ); } +inline bool LatLonGrid::wrap_lon() const { return ( wrapLon ); } //////////////////////////////////////////////////////////////////////// @@ -101,6 +100,3 @@ inline double LatLonGrid::scale_km() const { return ( -1.0 ); } //////////////////////////////////////////////////////////////////////// - - - diff --git a/met/src/libcode/vx_grid/lc_grid.cc b/met/src/libcode/vx_grid/lc_grid.cc index aab40b015f..41a2e375e9 100644 --- a/met/src/libcode/vx_grid/lc_grid.cc +++ b/met/src/libcode/vx_grid/lc_grid.cc @@ -638,7 +638,7 @@ return ( angle ); //////////////////////////////////////////////////////////////////////// -bool LambertGrid::is_global() const +bool LambertGrid::wrap_lon() const { diff --git a/met/src/libcode/vx_grid/lc_grid.h b/met/src/libcode/vx_grid/lc_grid.h index 6b67eabb0b..0b3fefdaaa 100644 --- a/met/src/libcode/vx_grid/lc_grid.h +++ b/met/src/libcode/vx_grid/lc_grid.h @@ -118,7 +118,7 @@ class LambertGrid : public GridRep { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); diff --git a/met/src/libcode/vx_grid/merc_grid.cc b/met/src/libcode/vx_grid/merc_grid.cc index c45dee4d6d..d89a7898e4 100644 --- a/met/src/libcode/vx_grid/merc_grid.cc +++ b/met/src/libcode/vx_grid/merc_grid.cc @@ -572,7 +572,7 @@ return ( 0.0 ); //////////////////////////////////////////////////////////////////////// -bool MercatorGrid::is_global() const +bool MercatorGrid::wrap_lon() const { diff --git a/met/src/libcode/vx_grid/merc_grid.h b/met/src/libcode/vx_grid/merc_grid.h index 94dd842bda..f46a9c3bf8 100644 --- a/met/src/libcode/vx_grid/merc_grid.h +++ b/met/src/libcode/vx_grid/merc_grid.h @@ -101,7 +101,7 @@ class MercatorGrid : public GridRep { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); diff --git a/met/src/libcode/vx_grid/rot_latlon_grid.cc b/met/src/libcode/vx_grid/rot_latlon_grid.cc index 2e97cba498..12b6286135 100644 --- a/met/src/libcode/vx_grid/rot_latlon_grid.cc +++ b/met/src/libcode/vx_grid/rot_latlon_grid.cc @@ -1,5 +1,3 @@ - - // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* // ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) @@ -321,6 +319,8 @@ snprintf(junk, sizeof(junk), " rot_lon_ll: %.3f", RData.rot_lon_ll); a << junk snprintf(junk, sizeof(junk), " delta_rot_lat: %.3f", RData.delta_rot_lat); a << junk; snprintf(junk, sizeof(junk), " delta_rot_lon: %.3f", RData.delta_rot_lon); a << junk; +snprintf(junk, sizeof(junk), " wrapLon: %s", bool_to_string(wrapLon)); a << junk; + snprintf(junk, sizeof(junk), " true_lat_south_pole: %.3f", RData.true_lat_south_pole); a << junk; snprintf(junk, sizeof(junk), " true_lon_south_pole: %.3f", RData.true_lon_south_pole); a << junk; @@ -372,26 +372,6 @@ return ( 0.0 ); //////////////////////////////////////////////////////////////////////// -bool RotatedLatLonGrid::is_global() const - -{ - -const double lon_range = fabs((Nx + 1)*delta_lon); -const double lat_range = fabs((Ny + 1)*delta_lat); - -const bool full_range_lat = (lat_range >= 180.0); -const bool full_range_lon = (lon_range >= 360.0); - -const bool answer = full_range_lat && full_range_lon; - -return ( answer ); - -} - - -//////////////////////////////////////////////////////////////////////// - - void RotatedLatLonGrid::shift_right(int N) { @@ -472,5 +452,3 @@ return; //////////////////////////////////////////////////////////////////////// - - diff --git a/met/src/libcode/vx_grid/rot_latlon_grid.h b/met/src/libcode/vx_grid/rot_latlon_grid.h index eea580d285..79a75e1869 100644 --- a/met/src/libcode/vx_grid/rot_latlon_grid.h +++ b/met/src/libcode/vx_grid/rot_latlon_grid.h @@ -1,5 +1,3 @@ - - // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* // ** Copyright UCAR (c) 1992 - 2021 // ** University Corporation for Atmospheric Research (UCAR) @@ -72,7 +70,7 @@ class RotatedLatLonGrid : public LatLonGrid { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); @@ -84,7 +82,8 @@ class RotatedLatLonGrid : public LatLonGrid { //////////////////////////////////////////////////////////////////////// -inline double RotatedLatLonGrid::scale_km() const { return ( -1.0 ); } +inline double RotatedLatLonGrid::scale_km() const { return ( -1.0 ); } +inline bool RotatedLatLonGrid::wrap_lon() const { return ( wrapLon ); } //////////////////////////////////////////////////////////////////////// @@ -94,6 +93,3 @@ inline double RotatedLatLonGrid::scale_km() const { return ( -1.0 ); } //////////////////////////////////////////////////////////////////////// - - - diff --git a/met/src/libcode/vx_grid/st_grid.cc b/met/src/libcode/vx_grid/st_grid.cc index 65246ae5fc..3b64e4a14d 100644 --- a/met/src/libcode/vx_grid/st_grid.cc +++ b/met/src/libcode/vx_grid/st_grid.cc @@ -553,7 +553,7 @@ return ( angle ); //////////////////////////////////////////////////////////////////////// -bool StereographicGrid::is_global() const +bool StereographicGrid::wrap_lon() const { diff --git a/met/src/libcode/vx_grid/st_grid.h b/met/src/libcode/vx_grid/st_grid.h index e80f3aadff..9fecdba445 100644 --- a/met/src/libcode/vx_grid/st_grid.h +++ b/met/src/libcode/vx_grid/st_grid.h @@ -99,7 +99,7 @@ class StereographicGrid : public GridRep { double rot_grid_to_earth(int x, int y) const; - bool is_global() const; + bool wrap_lon() const; void shift_right(int); diff --git a/met/src/libcode/vx_regrid/vx_regrid.cc b/met/src/libcode/vx_regrid/vx_regrid.cc index 0a7da68e3e..6be4189fa7 100644 --- a/met/src/libcode/vx_regrid/vx_regrid.cc +++ b/met/src/libcode/vx_regrid/vx_regrid.cc @@ -133,6 +133,7 @@ to_data.set_accum (from_data.accum()); // // copy data // + for (xt=0; xt<(to_grid.nx()); ++xt) { for (yt=0; yt<(to_grid.ny()); ++yt) { @@ -144,14 +145,14 @@ for (xt=0; xt<(to_grid.nx()); ++xt) { xf = nint(x_from); yf = nint(y_from); - if ( (xf < 0) || (xf >= from_grid.nx()) || (yf < 0) || (yf >= from_grid.ny()) ) { - + if ( ( (xf < 0 || xf >= from_grid.nx()) && !from_grid.wrap_lon() ) || + yf < 0 || yf >= from_grid.ny() ) { value = bad_data_float; - - } else { - value = compute_horz_interp(from_data, x_from, y_from, bad_data_double, - info.method, info.width, info.shape, info.vld_thresh); - + } + else { + value = compute_horz_interp(from_data, x_from, y_from, + bad_data_double, info.method, info.width, + info.shape, from_grid.wrap_lon(), info.vld_thresh); } to_data.put(value, xt, yt); @@ -327,6 +328,7 @@ to_data.set_accum (from_data.accum()); // // copy data // + for (xt=0; xt<(to_grid.nx()); ++xt) { for (yt=0; yt<(to_grid.ny()); ++yt) { @@ -338,15 +340,14 @@ for (xt=0; xt<(to_grid.nx()); ++xt) { xf = nint(x_from); yf = nint(y_from); - if ( (xf < 0) || (xf >= from_grid.nx()) || - (yf < 0) || (yf >= from_grid.ny()) ) { - + if ( ( (xf < 0 || xf >= from_grid.nx()) && !from_grid.wrap_lon() ) || + yf < 0 || yf >= from_grid.ny() ) { value = bad_data_float; } else { value = compute_horz_interp(from_data, x_from, y_from, bad_data_double, InterpMthd_Max, info.width, - info.shape, info.vld_thresh); + info.shape, from_grid.wrap_lon(), info.vld_thresh); } to_data.put(value, xt, yt); diff --git a/met/src/libcode/vx_regrid/vx_regrid_budget.cc b/met/src/libcode/vx_regrid/vx_regrid_budget.cc index 881e56ef1c..42157cb729 100644 --- a/met/src/libcode/vx_regrid/vx_regrid_budget.cc +++ b/met/src/libcode/vx_regrid/vx_regrid_budget.cc @@ -77,7 +77,7 @@ for (ixt=0; ixt<(to_grid.nx()); ++ixt) { from_grid.latlon_to_xy(lat, lon, dxf, dyf); - value = interp_bilin(from_data, dxf, dyf); + value = interp_bilin(from_data, from_grid.wrap_lon(), dxf, dyf); if ( value != bad_data_double ) { sum += value; ++count; } diff --git a/met/src/libcode/vx_statistics/apply_mask.cc b/met/src/libcode/vx_statistics/apply_mask.cc index 1aea2fe836..01d88f601e 100644 --- a/met/src/libcode/vx_statistics/apply_mask.cc +++ b/met/src/libcode/vx_statistics/apply_mask.cc @@ -185,7 +185,7 @@ void parse_grid_mask(const ConcatString &mask_grid_str, const Grid &grid, // // Check to make sure that we're not using the full domain // - if( full_domain_str != mask_grid_str) { + if(full_domain_str != mask_grid_str) { // // Search for the grid name in the predefined grids diff --git a/met/src/libcode/vx_statistics/pair_base.cc b/met/src/libcode/vx_statistics/pair_base.cc index d90e3167a6..aaf73755b9 100644 --- a/met/src/libcode/vx_statistics/pair_base.cc +++ b/met/src/libcode/vx_statistics/pair_base.cc @@ -876,6 +876,7 @@ double compute_interp(const DataPlaneArray &dpa, const double obs_v, const double cmn, const double csd, const InterpMthd method, const int width, const GridTemplateFactory::GridTemplates shape, + const bool wrap_lon, const double thresh, const bool spfh_flag, const LevelType lvl_typ, const double to_lvl, const int i_blw, const int i_abv, @@ -886,14 +887,16 @@ double compute_interp(const DataPlaneArray &dpa, if(dpa.n_planes() == 0) return(bad_data_double); v_blw = compute_horz_interp(dpa[i_blw], obs_x, obs_y, obs_v, cmn, csd, - method, width, shape, thresh, cat_thresh); + method, width, shape, wrap_lon, + thresh, cat_thresh); if(i_blw == i_abv) { v = v_blw; } else { v_abv = compute_horz_interp(dpa[i_abv], obs_x, obs_y, obs_v, cmn, csd, - method, width, shape, thresh, cat_thresh); + method, width, shape, wrap_lon, + thresh, cat_thresh); // Check for bad data prior to vertical interpolation if(is_bad_data(v_blw) || is_bad_data(v_abv)) { @@ -932,6 +935,7 @@ void get_interp_points(const DataPlaneArray &dpa, const double obs_x, const double obs_y, const InterpMthd method, const int width, const GridTemplateFactory::GridTemplates shape, + const bool wrap_lon, const double thresh, const bool spfh_flag, const LevelType lvl_typ, const double to_lvl, const int i_blw, const int i_abv, @@ -947,7 +951,7 @@ void get_interp_points(const DataPlaneArray &dpa, int i, n_vld; NumArray pts_blw, pts_abv; GridTemplateFactory gtf; - const GridTemplate* gt = gtf.buildGT(shape, width); + const GridTemplate* gt = gtf.buildGT(shape, width, wrap_lon); // Get interpolation points below the observation pts_blw = interp_points(dpa[i_blw], *gt, obs_x, obs_y); diff --git a/met/src/libcode/vx_statistics/pair_base.h b/met/src/libcode/vx_statistics/pair_base.h index ebe0f845fc..465432f16f 100644 --- a/met/src/libcode/vx_statistics/pair_base.h +++ b/met/src/libcode/vx_statistics/pair_base.h @@ -197,6 +197,7 @@ extern double compute_interp(const DataPlaneArray &dpa, const double obs_v, const double cmn, const double csd, const InterpMthd method, const int width, const GridTemplateFactory::GridTemplates shape, + const bool wrap_lon, const double thresh, const bool spfh_flag, const LevelType lvl_typ, const double to_lvl, const int i_blw, const int i_abv, @@ -206,6 +207,7 @@ extern void get_interp_points(const DataPlaneArray &dpa, const double obs_x, const double obs_y, const InterpMthd method, const int width, const GridTemplateFactory::GridTemplates shape, + const bool wrap_lon, const double thresh, const bool spfh_flag, const LevelType lvl_typ, const double to_lvl, const int i_blw, const int i_abv, diff --git a/met/src/libcode/vx_statistics/pair_data_ensemble.cc b/met/src/libcode/vx_statistics/pair_data_ensemble.cc index 0fcf84fc99..683aa499b9 100644 --- a/met/src/libcode/vx_statistics/pair_data_ensemble.cc +++ b/met/src/libcode/vx_statistics/pair_data_ensemble.cc @@ -1408,8 +1408,8 @@ void VxPairDataEnsemble::add_point_obs(float *hdr_arr, int *hdr_typ_arr, y = nint(obs_y); // Check if the observation's lat/lon is on the grid - if(x < 0 || x >= gr.nx() || - y < 0 || y >= gr.ny()) return; + if(((x < 0 || x >= gr.nx()) && !gr.wrap_lon()) || + y < 0 || y >= gr.ny()) return; // For pressure levels, check if the observation pressure level // falls in the requsted range. @@ -1556,7 +1556,7 @@ void VxPairDataEnsemble::add_point_obs(float *hdr_arr, int *hdr_typ_arr, cmn_v = compute_interp(climo_mn_dpa, obs_x, obs_y, obs_v, bad_data_double, bad_data_double, pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, - pd[0][0][k].interp_shape, + pd[0][0][k].interp_shape, gr.wrap_lon(), interp_thresh, spfh_flag, fcst_info->level().type(), to_lvl, cmn_lvl_blw, cmn_lvl_abv); @@ -1583,7 +1583,7 @@ void VxPairDataEnsemble::add_point_obs(float *hdr_arr, int *hdr_typ_arr, csd_v = compute_interp(climo_sd_dpa, obs_x, obs_y, obs_v, bad_data_double, bad_data_double, pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, - pd[0][0][k].interp_shape, + pd[0][0][k].interp_shape, gr.wrap_lon(), interp_thresh, spfh_flag, fcst_info->level().type(), to_lvl, csd_lvl_blw, csd_lvl_abv); @@ -1612,7 +1612,7 @@ void VxPairDataEnsemble::add_point_obs(float *hdr_arr, int *hdr_typ_arr, //////////////////////////////////////////////////////////////////////// -void VxPairDataEnsemble::add_ens(int member, bool mn) { +void VxPairDataEnsemble::add_ens(int member, bool mn, Grid &gr) { int i, j, k, l; int f_lvl_blw, f_lvl_abv; double to_lvl, fcst_v; @@ -1654,6 +1654,7 @@ void VxPairDataEnsemble::add_ens(int member, bool mn) { pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, pd[0][0][k].interp_shape, + gr.wrap_lon(), interp_thresh, spfh_flag, fcst_info->level().type(), to_lvl, f_lvl_blw, f_lvl_abv); diff --git a/met/src/libcode/vx_statistics/pair_data_ensemble.h b/met/src/libcode/vx_statistics/pair_data_ensemble.h index 84aa45796b..7d59896593 100644 --- a/met/src/libcode/vx_statistics/pair_data_ensemble.h +++ b/met/src/libcode/vx_statistics/pair_data_ensemble.h @@ -279,7 +279,7 @@ class VxPairDataEnsemble { unixtime, const char *, float *, Grid &, const char * = 0, const DataPlane * = 0); - void add_ens(int, bool mn); + void add_ens(int, bool mn, Grid &); int get_n_pair() const; diff --git a/met/src/libcode/vx_statistics/pair_data_point.cc b/met/src/libcode/vx_statistics/pair_data_point.cc index a77749abba..7bfda5fa42 100644 --- a/met/src/libcode/vx_statistics/pair_data_point.cc +++ b/met/src/libcode/vx_statistics/pair_data_point.cc @@ -967,8 +967,9 @@ void VxPairDataPoint::add_point_obs(float *hdr_arr, const char *hdr_typ_str, y = nint(obs_y); // Check if the observation's lat/lon is on the grid - if(x < 0 || x >= gr.nx() || - y < 0 || y >= gr.ny()) { + if(((x < 0 || x >= gr.nx()) && !gr.wrap_lon()) || + y < 0 || y >= gr.ny()) { + mlog << Debug(4) << "For " << fcst_info->magic_str() << " versus " << obs_info->magic_str() @@ -989,7 +990,8 @@ void VxPairDataPoint::add_point_obs(float *hdr_arr, const char *hdr_typ_str, double topo = compute_horz_interp( *sfc_info.topo_ptr, obs_x, obs_y, hdr_elv, InterpMthd_Bilin, 2, - GridTemplateFactory::GridTemplate_Square, 1.0); + GridTemplateFactory::GridTemplate_Square, + gr.wrap_lon(), 1.0); // Skip bad topography values if(is_bad_data(hdr_elv) || is_bad_data(topo)) { @@ -1165,7 +1167,7 @@ void VxPairDataPoint::add_point_obs(float *hdr_arr, const char *hdr_typ_str, cmn_v = compute_interp(climo_mn_dpa, obs_x, obs_y, obs_v, bad_data_double, bad_data_double, pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, - pd[0][0][k].interp_shape, + pd[0][0][k].interp_shape, gr.wrap_lon(), interp_thresh, spfh_flag, fcst_info->level().type(), to_lvl, cmn_lvl_blw, cmn_lvl_abv); @@ -1192,8 +1194,8 @@ void VxPairDataPoint::add_point_obs(float *hdr_arr, const char *hdr_typ_str, // Compute the interpolated climatology standard deviation csd_v = compute_interp(climo_sd_dpa, obs_x, obs_y, obs_v, bad_data_double, bad_data_double, - pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, - pd[0][0][k].interp_shape, + pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, + pd[0][0][k].interp_shape, gr.wrap_lon(), interp_thresh, spfh_flag, fcst_info->level().type(), to_lvl, csd_lvl_blw, csd_lvl_abv); @@ -1222,14 +1224,14 @@ void VxPairDataPoint::add_point_obs(float *hdr_arr, const char *hdr_typ_str, fcst_v = compute_sfc_interp(fcst_dpa[0], obs_x, obs_y, hdr_elv, obs_v, pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, - pd[0][0][k].interp_shape, interp_thresh, sfc_info, - is_land); + pd[0][0][k].interp_shape, gr.wrap_lon(), + interp_thresh, sfc_info, is_land); } // Otherwise, compute interpolated value else { fcst_v = compute_interp(fcst_dpa, obs_x, obs_y, obs_v, cmn_v, csd_v, pd[0][0][k].interp_mthd, pd[0][0][k].interp_wdth, - pd[0][0][k].interp_shape, + pd[0][0][k].interp_shape, gr.wrap_lon(), interp_thresh, spfh_flag, fcst_info->level().type(), to_lvl, f_lvl_blw, f_lvl_abv); @@ -1239,8 +1241,10 @@ void VxPairDataPoint::add_point_obs(float *hdr_arr, const char *hdr_typ_str, mlog << Debug(4) << "For " << fcst_info->magic_str() << " versus " << obs_info->magic_str() - << ", skipping observation due to bad data in the interpolated " - << "forecast value:\n" + << ", skipping observation due to bad data in the " + << interpmthd_to_string(pd[0][0][k].interp_mthd) << "(" + << pd[0][0][k].interp_wdth * pd[0][0][k].interp_wdth + << ") interpolated forecast value:\n" << point_obs_to_string(hdr_arr, hdr_typ_str, hdr_sid_str, hdr_ut, obs_qty, obs_arr, var_name) << "\n"; diff --git a/met/src/tools/core/ensemble_stat/ensemble_stat.cc b/met/src/tools/core/ensemble_stat/ensemble_stat.cc index d87c377bdf..26a8a5dfb3 100644 --- a/met/src/tools/core/ensemble_stat/ensemble_stat.cc +++ b/met/src/tools/core/ensemble_stat/ensemble_stat.cc @@ -1123,7 +1123,7 @@ int process_point_ens(int i_ens, int &n_miss) { conf_info.vx_opt[i].vx_pd.set_fcst_dpa(fcst_dpa); // Compute forecast values for this ensemble member - conf_info.vx_opt[i].vx_pd.add_ens(i_ens-n_miss, is_ens_mean); + conf_info.vx_opt[i].vx_pd.add_ens(i_ens-n_miss, is_ens_mean, grid); } // end for i @@ -1588,13 +1588,13 @@ void process_grid_vx() { // Smooth the ensemble mean field, if requested if(ens_mean_flag && (field == FieldType_Fcst || field == FieldType_Both)) { - emn_dp = smooth_field(emn_dp, mthd, wdth, shape, + emn_dp = smooth_field(emn_dp, mthd, wdth, shape, grid.wrap_lon(), vld_thresh, gaussian); } // Smooth the observation field, if requested if(field == FieldType_Obs || field == FieldType_Both) { - obs_dp = smooth_field(obs_dp, mthd, wdth, shape, + obs_dp = smooth_field(obs_dp, mthd, wdth, shape, grid.wrap_lon(), vld_thresh, gaussian); } @@ -1617,7 +1617,7 @@ void process_grid_vx() { // Smooth the forecast field, if requested if(field == FieldType_Fcst || field == FieldType_Both) { - fcst_dp[k] = smooth_field(fcst_dp[k], mthd, wdth, shape, + fcst_dp[k] = smooth_field(fcst_dp[k], mthd, wdth, shape, grid.wrap_lon(), vld_thresh, gaussian); } @@ -2060,7 +2060,8 @@ void track_counts(int i_vx, const DataPlane &dp) { fractional_coverage(dp, frac_dp, conf_info.nbrhd_prob.width[j], conf_info.nbrhd_prob.shape, - ThreshBuf[i], conf_info.nbrhd_prob.vld_thresh); + grid.wrap_lon(), ThreshBuf[i], + conf_info.nbrhd_prob.vld_thresh); // Increment counts const double *Frac = frac_dp.data(); @@ -2496,6 +2497,7 @@ void write_ens_nc(int i_ens, DataPlane &dp) { smooth_dp = smooth_field(prob_dp, InterpMthd_UW_Mean, conf_info.nbrhd_prob.width[j], conf_info.nbrhd_prob.shape, + grid.wrap_lon(), conf_info.nbrhd_prob.vld_thresh, info); for(k=0; kshape, interp->width[j]); + GridTemplate* gt = gtf.buildGT(interp->shape, interp->width[j], grid.wrap_lon()); shc.set_interp_mthd(interp_mthd, interp->shape); int interp_pnts = gt->size(); @@ -769,8 +769,8 @@ void process_scores() { if(interp->field == FieldType_Fcst || interp->field == FieldType_Both) { smooth_field(fcst_dp, fcst_dp_smooth, interp_mthd, - interp->width[j], interp->shape, interp->vld_thresh, - interp->gaussian); + interp->width[j], interp->shape, grid.wrap_lon(), + interp->vld_thresh, interp->gaussian); } // Do not smooth the forecast field else { @@ -781,8 +781,8 @@ void process_scores() { if(interp->field == FieldType_Obs || interp->field == FieldType_Both) { smooth_field(obs_dp, obs_dp_smooth, interp_mthd, - interp->width[j], interp->shape, interp->vld_thresh, - interp->gaussian); + interp->width[j], interp->shape, grid.wrap_lon(), + interp->vld_thresh, interp->gaussian); } // Do not smooth the observation field else { @@ -970,8 +970,8 @@ void process_scores() { if(interp->field == FieldType_Fcst || interp->field == FieldType_Both) { smooth_field(fu_dp, fu_dp_smooth, interp_mthd, - interp->width[j], interp->shape, interp->vld_thresh, - interp->gaussian); + interp->width[j], interp->shape, grid.wrap_lon(), + interp->vld_thresh, interp->gaussian); } // Do not smooth the forecast field else { @@ -984,8 +984,8 @@ void process_scores() { interp->field == FieldType_Both) { smooth_field(ou_dp, ou_dp_smooth, interp_mthd, interp->width[j], - interp->shape, interp->vld_thresh, - interp->gaussian); + interp->shape, grid.wrap_lon(), + interp->vld_thresh, interp->gaussian); } // Do not smooth the observation field else { @@ -1392,6 +1392,7 @@ void process_scores() { // Compute fractional coverage fractional_coverage(fcst_dp, fcst_dp_smooth, nbrhd->width[j], nbrhd->shape, + grid.wrap_lon(), conf_info.vx_opt[i].fcat_ta[k], nbrhd->vld_thresh); @@ -1430,6 +1431,7 @@ void process_scores() { // Compute fractional coverage fractional_coverage(obs_dp, obs_dp_smooth, nbrhd->width[j], nbrhd->shape, + grid.wrap_lon(), conf_info.vx_opt[i].ocat_ta[k], nbrhd->vld_thresh); diff --git a/met/src/tools/core/point_stat/point_stat.cc b/met/src/tools/core/point_stat/point_stat.cc index 5f609feeff..549e6fce9f 100644 --- a/met/src/tools/core/point_stat/point_stat.cc +++ b/met/src/tools/core/point_stat/point_stat.cc @@ -1676,7 +1676,8 @@ void do_hira_ens(int i_vx, const PairDataPoint *pd_ptr) { // Determine the number of points in the area GridTemplateFactory gtf; GridTemplate* gt = gtf.buildGT(conf_info.vx_opt[i_vx].hira_info.shape, - conf_info.vx_opt[i_vx].hira_info.width[i]); + conf_info.vx_opt[i_vx].hira_info.width[i], + grid.wrap_lon()); // Initialize hira_pd.clear(); @@ -1696,7 +1697,7 @@ void do_hira_ens(int i_vx, const PairDataPoint *pd_ptr) { get_interp_points(conf_info.vx_opt[i_vx].vx_pd.fcst_dpa, pd_ptr->x_na[j], pd_ptr->y_na[j], InterpMthd_Nbrhd, conf_info.vx_opt[i_vx].hira_info.width[i], - conf_info.vx_opt[i_vx].hira_info.shape, + conf_info.vx_opt[i_vx].hira_info.shape, grid.wrap_lon(), conf_info.vx_opt[i_vx].hira_info.vld_thresh, spfh_flag, conf_info.vx_opt[i_vx].vx_pd.fcst_info->level().type(), pd_ptr->lvl_na[j], lvl_blw, lvl_abv, f_ens); @@ -1873,7 +1874,7 @@ void do_hira_prob(int i_vx, const PairDataPoint *pd_ptr) { pd_ptr->x_na[k], pd_ptr->y_na[k], pd_ptr->o_na[k], pd_ptr->cmn_na[k], pd_ptr->csd_na[k], InterpMthd_Nbrhd, conf_info.vx_opt[i_vx].hira_info.width[j], - conf_info.vx_opt[i_vx].hira_info.shape, + conf_info.vx_opt[i_vx].hira_info.shape, grid.wrap_lon(), conf_info.vx_opt[i_vx].hira_info.vld_thresh, spfh_flag, conf_info.vx_opt[i_vx].vx_pd.fcst_info->level().type(), pd_ptr->lvl_na[k], lvl_blw, lvl_abv, &cat_thresh); @@ -1892,7 +1893,7 @@ void do_hira_prob(int i_vx, const PairDataPoint *pd_ptr) { pd_ptr->x_na[k], pd_ptr->y_na[k], pd_ptr->o_na[k], pd_ptr->cmn_na[k], pd_ptr->csd_na[k], InterpMthd_Nbrhd, conf_info.vx_opt[i_vx].hira_info.width[j], - conf_info.vx_opt[i_vx].hira_info.shape, + conf_info.vx_opt[i_vx].hira_info.shape, grid.wrap_lon(), conf_info.vx_opt[i_vx].hira_info.vld_thresh, spfh_flag, conf_info.vx_opt[i_vx].vx_pd.fcst_info->level().type(), pd_ptr->lvl_na[k], lvl_blw, lvl_abv, &cat_thresh); diff --git a/met/src/tools/core/point_stat/point_stat_conf_info.cc b/met/src/tools/core/point_stat/point_stat_conf_info.cc index 87183cb5d0..6117a0f7fc 100644 --- a/met/src/tools/core/point_stat/point_stat_conf_info.cc +++ b/met/src/tools/core/point_stat/point_stat_conf_info.cc @@ -1051,6 +1051,7 @@ void PointStatVxOpt::set_vx_pd(PointStatConfInfo *conf_info) { for(i=0; i @@ -346,7 +346,7 @@ &DATA_DIR_MODEL;/grib1/gfs/gfs_2012040900_F036.grib \ G212 \ &OUTPUT_DIR;/regrid/regrid_data_plane_GFS_TO_G212_CONVERT_CENSOR.nc \ - -field 'name="TMP"; level="Z2";' \ + -field 'name="TMP"; level="Z2";' \ -convert 'convert(x) = x - 273.15;' \ -censor lt0 -9999 @@ -355,4 +355,20 @@ + + + + &MET_BIN;/regrid_data_plane + \ + &DATA_DIR_MODEL;/nccf/GloTEC_TEC_2015_03_17.nc \ + "latlon 360 91 -45.0 0 1.0 1.0" \ + &OUTPUT_DIR;/regrid/regrid_data_plane_WRAP_LON.nc \ + -field 'name="TEC"; level="(0,*,*)"; file_type=NETCDF_NCCF;' \ + -method MAX -width 5 -shape CIRCLE + + + &OUTPUT_DIR;/regrid/regrid_data_plane_WRAP_LON.nc + + + From 3c725bc6f3340633c4f6418313589bd420df7fd9 Mon Sep 17 00:00:00 2001 From: johnhg Date: Fri, 1 Oct 2021 22:42:19 -0600 Subject: [PATCH 2/2] Feature 1904 gen_ens_prod (#1927) * Per #1904, add gen_ens_prod tool. Note that this is a complete copy of the existing Ensemble-Stat tool, just renamed to Gen-Ens-Prod, including the newly added documentation section. * Per #1904, update the gen-ens-prod documentation labels, switching _ES_ to _GEP_ to get rid of RTD documentation warnings. * Per #1904, more renaming of EnsembleStat to GenEnsProd in the code and add the default configuration file. * Per #1904, add updated version of the MET flowchart for version 10.1.0. * Per #1904, add make directives for running a sample call to gen_ens_prod. * Per #1904, updates to the default Gen-Ens-Prod config file. * Per #1904, strip out the logic from Ensemble-Stat that does not apply to Gen-Ens-Prod. * Per #1904, updates to the Gen-Ens-Prod documentation. These are not complete. Continue edits in the configuration section. * Per #1904, fix command line in example to use -out instead of -outdir. * Per #1904, removing lots of uneeded code. Update to read climatology data for use in defining climo cdp threshold types. * Per #1904, add climo and climo_cdp options to ensemble_flag. * Per #1904, update gen_ens_prod to actually write climo and climo_cdp outputs. * Per #1904, add NumArray::set_const(double, int) to quickly call erase() followed by add_const(double, int). That saves code in gen_ens_prod. * Per #1904, add support for the ensemble control member. Exclude it from the spread. * Per #1904, update call to gen_ens_prod for make test and also update the unit tests. Still need more work to get climo working in unit tests. * Per #1904, update the fractional_coverage() function to handle climo mean/stdev for CDP type thresholds. * Per #1904, update Grid-Stat, Ensemlbe-Stat, and Gen-Ens-Prod to pass climo data to the fractional_coverage() function. * Per #1904, fix logic for handling climo data in the fractional_coverage() function. Also update the gen_ens_prod unit tests to call it once with/without a control member. I manually confirmed that the spread is the same in the output but the means differ. * Per #1904, add the make test script logic to unit_met_test_scripts.xml * Per #1904, complete initial version of docs for gen_ens_prod tool. Remove unused rng and tmp_dir config entries. Simplify variable names. * Per #1904, fix names to get it compiling and remove unneeded gsl include. * Per #1904, simplify variable names. * Per #1904, correct the logic for handling the control member. The ensemble sum is used to compute both the mean and the standard deviation. Since we include control member in the mean but not the standard deviation, we need to track two different versions of that sum. * Per #1904, in gen_ens_prod.cc correct calls to the smooth_field() and fractional_coverage() utility functions as their signatures where changed by recent enhancements in develop. --- met/README | 1 + met/configure.ac | 22 + met/data/config/GenEnsProdConfig_default | 141 +++ met/data/config/Makefile.am | 1 + met/docs/Flowchart/MET_flowchart.pptx | Bin 348311 -> 372615 bytes met/docs/Flowchart/MET_flowchart_v10.1.0.png | Bin 0 -> 169821 bytes met/docs/Users_Guide/gen-ens-prod.rst | 228 ++++ met/docs/Users_Guide/index.rst | 1 + met/export.mk | 1 + met/out/gen_ens_prod/.gitignore | 2 + met/scripts/.gitignore | 1 + met/scripts/Makefile | 2 + met/scripts/config/GenEnsProdConfig | 163 +++ met/scripts/examples/test_gen_ens_prod.sh | 8 + met/scripts/mk/gen_ens_prod.mk | 33 + met/src/basic/vx_util/data_plane_util.cc | 46 +- met/src/basic/vx_util/data_plane_util.h | 3 +- met/src/basic/vx_util/num_array.cc | 22 + met/src/basic/vx_util/num_array.h | 1 + .../tools/core/ensemble_stat/ensemble_stat.cc | 1 + met/src/tools/core/grid_stat/grid_stat.cc | 2 + met/src/tools/other/Makefile.am | 4 + met/src/tools/other/gen_ens_prod/.gitignore | 7 + met/src/tools/other/gen_ens_prod/Makefile.am | 44 + .../tools/other/gen_ens_prod/gen_ens_prod.cc | 1053 +++++++++++++++++ .../tools/other/gen_ens_prod/gen_ens_prod.h | 116 ++ .../gen_ens_prod/gen_ens_prod_conf_info.cc | 362 ++++++ .../gen_ens_prod/gen_ens_prod_conf_info.h | 121 ++ test/bin/unit_test.sh | 1 + test/config/GenEnsProdConfig | 158 +++ test/xml/unit_gen_ens_prod.xml | 78 ++ test/xml/unit_met_test_scripts.xml | 19 +- 32 files changed, 2635 insertions(+), 7 deletions(-) create mode 100644 met/data/config/GenEnsProdConfig_default create mode 100644 met/docs/Flowchart/MET_flowchart_v10.1.0.png create mode 100644 met/docs/Users_Guide/gen-ens-prod.rst create mode 100644 met/out/gen_ens_prod/.gitignore create mode 100644 met/scripts/config/GenEnsProdConfig create mode 100755 met/scripts/examples/test_gen_ens_prod.sh create mode 100644 met/scripts/mk/gen_ens_prod.mk create mode 100644 met/src/tools/other/gen_ens_prod/.gitignore create mode 100644 met/src/tools/other/gen_ens_prod/Makefile.am create mode 100644 met/src/tools/other/gen_ens_prod/gen_ens_prod.cc create mode 100644 met/src/tools/other/gen_ens_prod/gen_ens_prod.h create mode 100644 met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.cc create mode 100644 met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.h create mode 100644 test/config/GenEnsProdConfig create mode 100644 test/xml/unit_gen_ens_prod.xml diff --git a/met/README b/met/README index 91965d9cd5..11a9bce6b7 100644 --- a/met/README +++ b/met/README @@ -191,6 +191,7 @@ sub-directory, or the MET Online Tutorial: command line) will contain 36 executables: - ascii2nc - ensemble_stat + - gen_ens_prod - gen_vx_mask - grid_stat - gis_dump_dbf diff --git a/met/configure.ac b/met/configure.ac index cbe955c398..2fc599e47a 100644 --- a/met/configure.ac +++ b/met/configure.ac @@ -485,6 +485,27 @@ else AC_MSG_NOTICE([ensemble_stat will not be compiled]) fi +# gen_ens_prod + +AC_ARG_ENABLE(gen_ens_prod, + [AS_HELP_STRING([--disable-gen_ens_prod], [Disable compilation of gen_ens_prod])], + [case "${enableval}" in + yes | no ) ENABLE_GEN_ENS_PROD="${enableval}" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-gen_ens_prod) ;; + esac], + [ENABLE_GEN_ENS_PROD="yes"] +) + +AM_CONDITIONAL([ENABLE_GEN_ENS_PROD], [test "x$ENABLE_GEN_ENS_PROD" = "xyes"]) + +if test "x$ENABLE_GEN_ENS_PROD" = "xyes"; then + AC_DEFINE([ENABLE_GEN_ENS_PROD], [], ["build gen_ens_prod"]) + AC_MSG_NOTICE([gen_ens_prod will be compiled]) +else + AC_MSG_NOTICE([gen_ens_prod will not be compiled]) +fi + + # gen_vx_mask AC_ARG_ENABLE(gen_vx_mask, @@ -1226,6 +1247,7 @@ AC_CONFIG_FILES([Makefile src/tools/other/Makefile src/tools/other/ascii2nc/Makefile src/tools/other/lidar2nc/Makefile + src/tools/other/gen_ens_prod/Makefile src/tools/other/gen_vx_mask/Makefile src/tools/other/gis_utils/Makefile src/tools/other/ioda2nc/Makefile diff --git a/met/data/config/GenEnsProdConfig_default b/met/data/config/GenEnsProdConfig_default new file mode 100644 index 0000000000..fbe81a00f4 --- /dev/null +++ b/met/data/config/GenEnsProdConfig_default @@ -0,0 +1,141 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Gen-Ens-Prod configuration file. +// +// For additional information, please see the MET User's Guide. +// +//////////////////////////////////////////////////////////////////////////////// + +// +// Output model name to be written +// +model = "WRF"; + +// +// Output description to be written +// May be set separately in each "obs.field" entry +// +desc = "NA"; + +//////////////////////////////////////////////////////////////////////////////// + +// +// Verification grid +// May be set separately in each "field" entry +// +regrid = { + to_grid = NONE; + method = NEAREST; + width = 1; + vld_thresh = 0.5; + shape = SQUARE; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// May be set separately in each "field" entry +// +censor_thresh = []; +censor_val = []; +cat_thresh = []; +nc_var_str = ""; + +// +// Ensemble fields to be processed +// +ens = { + ens_thresh = 1.0; + vld_thresh = 1.0; + + field = [ + { + name = "APCP"; + level = "A03"; + cat_thresh = [ >0.0, >=5.0 ]; + } + ]; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Neighborhood ensemble probabilities +// +nbrhd_prob = { + width = [ 5 ]; + shape = CIRCLE; + vld_thresh = 0.0; +} + +// +// NMEP smoothing methods +// +nmep_smooth = { + vld_thresh = 0.0; + shape = CIRCLE; + gaussian_dx = 81.27; + gaussian_radius = 120; + type = [ + { + method = GAUSSIAN; + width = 1; + } + ]; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Climatology data +// +climo_mean = { + + file_name = []; + field = []; + + regrid = { + method = NEAREST; + width = 1; + vld_thresh = 0.5; + shape = SQUARE; + } + + time_interp_method = DW_MEAN; + day_interval = 31; + hour_interval = 6; +} + +climo_stdev = climo_mean; +climo_stdev = { + file_name = []; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Ensemble product output types +// May be set separately in each "ens.field" entry +// +ensemble_flag = { + latlon = TRUE; + mean = TRUE; + stdev = TRUE; + minus = TRUE; + plus = TRUE; + min = TRUE; + max = TRUE; + range = TRUE; + vld_count = TRUE; + frequency = TRUE; + nep = FALSE; + nmep = FALSE; + climo = FALSE; + climo_cdp = FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// + +version = "V10.1.0"; + +//////////////////////////////////////////////////////////////////////////////// diff --git a/met/data/config/Makefile.am b/met/data/config/Makefile.am index c73bb4cadc..5ab35344a9 100644 --- a/met/data/config/Makefile.am +++ b/met/data/config/Makefile.am @@ -23,6 +23,7 @@ config_DATA = \ ConfigMapData \ Ascii2NcConfig_default \ EnsembleStatConfig_default \ + GenEnsProdConfig_default \ GridStatConfig_default \ GridDiagConfig_default \ IODA2NCConfig_default \ diff --git a/met/docs/Flowchart/MET_flowchart.pptx b/met/docs/Flowchart/MET_flowchart.pptx index 575eaada143e9fdec65e32b93b8d25aebeb57fb9..5e4f364225c200df84ce4410605c6935ec25474b 100644 GIT binary patch delta 27199 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 Cdj+Z)|?kRDTlVrl~dJpj$Pew(u>23mN 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 diff --git a/met/docs/Flowchart/MET_flowchart_v10.1.0.png b/met/docs/Flowchart/MET_flowchart_v10.1.0.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f287d189d29cc5cae6cff4521dc634dd10319d GIT binary patch literal 169821 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 literal 0 HcmV?d00001 diff --git a/met/docs/Users_Guide/gen-ens-prod.rst b/met/docs/Users_Guide/gen-ens-prod.rst new file mode 100644 index 0000000000..4b1d556d69 --- /dev/null +++ b/met/docs/Users_Guide/gen-ens-prod.rst @@ -0,0 +1,228 @@ +.. _gen-ens-prod: + +Gen-Ens-Prod Tool +================= + +Introduction +____________ + +The Gen-Ens-Prod tool generates simple ensemble products (mean, spread, probability, etc) from gridded ensemble member input files. While it processes model inputs, but it does not compare them to observations or compute statistics. However, the output products can be passed as input to the MET statistics tools for comparison against observations. Climatological mean and standard deviation data may also be provided to define thresholds based on the climatological distribution at each grid point. + +Note that this ensemble product generation step was provided by the Ensemble-Stat tool in earlier versions of MET. The Gen-Ens-Prod tool replaces and extends that functionality. Users are strongly encouraged to migrate ensemble product generation from Ensemble-Stat to Gen-Ens-Prod, as new features will only be added to Gen-Ens-Prod and the existing Ensemble-Stat functionality will be deprecated in a future version. + +Scientific and statistical aspects +__________________________________ + +Ensemble forecasts derived from a set of deterministic ensemble members +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Ensemble forecasts are often created as a set of deterministic forecasts. The ensemble members are rarely used separately. Instead, they can be combined in various ways to produce a forecast. MET can combine the ensemble members into some type of summary forecast according to user specifications. Ensemble means are the most common, and can be paired with the ensemble variance or spread. Maximum, minimum and other summary values are also available, with details in the practical information section. + +The -ctrl command line option specifies an input file for the ensemble control member. The fields specified in the configuration file are read from the control member file. Those fields are included in the computation of the ensemble mean and probabilities but excluded from the ensemble spread. + +The ensemble relative frequency is the simplest method for turning a set of deterministic forecasts into something resembling a probability forecast. MET will create the ensemble relative frequency as the proportion of ensemble members forecasting some event. For example, if 5 out of 10 ensemble members predict measurable precipitation at a grid location, then the ensemble relative frequency of precipitation will be :math:`5/10=0.5`. If the ensemble relative frequency is calibrated (unlikely) then this could be thought of as a probability of precipitation. + +The neighborhood ensemble probability (NEP) and neighborhood maximum ensemble probability (NMEP) methods are described in :ref:`Schwartz and Sobash (2017) `. They are an extension of the ensemble relative frequencies described above. The NEP value is computed by averaging the relative frequency of the event within the neighborhood over all ensemble members. The NMEP value is computed as the fraction of ensemble members for which the event is occurring somewhere within the surrounding neighborhood. The NMEP output is typically smoothed using a Gaussian kernel filter. The neighborhood sizes and smoothing options can be customized in the configuration file. + +The Gen-Ens-Prod tool writes the gridded relative frequencies, NEP, and NMEP fields to a NetCDF output file. Probabilistic verification methods can then be applied to those fields by evaluating them with the Grid-Stat and/or Point-Stat tools. + +Climatology data +~~~~~~~~~~~~~~~~ + +The ensemble relative frequencies derived by Gen-Ens-Prod are computed by applying threshold(s) to the input ensemble member data. Those thresholds can be simple and remain constant over the entire domain (e.g. >0) or can be defined relative to the climatological distribution at each grid point (e.g. >CDP90, for exceeding the 90-th percentile of climatology). When using climatological distribution percentile (CDP) thresholds, the climatological mean and standard deviation must be provided in the configuration file. + +Practical Information +_____________________ + +This section contains information about configuring and running the Gen-Ens-Prod tool. The Gen-Ens-Prod tool writes a NetCDF output file containing the requested ensemble product fields for each input field specified. If provided, the climatology data files must be gridded. All input gridded model and climatology datasets must be on the same grid. However, users may leverage the automated regridding feature in MET if the desired output grid is specified in the configuration file. + +gen_ens_prod usage +~~~~~~~~~~~~~~~~~~~ + +The usage statement for the Ensemble Stat tool is shown below: + +.. code-block:: none + + Usage: gen_ens_prod + -ens file_1 ... file_n | ens_file_list + -out file + -config file + [-ctrl file] + [-log file] + [-v level] + +gen_ens_prod has three required arguments and accepts several optional ones. + +Required arguments gen_ens_prod +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. The **-ens file_1 ... file_n** option specifies the ensemble member file names. This argument is not required when ensemble files are specified in the **ens_file_list**, detailed below. + +2. The **ens_file_list** option is an ASCII file containing a list of ensemble member file names. This is not required when a file list is included on the command line, as described above. + +3. The **-out file** option specifies the NetCDF output file name to be written. + +4. The **-config file** option is a **GenEnsProdConfig** file containing the desired configuration settings. + +Optional arguments for gen_ens_prod +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4. The **-ctrl file** option specifies the input file for the ensemble control member. Data for this member is included in the computation of the ensemble mean, but excluded from the spread. + +5. The **-log** file outputs log messages to the specified file. + +6. The **-v level** option indicates the desired level of verbosity. The value of "level" will override the default setting of 2. Setting the verbosity to 0 will make the tool run with no log messages, while increasing the verbosity will increase the amount of logging. + +An example of the gen_ens_prod calling sequence is shown below: + +.. code-block:: none + + gen_ens_prod \ + -ens sample_fcst/2009123112/*gep*/d01_2009123112_02400.grib \ + -out out/gen_ens_prod/config/GenEnsProdConfig \ + -config config/GenEnsProdConfig -v 2 + +In this example, the Gen-Ens-Prod tool derives products from the input ensemble members listed on the command line. + +gen_ens_prod configuration file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default configuration file for the Gen-Ens-Prod tool named **GenEnsProdConfig_default** can be found in the installed *share/met/config* directory. Another version is located in *scripts/config*. We encourage users to make a copy of these files prior to modifying their contents. The contents of the configuration file are described in the subsections below. + +Note that environment variables may be used when editing configuration files, as described in :numref:`pb2nc configuration file` for the PB2NC tool. + +____________________ + +.. code-block:: none + + model = "WRF"; + desc = "NA"; + regrid = { ... } + censor_thresh = []; + censor_val = []; + nc_var_str = ""; + climo_mean = { ... } // Corresponding to ens.field entries + climo_stdev = { ... } // Corresponding to ens.field entries + rng = { ... } + version = "VN.N"; + +The configuration options listed above are common to many MET tools and are described in :numref:`config_options`. + +_____________________ + +.. code-block:: none + + ens = { + ens_thresh = 1.0; + vld_thresh = 1.0; + field = [ + { + name = "APCP"; + level = "A03"; + cat_thresh = [ >0.0, >=5.0 ]; + } + ]; + } + +The **ens** dictionary defines which ensemble fields should be processed. + +When summarizing the ensemble, compute a ratio of the number of valid ensemble fields to the total number of ensemble members. If this ratio is less than the **ens_thresh**, then quit with an error. This threshold must be between 0 and 1. Setting this threshold to 1 requires that all ensemble members input files exist and all requested data be present. + +When summarizing the ensemble, for each grid point compute a ratio of the number of valid data values to the number of ensemble members. If that ratio is less than **vld_thresh**, write out bad data for that grid point. This threshold must be between 0 and 1. Setting this threshold to 1 requires that each grid point contain valid data for all ensemble members in order to compute ensemble product values for that grid point. + +For each dictionary entry in the **field** array, give the name and vertical or accumulation level, plus one or more categorical thresholds in the **cat_thresh** entry. The formatting for threshold are described in :numref:`config_options`. It is the user's responsibility to know the units for each model variable and choose appropriate threshold values. The thresholds are used to define ensemble relative frequencies. For example, a threshold of >=5 is used to define the proportion of ensemble members predicting precipitation of at least 5mm at each grid point. + +_______________________ + +.. code-block:: none + + nbrhd_prob = { + width = [ 5 ]; + shape = CIRCLE; + vld_thresh = 0.0; + } + +The **nbrhd_prob** dictionary defines the neighborhoods used to compute NEP and NMEP output. + +The neighborhood **shape** is a **SQUARE** or **CIRCLE** centered on the current point, and the **width** array specifies the width of the square or diameter of the circle as an odd integer. The **vld_thresh** entry is a number between 0 and 1 specifying the required ratio of valid data in the neighborhood for an output value to be computed. + +If **ensemble_flag.nep** is set to TRUE, NEP output is created for each combination of the categorical threshold (**cat_thresh**) and neighborhood width specified. + +_____________________ + +.. code-block:: none + + nmep_smooth = { + vld_thresh = 0.0; + shape = CIRCLE; + gaussian_dx = 81.27; + gaussian_radius = 120; + type = [ + { + method = GAUSSIAN; + width = 1; + } + ]; + } + +Similar to the **interp** dictionary, the **nmep_smooth** dictionary includes a **type** array of dictionaries to define one or more methods for smoothing the NMEP data. Setting the interpolation method to nearest neighbor (**NEAREST**) effectively disables this smoothing step. + +If **ensemble_flag.nmep** is set to TRUE, NMEP output is created for each combination of the categorical threshold (**cat_thresh**), neighborhood width (**nbrhd_prob.width**), and smoothing method(**nmep_smooth.type**) specified. + +_____________________ + +.. code-block:: none + + ensemble_flag = { + latlon = TRUE; + mean = TRUE; + stdev = TRUE; + minus = TRUE; + plus = TRUE; + min = TRUE; + max = TRUE; + range = TRUE; + vld_count = TRUE; + frequency = TRUE; + nep = FALSE; + nmep = FALSE; + climo = FALSE; + climo_cdp = FALSE; + } + +The **ensemble_flag** specifies which derived ensemble fields should be calculated and output. Setting the flag to TRUE produces output of the specified field, while FALSE produces no output for that field type. The flags correspond to the following output line types: + +1. Grid Latitude and Longitude Fields + +2. Ensemble Mean Field + +3. Ensemble Standard Deviation Field + +4. Ensemble Mean - One Standard Deviation Field + +5. Ensemble Mean + One Standard Deviation Field + +6. Ensemble Minimum Field + +7. Ensemble Maximum Field + +8. Ensemble Range Field + +9. Ensemble Valid Data Count + +10. Ensemble Relative Frequency (i.e. uncalibrate probability forecast) for each categorical threshold (**cat_thresh**) specified + +11. Neighborhood Ensemble Probability for each categorical threshold (**cat_thresh**) and neighborhood width (**nbrhd_prob.width**) specified + +12. Neighborhood Maximum Ensemble Probability for each categorical threshold (**cat_thresh**), neighborhood width (**nbrhd_prob.width**), and smoothing method (**nmep_smooth.type**) specified + +13. Climatology mean (**climo_mean**) and standard deviation (**climo_stdev**) data regridded to the model domain + +14. Climatological Distribution Percentile field for each CDP threshold specified + +gen_ens_prod output +~~~~~~~~~~~~~~~~~~~~ + +The Gen-Ens-Prod tools writes a gridded NetCDF output file whose file name is specified using the -out command line option. The contents of that file depend on the contents of the **ens.field** array, the **ensemble_flag** options selected, and the presence of climatology data. The NetCDF variable names are self-describing and include the name/level of the field being processed, the type of ensemble product, and any relevant threshold information. If **nc_var_str** is defined for an **ens.field** array entry, that string is included in the corresponding NetCDF output variable names. + +The Gen-Ens-Prod NetCDF output can be passed as input to the MET statistics tools, like Point-Stat and Grid-Stat, for futher processing and comparison against observations. diff --git a/met/docs/Users_Guide/index.rst b/met/docs/Users_Guide/index.rst index 213fcae61d..5ee52862e0 100644 --- a/met/docs/Users_Guide/index.rst +++ b/met/docs/Users_Guide/index.rst @@ -50,6 +50,7 @@ The National Center for Atmospheric Research (NCAR) is sponsored by NSF. The DTC config_options_tc reformat_point reformat_grid + gen-ens-prod masking point-stat grid-stat diff --git a/met/export.mk b/met/export.mk index 93bd734461..eeb741edab 100644 --- a/met/export.mk +++ b/met/export.mk @@ -41,6 +41,7 @@ export HDF5_BASE_DIR export ENABLE_ASCII2NC export ENABLE_ENSEMBLE_STAT +export ENABLE_GEN_ENS_PROD export ENABLE_GEN_VX_MASK export ENABLE_GRID_STAT export ENABLE_IODA2NC diff --git a/met/out/gen_ens_prod/.gitignore b/met/out/gen_ens_prod/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/met/out/gen_ens_prod/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/met/scripts/.gitignore b/met/scripts/.gitignore index 2c32bbda55..1263b3675e 100644 --- a/met/scripts/.gitignore +++ b/met/scripts/.gitignore @@ -1,6 +1,7 @@ ascii2nc ensemble_stat gen_vx_mask +gen_ens_prod grib2 grid_stat gis_utils diff --git a/met/scripts/Makefile b/met/scripts/Makefile index e9bd0c73ff..2081261e28 100755 --- a/met/scripts/Makefile +++ b/met/scripts/Makefile @@ -51,6 +51,7 @@ all: ${TESTS} include $(MK_DIR)/ascii2nc.mk include $(MK_DIR)/ensemble_stat.mk +include $(MK_DIR)/gen_ens_prod.mk include $(MK_DIR)/gen_vx_mask.mk include $(MK_DIR)/grib2.mk include $(MK_DIR)/grid_stat.mk @@ -94,6 +95,7 @@ clean: rm -f ${TESTS} rm -f ${TEST_OUT_DIR}/ascii2nc/* rm -f ${TEST_OUT_DIR}/ensemble_stat/* + rm -f ${TEST_OUT_DIR}/gen_ens_prod/* rm -f ${TEST_OUT_DIR}/gen_vx_mask/* rm -f ${TEST_OUT_DIR}/grid_stat/* rm -f ${TEST_OUT_DIR}/madis2nc/* diff --git a/met/scripts/config/GenEnsProdConfig b/met/scripts/config/GenEnsProdConfig new file mode 100644 index 0000000000..a0a52794c9 --- /dev/null +++ b/met/scripts/config/GenEnsProdConfig @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Gen-Ens-Prod configuration file. +// +// For additional information, please see the MET User's Guide. +// +//////////////////////////////////////////////////////////////////////////////// + +// +// Output model name to be written +// +model = "WRF"; + +// +// Output description to be written +// May be set separately in each "obs.field" entry +// +desc = "NA"; + +//////////////////////////////////////////////////////////////////////////////// + +// +// Verification grid +// May be set separately in each "field" entry +// +regrid = { + to_grid = NONE; + method = NEAREST; + width = 1; + vld_thresh = 0.5; + shape = SQUARE; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// May be set separately in each "field" entry +// +censor_thresh = []; +censor_val = []; +cat_thresh = []; +nc_var_str = ""; + +// +// Ensemble fields to be processed +// +ens = { + ens_thresh = 1.0; + vld_thresh = 1.0; + + field = [ + { + name = "APCP"; + level = [ "A24" ]; + cat_thresh = [ >0.0, >=10.0 ]; + ensemble_flag = TRUE; + }, + { + name = "REFC"; + level = [ "L0" ]; + cat_thresh = [ >=35.0 ]; + GRIB1_ptv = 129; + }, + { + name = "UGRD"; + level = [ "Z10" ]; + cat_thresh = [ >=5.0 ]; + }, + { + name = "VGRD"; + level = [ "Z10" ]; + cat_thresh = [ >=5.0 ]; + }, + { + name = "WIND"; + level = [ "Z10" ]; + cat_thresh = [ >=5.0 ]; + } + ]; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Neighborhood ensemble probabilities +// +nbrhd_prob = { + width = [ 5 ]; + shape = CIRCLE; + vld_thresh = 0.0; +} + +// +// NMEP smoothing methods +// +nmep_smooth = { + vld_thresh = 0.0; + shape = CIRCLE; + gaussian_dx = 81.27; + gaussian_radius = 120; + type = [ + { + method = GAUSSIAN; + width = 1; + } + ]; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Climatology data +// +climo_mean = { + + file_name = []; + field = []; + + regrid = { + method = NEAREST; + width = 1; + vld_thresh = 0.5; + shape = SQUARE; + } + + time_interp_method = DW_MEAN; + day_interval = 31; + hour_interval = 6; +} + +climo_stdev = climo_mean; +climo_stdev = { + file_name = []; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Ensemble product output types +// May be set separately in each "ens.field" entry +// +ensemble_flag = { + latlon = TRUE; + mean = TRUE; + stdev = TRUE; + minus = FALSE; + plus = FALSE; + min = FALSE; + max = FALSE; + range = FALSE; + vld_count = FALSE; + frequency = TRUE; + nep = FALSE; + nmep = FALSE; + climo = FALSE; + climo_cdp = FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// + +version = "V10.1.0"; + +//////////////////////////////////////////////////////////////////////////////// diff --git a/met/scripts/examples/test_gen_ens_prod.sh b/met/scripts/examples/test_gen_ens_prod.sh new file mode 100755 index 0000000000..c30d3cd273 --- /dev/null +++ b/met/scripts/examples/test_gen_ens_prod.sh @@ -0,0 +1,8 @@ +#/bin/sh + +echo +echo "*** Running Gen-Ens-Prod using GRIB forecasts ***" +gen_ens_prod \ + -ens ../data/sample_fcst/2009123112/*gep*/d01_2009123112_02400.grib \ + -config config/GenEnsProdConfig \ + -out ${TEST_OUT_DIR}/gen_ens_prod/gen_ens_prod_20100101_120000V_ens.nc -v 2 diff --git a/met/scripts/mk/gen_ens_prod.mk b/met/scripts/mk/gen_ens_prod.mk new file mode 100644 index 0000000000..7c9f0f448f --- /dev/null +++ b/met/scripts/mk/gen_ens_prod.mk @@ -0,0 +1,33 @@ + + + +######################################################################## + + +GEN_ENS_PROD_EXEC = ${OTHER_DIR}/gen_ens_prod/gen_ens_prod + + +######################################################################## + + + ## + ## gen_ens_prod + ## + ## prerequisites: + ## + + +gen_ens_prod: ${GEN_ENS_PROD_EXEC} + @ echo + @ echo "*** Running Gen-Ens-Prod using GRIB forecasts ***" + ${GEN_ENS_PROD_EXEC} \ + -ens ../data/sample_fcst/2009123112/*gep*/d01_2009123112_02400.grib \ + -config config/GenEnsProdConfig \ + -out ${TEST_OUT_DIR}/gen_ens_prod/gen_ens_prod_20100101_120000V_ens.nc -v 2 + @ + @ touch gen_ens_prod + + +######################################################################## + + diff --git a/met/src/basic/vx_util/data_plane_util.cc b/met/src/basic/vx_util/data_plane_util.cc index 38b5469c5a..3f5b5f7278 100644 --- a/met/src/basic/vx_util/data_plane_util.cc +++ b/met/src/basic/vx_util/data_plane_util.cc @@ -203,20 +203,50 @@ DataPlane smooth_field(const DataPlane &dp, void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, int width, const GridTemplateFactory::GridTemplates shape, - bool wrap_lon, SingleThresh t, double vld_t) { + bool wrap_lon, SingleThresh t, + const DataPlane *cmn, const DataPlane *csd, double vld_t) { GridPoint *gp = NULL; int x, y; int n_vld = 0; int n_thr = 0; double v; + double bad = bad_data_double; + bool use_climo = false; // Check that width is set to 1 or greater if(width < 1) { mlog << Error << "\nfractional_coverage() -> " - << "Grid must have at least one point in it. \n\n"; + << "grid must have at least one point in it. \n\n"; exit(1); } + // Check climatology data, if needed + if(cmn && csd) { + if(!cmn->is_empty() && !csd->is_empty()) use_climo = true; + } + + // Check climatology dimensions + if(use_climo) { + + // Check dimensions + if(cmn->nx() != dp.nx() || cmn->ny() != dp.ny()) { + mlog << Error << "\nfractional_coverage() -> " + << "climatology mean dimension (" + << cmn->nx() << ", " << cmn->ny() + << ") does not match the data dimenion (" + << dp.nx() << ", " << dp.ny() << ")!\n\n"; + exit(1); + } + if(csd->nx() != dp.nx() || csd->ny() != dp.ny()) { + mlog << Error << "\nfractional_coverage() -> " + << "climatology standard deviation dimension (" + << csd->nx() << ", " << csd->ny() + << ") does not match the data dimenion (" + << dp.nx() << ", " << dp.ny() << ")!\n\n"; + exit(1); + } + } + // Build the grid template GridTemplateFactory gtf; GridTemplate* gt = gtf.buildGT(shape, width, wrap_lon); @@ -247,7 +277,9 @@ void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, gp = gt->getNextInGrid()) { if(is_bad_data(v = dp.get(gp->x, gp->y))) continue; n_vld++; - if(t.check(v)) n_thr++; + if(t.check(v, + (use_climo ? cmn->get(gp->x, gp->y) : bad), + (use_climo ? csd->get(gp->x, gp->y) : bad))) n_thr++; } } // Subtract off the bottom edge, shift up, and add the top. @@ -259,7 +291,9 @@ void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, gp = gt->getNextInBotEdge()) { if(is_bad_data(v = dp.get(gp->x, gp->y))) continue; n_vld--; - if(t.check(v)) n_thr--; + if(t.check(v, + (use_climo ? cmn->get(gp->x, gp->y) : bad), + (use_climo ? csd->get(gp->x, gp->y) : bad))) n_thr--; } // Increment Y @@ -271,7 +305,9 @@ void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, gp = gt->getNextInTopEdge()) { if(is_bad_data(v = dp.get(gp->x, gp->y))) continue; n_vld++; - if(t.check(v)) n_thr++; + if(t.check(v, + (use_climo ? cmn->get(gp->x, gp->y) : bad), + (use_climo ? csd->get(gp->x, gp->y) : bad))) n_thr++; } } diff --git a/met/src/basic/vx_util/data_plane_util.h b/met/src/basic/vx_util/data_plane_util.h index 2531cb80f1..c5b8a4b65a 100644 --- a/met/src/basic/vx_util/data_plane_util.h +++ b/met/src/basic/vx_util/data_plane_util.h @@ -52,7 +52,8 @@ extern DataPlane smooth_field(const DataPlane &dp, extern void fractional_coverage(const DataPlane &dp, DataPlane &frac_dp, int width, const GridTemplateFactory::GridTemplates shape, - bool wrap_lon, SingleThresh t, double vld_t); + bool wrap_lon, SingleThresh t, + const DataPlane *cmn, const DataPlane *csd, double vld_t); extern void apply_mask(const DataPlane &, const MaskPlane &, NumArray &); extern void apply_mask(DataPlane &, const MaskPlane &); diff --git a/met/src/basic/vx_util/num_array.cc b/met/src/basic/vx_util/num_array.cc index 3a6b10e5a1..a792109883 100644 --- a/met/src/basic/vx_util/num_array.cc +++ b/met/src/basic/vx_util/num_array.cc @@ -570,6 +570,28 @@ return; //////////////////////////////////////////////////////////////////////// +void NumArray::set_const(double v, int n) + +{ + +erase(); + +add_const(v, n); + + // + // a constant array is sorted + // + +Sorted = true; + +return; + +} + + +//////////////////////////////////////////////////////////////////////// + + void NumArray::sort_array() { diff --git a/met/src/basic/vx_util/num_array.h b/met/src/basic/vx_util/num_array.h index eaea0f7990..d1446bb114 100644 --- a/met/src/basic/vx_util/num_array.h +++ b/met/src/basic/vx_util/num_array.h @@ -83,6 +83,7 @@ class NumArray { void set(double); void set(int, int); void set(int, double); + void set_const(double, int); // Increment value void inc(int, int); diff --git a/met/src/tools/core/ensemble_stat/ensemble_stat.cc b/met/src/tools/core/ensemble_stat/ensemble_stat.cc index 26a8a5dfb3..6dfc98dee3 100644 --- a/met/src/tools/core/ensemble_stat/ensemble_stat.cc +++ b/met/src/tools/core/ensemble_stat/ensemble_stat.cc @@ -2061,6 +2061,7 @@ void track_counts(int i_vx, const DataPlane &dp) { conf_info.nbrhd_prob.width[j], conf_info.nbrhd_prob.shape, grid.wrap_lon(), ThreshBuf[i], + (const DataPlane *) 0, (const DataPlane *) 0, conf_info.nbrhd_prob.vld_thresh); // Increment counts diff --git a/met/src/tools/core/grid_stat/grid_stat.cc b/met/src/tools/core/grid_stat/grid_stat.cc index 8871324f3c..380df877b5 100644 --- a/met/src/tools/core/grid_stat/grid_stat.cc +++ b/met/src/tools/core/grid_stat/grid_stat.cc @@ -1394,6 +1394,7 @@ void process_scores() { nbrhd->width[j], nbrhd->shape, grid.wrap_lon(), conf_info.vx_opt[i].fcat_ta[k], + &cmn_dp, &csd_dp, nbrhd->vld_thresh); // Compute the binary threshold field @@ -1433,6 +1434,7 @@ void process_scores() { nbrhd->width[j], nbrhd->shape, grid.wrap_lon(), conf_info.vx_opt[i].ocat_ta[k], + &cmn_dp, &csd_dp, nbrhd->vld_thresh); // Compute the binary threshold field diff --git a/met/src/tools/other/Makefile.am b/met/src/tools/other/Makefile.am index 3f56c89a18..47c8da59ce 100644 --- a/met/src/tools/other/Makefile.am +++ b/met/src/tools/other/Makefile.am @@ -22,6 +22,10 @@ if ENABLE_GIS_UTILS SUBDIRS += gis_utils endif +if ENABLE_GEN_ENS_PROD + SUBDIRS += gen_ens_prod +endif + if ENABLE_GEN_VX_MASK SUBDIRS += gen_vx_mask endif diff --git a/met/src/tools/other/gen_ens_prod/.gitignore b/met/src/tools/other/gen_ens_prod/.gitignore new file mode 100644 index 0000000000..3ad703829a --- /dev/null +++ b/met/src/tools/other/gen_ens_prod/.gitignore @@ -0,0 +1,7 @@ +gen_ens_prod +*.o +*.a +.deps +Makefile +Makefile.in +*.dSYM diff --git a/met/src/tools/other/gen_ens_prod/Makefile.am b/met/src/tools/other/gen_ens_prod/Makefile.am new file mode 100644 index 0000000000..5c1191f123 --- /dev/null +++ b/met/src/tools/other/gen_ens_prod/Makefile.am @@ -0,0 +1,44 @@ +## @start 1 +## Makefile.am -- Process this file with automake to produce Makefile.in +## @end 1 + +MAINTAINERCLEANFILES = Makefile.in + +# Include the project definitions + +include ${top_srcdir}/Make-include + +# The program + +bin_PROGRAMS = gen_ens_prod +gen_ens_prod_SOURCES = gen_ens_prod.cc \ + gen_ens_prod_conf_info.cc +gen_ens_prod_CPPFLAGS = ${MET_CPPFLAGS} +gen_ens_prod_LDFLAGS = ${MET_LDFLAGS} +gen_ens_prod_LDADD = -lvx_stat_out \ + -lvx_statistics \ + -lvx_shapedata \ + -lvx_gsl_prob \ + -lvx_analysis_util \ + -lvx_data2d_factory \ + -lvx_data2d_nc_met \ + -lvx_data2d_grib $(GRIB2_LIBS) \ + -lvx_data2d_nc_pinterp \ + -lvx_data2d_nccf \ + $(PYTHON_LIBS) \ + -lvx_data2d \ + -lvx_nc_obs \ + -lvx_nc_util \ + -lvx_regrid \ + -lvx_grid \ + -lvx_config \ + -lvx_color \ + -lvx_util \ + -lvx_math \ + -lvx_cal \ + -lvx_log \ + $(PYTHON_LIBS) \ + -lm -lnetcdf_c++4 -lnetcdf -lgsl -lgslcblas + +EXTRA_DIST = gen_ens_prod.h \ + gen_ens_prod_conf_info.h diff --git a/met/src/tools/other/gen_ens_prod/gen_ens_prod.cc b/met/src/tools/other/gen_ens_prod/gen_ens_prod.cc new file mode 100644 index 0000000000..ce956467af --- /dev/null +++ b/met/src/tools/other/gen_ens_prod/gen_ens_prod.cc @@ -0,0 +1,1053 @@ +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 +// ** University Corporation for Atmospheric Research (UCAR) +// ** National Center for Atmospheric Research (NCAR) +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +//////////////////////////////////////////////////////////////////////// +// +// Filename: gen_ens_prod.cc +// +// Description: +// +// Mod# Date Name Description +// ---- ---- ---- ----------- +// 000 09/10/21 Halley Gotway Initial version (MET #1904). +// +//////////////////////////////////////////////////////////////////////// + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gen_ens_prod.h" + +#include "vx_nc_util.h" +#include "vx_data2d_nc_met.h" +#include "vx_regrid.h" +#include "vx_log.h" + +#include "nc_obs_util.h" +#include "nc_point_obs_in.h" + +//////////////////////////////////////////////////////////////////////// + +static void process_command_line(int, char **); +static void process_grid(const Grid &); +static void process_ensemble(); +static bool get_data_plane(const char *, GrdFileType, VarInfo *, DataPlane &); + +static void clear_counts(); +static void track_counts(int, const DataPlane &, bool, + const DataPlane &, const DataPlane &); + +static void setup_nc_file(); +static void write_ens_nc(int, int, const DataPlane &, + const DataPlane &, const DataPlane &); +static void write_ens_var_float(int, float *, const DataPlane &, + const char *, const char *); +static void write_ens_var_int(int, int *, const DataPlane &, + const char *, const char *); +static void write_ens_data_plane(int, const DataPlane &, const DataPlane &, + const char *, const char *); + +static void add_var_att_local(VarInfo *, NcVar *, bool is_int, + const DataPlane &, const char *, const char *); + +static void clean_up(); +static void usage(); + +static void set_ens_files (const StringArray &); +static void set_out_file (const StringArray &); +static void set_config_file(const StringArray &); +static void set_ctrl_file (const StringArray &); + +//////////////////////////////////////////////////////////////////////// + +int main(int argc, char *argv[]) { + + // Set handler to be called for memory allocation error + set_new_handler(oom); + + // Process the command line arguments + process_command_line(argc, argv); + + // Process the ensemble fields + process_ensemble(); + + // Close output files and deallocate memory + clean_up(); + + return(0); +} + +//////////////////////////////////////////////////////////////////////// + +void process_command_line(int argc, char **argv) { + int i; + CommandLine cline; + ConcatString default_config_file; + Met2dDataFile *ens_mtddf = (Met2dDataFile *) 0; + + // + // Check for zero arguments + // + if(argc == 1) usage(); + + // + // Parse the command line into tokens + // + cline.set(argc, argv); + + // + // Set the usage function + // + cline.set_usage(usage); + + // + // Add the options function calls + // + cline.add(set_ens_files, "-ens", -1); + cline.add(set_out_file, "-out", 1); + cline.add(set_config_file, "-config", 1); + cline.add(set_ctrl_file, "-ctrl", 1); + + // + // Parse the command line + // + cline.parse(); + + // Check for error, there should be zero arguments left + if(cline.n() != 0) usage(); + + // Check that the required arguments have been set + n_ens = ens_files.n(); + if(ens_files.n() == 0) { + mlog << Error << "\nprocess_command_line() -> " + << "the ensemble file list must be set using the " + << "\"-ens\" option.\n\n"; + exit(1); + } + if(config_file.length() == 0) { + mlog << Error << "\nprocess_command_line() -> " + << "the output file must be set using the " + << "\"-out\" option.\n\n"; + exit(1); + } + if(config_file.length() == 0) { + mlog << Error << "\nprocess_command_line() -> " + << "the configuration file must be set using the " + << "\"-config\" option.\n\n"; + exit(1); + } + + // Create the default config file name + default_config_file = replace_path(default_config_filename); + + // List the config files + mlog << Debug(1) + << "Default Config File: " << default_config_file << "\n" + << "User Config File: " << config_file << "\n"; + + // Read the config files + conf_info.read_config(default_config_file, config_file); + + // Get the ensemble file type from config, if present + etype = parse_conf_file_type(conf_info.conf.lookup_dictionary(conf_key_ens)); + + // Read the first input ensemble file + if(!(ens_mtddf = mtddf_factory.new_met_2d_data_file(ens_files[0].c_str(), etype))) { + mlog << Error << "\nprocess_command_line() -> " + << "trouble reading ensemble file \"" << ens_files[0] << "\"\n\n"; + exit(1); + } + + // Store the input ensemble file type + etype = ens_mtddf->file_type(); + + // Process the configuration + conf_info.process_config(etype); + + // Allocate arrays to store threshold counts + thresh_cnt_na = new NumArray [conf_info.get_max_n_cat()]; + thresh_nbrhd_cnt_na = new NumArray * [conf_info.get_max_n_cat()]; + + for(i=0; i " + << "cannot open input ensemble file: " + << ens_files[i] << "\n\n"; + ens_file_vld.add(0); + } + else { + ens_file_vld.add(1); + } + } + + // Deallocate memory for data files + if(ens_mtddf) { delete ens_mtddf; ens_mtddf = (Met2dDataFile *) 0; } + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void process_grid(const Grid &fcst_grid) { + Grid obs_grid; + + // Parse regridding logic + RegridInfo ri; + if(conf_info.get_n_var() > 0) { + ri = conf_info.ens_info[0]->regrid(); + } + else { + mlog << Error << "\nprocess_grid() -> " + << "at least one ensemble field must be specified!\n\n"; + exit(1); + } + + // Determine the verification grid + grid = parse_vx_grid(ri, &fcst_grid, &fcst_grid); + nxy = grid.nx() * grid.ny(); + + return; +} + +//////////////////////////////////////////////////////////////////////// + +bool get_data_plane(const char *infile, GrdFileType ftype, + VarInfo *info, DataPlane &dp) { + bool found; + Met2dDataFile *mtddf = (Met2dDataFile *) 0; + + // Read the current ensemble file + if(!(mtddf = mtddf_factory.new_met_2d_data_file(infile, ftype))) { + mlog << Error << "\nget_data_plane() -> " + << "trouble reading file \"" << infile << "\"\n\n"; + exit(1); + } + + // Read the gridded data field + if((found = mtddf->data_plane(*info, dp))) { + + // Setup the verification grid, if necessary + if(nxy == 0) process_grid(mtddf->grid()); + + // Create the output file, if necessary + if(nc_out == (NcFile *) 0) setup_nc_file(); + + // Regrid, if requested and necessary + if(!(mtddf->grid() == grid)) { + mlog << Debug(1) + << "Regridding field \"" << info->magic_str() + << "\" to the verification grid.\n"; + dp = met_regrid(dp, mtddf->grid(), grid, info->regrid()); + } + + // Store the valid time, if not already set + if(ens_valid_ut == (unixtime) 0) { + ens_valid_ut = dp.valid(); + } + // Check to make sure that the valid time doesn't change + else if(ens_valid_ut != dp.valid()) { + mlog << Warning << "\nget_data_plane() -> " + << "The valid time has changed, " + << unix_to_yyyymmdd_hhmmss(ens_valid_ut) + << " != " << unix_to_yyyymmdd_hhmmss(dp.valid()) + << " in \"" << infile << "\"\n\n"; + } + + } // end if found + + // Deallocate the data file pointer, if necessary + if(mtddf) { delete mtddf; mtddf = (Met2dDataFile *) 0; } + + return(found); +} + +//////////////////////////////////////////////////////////////////////// + +void process_ensemble() { + int i_var, i_file, n_ens_vld; + bool need_reset; + DataPlane ens_dp, ctrl_dp, cmn_dp, csd_dp; + unixtime max_init_ut = bad_data_ll; + + // Loop through each of the ensemble fields to be processed + for(i_var=0; i_varmagic_str() << "\n"; + + // Need to reinitialize counts and sums for each ensemble field + need_reset = true; + + // Loop through the ensemble member files + for(i_file=0, n_ens_vld=0; i_file " + << "ensemble field \"" << conf_info.ens_info[i_var]->magic_str() + << "\" not found in file \"" << ens_files[i_file] << "\"\n\n"; + continue; + } + else { + n_ens_vld++; + } + + // Reinitialize for the current variable + if(need_reset) { + + need_reset = false; + + // Reset the running sums and counts + clear_counts(); + + // Read climatology data for this field + cmn_dp = read_climo_data_plane( + conf_info.conf.lookup_array(conf_key_climo_mean_field, false), + i_var, ens_valid_ut, grid); + + csd_dp = read_climo_data_plane( + conf_info.conf.lookup_array(conf_key_climo_stdev_field, false), + i_var, ens_valid_ut, grid); + + // Read ensemble control member data, if provided + if(ctrl_file.nonempty()) { + + // Error out if missing + if(!get_data_plane(ctrl_file.c_str(), etype, + conf_info.ens_info[i_var], ctrl_dp)) { + mlog << Error << "\nprocess_ensemble() -> " + << "control member ensemble field \"" + << conf_info.ens_info[i_var]->magic_str() + << "\" not found in file \"" << ctrl_file << "\"\n\n"; + exit(1); + } + + // Apply current data to the running sums and counts + track_counts(i_var, ctrl_dp, true, cmn_dp, csd_dp); + + } // end if ctrl_file + + mlog << Debug(3) + << "Found " << (ctrl_dp.is_empty() ? 0 : 1) + << " control member, " << (cmn_dp.is_empty() ? 0 : 1) + << " climatology mean, and " << (csd_dp.is_empty() ? 0 : 1) + << " climatology standard deviation field(s) for \"" + << conf_info.ens_info[i_var]->magic_str() << "\".\n"; + + } // end if need_reset + + // Apply current data to the running sums and counts + track_counts(i_var, ens_dp, false, cmn_dp, csd_dp); + + // Keep track of the maximum initialization time + if(is_bad_data(max_init_ut) || ens_dp.init() > max_init_ut) { + max_init_ut = ens_dp.init(); + } + + } // end for i_file + + // Check for too much missing data + if(((double) n_ens_vld/n_ens) < conf_info.vld_ens_thresh) { + mlog << Error << "\nprocess_ensemble() -> " + << n_ens - n_ens_vld << " of " << n_ens + << " missing fields for \"" << conf_info.ens_info[i_var]->magic_str() + << "\" exceeds the maximum allowable specified by \"" + << conf_key_ens_ens_thresh << "\" (" << conf_info.vld_ens_thresh + << ") in the configuration file.\n\n"; + exit(1); + } + + // Write out the ensemble information to a NetCDF file + ens_dp.set_init(max_init_ut); + write_ens_nc(i_var, n_ens_vld, ens_dp, cmn_dp, csd_dp); + + } // end for i_var + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void clear_counts() { + int i, j; + + cnt_na.set_const(0.0, nxy); + min_na.set_const(bad_data_double, nxy); + max_na.set_const(bad_data_double, nxy); + sum_na.set_const(0.0, nxy); + + stdev_cnt_na.set_const(0.0, nxy); + stdev_sum_na.set_const(0.0, nxy); + stdev_ssq_na.set_const(0.0, nxy); + + for(i=0; i= max_na.buf()[i] || is_bad_data(max_na.buf()[i])) max_na.buf()[i] = ens; + + // Standard deviation sum, sum of squares, and count, excluding control member + if(!is_ctrl) { + stdev_sum_na.buf()[i] += ens; + stdev_ssq_na.buf()[i] += ens*ens; + stdev_cnt_na.buf()[i] += 1; + } + + // Event frequency + for(j=0; j 0 + if(conf_info.nc_info[i_var].do_nmep) { + DataPlane frac_dp; + + // Loop over thresholds + for(i=0; i 0) thresh_nbrhd_cnt_na[i][j].inc(k, 1); + } // end for k + + } // end for j + } // end for i + } // end if do_nmep + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void setup_nc_file() { + + // Create a new NetCDF file and open it + nc_out = open_ncfile(out_file.c_str(), true); + + if(IS_INVALID_NC_P(nc_out)) { + mlog << Error << "\nsetup_nc_file() -> " + << "trouble opening output NetCDF file " + << out_file << "\n\n"; + exit(1); + } + + // Add global attributes + write_netcdf_global(nc_out, out_file.text(), program_name, + conf_info.model.c_str()); + + // Add the projection information + write_netcdf_proj(nc_out, grid); + + // Define Dimensions + lat_dim = add_dim(nc_out, "lat", (long) grid.ny()); + lon_dim = add_dim(nc_out, "lon", (long) grid.nx()); + + // Add the lat/lon variables + if(conf_info.nc_info[0].do_latlon) { + write_netcdf_latlon(nc_out, &lat_dim, &lon_dim, grid); + } + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void write_ens_nc(int i_var, int n_ens_vld, + const DataPlane &ens_dp, + const DataPlane &cmn_dp, + const DataPlane &csd_dp) { + int i, j, k; + double t; + char type_str[max_str_len]; + DataPlane prob_dp, nbrhd_dp; + + // Allocate memory for storing ensemble data + float *ens_mean = new float [nxy]; + float *ens_stdev = new float [nxy]; + float *ens_minus = new float [nxy]; + float *ens_plus = new float [nxy]; + float *ens_min = new float [nxy]; + float *ens_max = new float [nxy]; + float *ens_range = new float [nxy]; + int *ens_vld = new int [nxy]; + + // Store the threshold for the ratio of valid data points + t = conf_info.vld_data_thresh; + + // Store the data + for(i=0; i simp; + conf_info.cat_ta[i_var].get_simple_nodes(simp); + + // Process all CDP thresholds except 0 and 100 + for(vector::iterator it = simp.begin(); + it != simp.end(); it++) { + if(it->ptype() == perc_thresh_climo_dist && + !is_eq(it->pvalue(), 0.0) && + !is_eq(it->pvalue(), 100.0)) { + snprintf(type_str, sizeof(type_str), "CLIMO_CDP%i", + nint(it->pvalue())); + cdp_dp = normal_cdf_inv(it->pvalue()/100.0, cmn_dp, csd_dp); + write_ens_data_plane(i_var, cdp_dp, ens_dp, + type_str, + "Climatology distribution percentile"); + } + } // end for it + } + + // Deallocate and clean up + if(ens_mean) { delete [] ens_mean; ens_mean = (float *) 0; } + if(ens_stdev) { delete [] ens_stdev; ens_stdev = (float *) 0; } + if(ens_minus) { delete [] ens_minus; ens_minus = (float *) 0; } + if(ens_plus) { delete [] ens_plus; ens_plus = (float *) 0; } + if(ens_min) { delete [] ens_min; ens_min = (float *) 0; } + if(ens_max) { delete [] ens_max; ens_max = (float *) 0; } + if(ens_range) { delete [] ens_range; ens_range = (float *) 0; } + if(ens_vld) { delete [] ens_vld; ens_vld = (int *) 0; } + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void write_ens_var_float(int i_var, float *ens_data, const DataPlane &dp, + const char *type_str, + const char *long_name_str) { + NcVar ens_var; + ConcatString ens_var_name, var_str, name_str, cs; + + // Append nc_var_str config file entry + cs = conf_info.nc_var_str[i_var]; + if(cs.length() > 0) var_str << "_" << cs; + + // Construct the variable name + ens_var_name << cs_erase + << conf_info.ens_info[i_var]->name_attr() << "_" + << conf_info.ens_info[i_var]->level_attr() + << var_str << "_" << type_str; + + // Skip variable names that have already been written + if(nc_ens_var_sa.has(ens_var_name)) return; + + // Otherwise, add to the list of previously defined variables + nc_ens_var_sa.add(ens_var_name); + + ens_var = add_var(nc_out, (string)ens_var_name, ncFloat, lat_dim, lon_dim); + + // + // Construct the variable name attribute + // For the ensemble mean, just use the variable name. + // For all other fields, append the field type. + // + if(strcmp(type_str, "ENS_MEAN") == 0) { + name_str << cs_erase + << conf_info.ens_info[i_var]->name_attr(); + } + else { + name_str << cs_erase + << conf_info.ens_info[i_var]->name_attr() << "_" + << type_str; + } + + // Add the variable attributes + add_var_att_local(conf_info.ens_info[i_var], &ens_var, false, dp, + name_str.c_str(), long_name_str); + + // Write the data + if(!put_nc_data_with_dims(&ens_var, &ens_data[0], grid.ny(), grid.nx())) { + mlog << Error << "\nwrite_ens_var_float() -> " + << "error in ens_var->put for the " << ens_var_name + << " field.\n\n"; + exit(1); + } + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void write_ens_var_int(int i_var, int *ens_data, const DataPlane &dp, + const char *type_str, + const char *long_name_str) { + NcVar ens_var; + ConcatString ens_var_name, var_str, name_str, cs; + + // Append nc_var_str config file entry + cs = conf_info.nc_var_str[i_var]; + if(cs.length() > 0) var_str << "_" << cs; + + // Construct the variable name + ens_var_name << cs_erase + << conf_info.ens_info[i_var]->name_attr() << "_" + << conf_info.ens_info[i_var]->level_attr() + << var_str << "_" << type_str; + + // Skip variable names that have already been written + if(nc_ens_var_sa.has(ens_var_name)) return; + + // Otherwise, add to the list of previously defined variables + nc_ens_var_sa.add(ens_var_name); + + int deflate_level = conf_info.get_compression_level(); + ens_var = add_var(nc_out, (string)ens_var_name, ncInt, lat_dim, lon_dim, deflate_level); + + // Construct the variable name attribute + name_str << cs_erase + << conf_info.ens_info[i_var]->name_attr() << "_" + << type_str; + + // Add the variable attributes + add_var_att_local(conf_info.ens_info[i_var], &ens_var, true, dp, + name_str.c_str(), long_name_str); + + // Write the data + if(!put_nc_data_with_dims(&ens_var, &ens_data[0], grid.ny(), grid.nx())) { + mlog << Error << "\nwrite_ens_var_int() -> " + << "error in ens_var->put for the " << ens_var_name + << " field.\n\n"; + exit(1); + } + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void write_ens_data_plane(int i_var, const DataPlane &ens_dp, const DataPlane &dp, + const char *type_str, + const char *long_name_str) { + + // Allocate memory for this data + float *ens_data = new float [nxy]; + + // Store the data in an array of floats + for(int i=0; iname_attr() << " at " + << info->level_attr() << " " + << long_name_str; + + // Add variable attributes + add_att(nc_var, "name", name_str); + add_att(nc_var, "long_name", (string)att_str); + add_att(nc_var, "level", (string)info->level_attr()); + add_att(nc_var, "units", (string)info->units_attr()); + + if(is_int) add_att(nc_var, "_FillValue", bad_data_int); + else add_att(nc_var, "_FillValue", bad_data_float); + + // Write out times + write_netcdf_var_times(nc_var, dp); + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void clean_up() { + int i, j; + + mlog << Debug(2) << "\n" << sep_str << "\n\n"; + + // List the output NetCDF file + mlog << Debug(1) << "Output file: " << out_file << "\n"; + + // Close the output NetCDF file + if(nc_out) { delete nc_out; nc_out = (NcFile *) 0; } + + // Deallocate threshold count arrays + if(thresh_cnt_na) { + for(i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +using namespace netCDF; + +#include "gen_ens_prod_conf_info.h" + +#include "vx_data2d_factory.h" +#include "vx_grid.h" +#include "vx_util.h" +#include "vx_stat_out.h" + +//////////////////////////////////////////////////////////////////////// +// +// Constants +// +//////////////////////////////////////////////////////////////////////// + +static const char * program_name = "gen_ens_prod"; + +// Default configuration file name +static const char * default_config_filename = + "MET_BASE/config/GenEnsProdConfig_default"; + +//////////////////////////////////////////////////////////////////////// +// +// Variables for Command Line Arguments +// +//////////////////////////////////////////////////////////////////////// + +static StringArray ens_files; +static IntArray ens_file_vld; +static GrdFileType etype = FileType_None; +static int n_ens; +static GenEnsProdConfInfo conf_info; +static ConcatString config_file; +static ConcatString out_file; +static ConcatString ctrl_file; +static unixtime ens_valid_ut = (unixtime) 0; + +//////////////////////////////////////////////////////////////////////// +// +// Variables for Output Files +// +//////////////////////////////////////////////////////////////////////// + +// Output NetCDF file +static NcFile *nc_out = (NcFile *) 0; +static NcDim lat_dim; +static NcDim lon_dim; + +// List of output NetCDF variable names +static StringArray nc_ens_var_sa; + +//////////////////////////////////////////////////////////////////////// +// +// Miscellaneous Variables +// +//////////////////////////////////////////////////////////////////////// + +// Grid variables +static Grid grid; +static int nxy = 0; + +// Data file factory and input files +static Met2dDataFileFactory mtddf_factory; + +// Arrays to store running sums and counts +static NumArray cnt_na, min_na, max_na, sum_na; +static NumArray stdev_cnt_na, stdev_sum_na, stdev_ssq_na; +static NumArray *thresh_cnt_na = (NumArray *) 0; // [n_thresh] +static NumArray **thresh_nbrhd_cnt_na = (NumArray **) 0; // [n_thresh][n_nbrhd] + +//////////////////////////////////////////////////////////////////////// + +#endif // __GEN_ENS_PROD_H__ + +//////////////////////////////////////////////////////////////////////// diff --git a/met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.cc b/met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.cc new file mode 100644 index 0000000000..ef7a274802 --- /dev/null +++ b/met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.cc @@ -0,0 +1,362 @@ +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 +// ** University Corporation for Atmospheric Research (UCAR) +// ** National Center for Atmospheric Research (NCAR) +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +//////////////////////////////////////////////////////////////////////// + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include + +#include "gen_ens_prod_conf_info.h" +#include "configobjecttype_to_string.h" + +#include "vx_data2d_factory.h" +#include "vx_data2d.h" +#include "vx_log.h" + +#include "GridTemplate.h" + +//////////////////////////////////////////////////////////////////////// +// +// Code for class GenEnsProdConfInfo +// +//////////////////////////////////////////////////////////////////////// + +GenEnsProdConfInfo::GenEnsProdConfInfo() { + init_from_scratch(); +} + +//////////////////////////////////////////////////////////////////////// + +GenEnsProdConfInfo::~GenEnsProdConfInfo() { + clear(); +} + +//////////////////////////////////////////////////////////////////////// + +void GenEnsProdConfInfo::init_from_scratch() { + + clear(); + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void GenEnsProdConfInfo::clear() { + vector::const_iterator it; + + // Clear, erase, and initialize members + model.clear(); + desc.clear(); + for(it = ens_info.begin(); it != ens_info.end(); it++) { + if(*it) { delete *it; } + } + ens_info.clear(); + cdf_info.clear(); + cat_ta.clear(); + nc_var_str.clear(); + nbrhd_prob.clear(); + nmep_smooth.clear(); + vld_ens_thresh = bad_data_double; + vld_data_thresh = bad_data_double; + nc_info.clear(); + version.clear(); + + // Reset counts + n_var = 0; + max_n_cat = 0; + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void GenEnsProdConfInfo::read_config(const ConcatString default_file_name, + const ConcatString user_file_name) { + + // Read the config file constants + conf.read(replace_path(config_const_filename).c_str()); + + // Read the default config file + conf.read(default_file_name.c_str()); + + // Read the user-specified config file + conf.read(user_file_name.c_str()); + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void GenEnsProdConfInfo::process_config(GrdFileType etype) { + int i; + VarInfoFactory info_factory; + Dictionary *edict = (Dictionary *) 0; + Dictionary i_edict; + InterpMthd mthd; + + // Dump the contents of the config file + if(mlog.verbosity_level() >= 5) conf.dump(cout); + + // Initialize + clear(); + + // Conf: version + version = parse_conf_version(&conf); + + // Conf: model + model = parse_conf_string(&conf, conf_key_model); + + // Conf: desc + desc = parse_conf_string(&conf, conf_key_desc); + + // Conf: ens.field + edict = conf.lookup_array(conf_key_ens_field); + + // Determine the number of ensemble fields to be processed + if((n_var = parse_conf_n_vx(edict)) == 0) { + mlog << Error << "\nGenEnsProdConfInfo::process_config() -> " + << "At least one field must be specified in the \"" + << conf_key_ens_field << "\" dictionary!\n\n"; + exit(1); + } + + // Parse the ensemble field information + for(i=0,max_n_cat=0; iset_dict(i_edict); + + // Dump the contents of the current VarInfo + if(mlog.verbosity_level() >= 5) { + mlog << Debug(5) + << "Parsed ensemble field number " << i+1 << ":\n"; + ens_info[i]->dump(cout); + } + + // Conf: nc_var_str + nc_var_str.add(parse_conf_string(&i_edict, conf_key_nc_var_str, false)); + + // Conf: cat_thresh + cat_ta.push_back(i_edict.lookup_thresh_array(conf_key_cat_thresh)); + + // Dump the contents of the current thresholds + if(mlog.verbosity_level() >= 5) { + mlog << Debug(5) + << "Parsed thresholds for ensemble field number " << i+1 << ":\n"; + cat_ta[i].dump(cout); + } + + // Keep track of the maximum number of thresholds + if(cat_ta[i].n() > max_n_cat) max_n_cat = cat_ta[i].n(); + + // Conf: ensemble_flag + nc_info.push_back(parse_nc_info(&i_edict)); + } + + // Conf: ens.ens_thresh + vld_ens_thresh = conf.lookup_double(conf_key_ens_ens_thresh); + + // Check that the valid ensemble threshold is between 0 and 1. + if(vld_ens_thresh < 0.0 || vld_ens_thresh > 1.0) { + mlog << Error << "\nGenEnsProdConfInfo::process_config() -> " + << "The \"" << conf_key_ens_ens_thresh << "\" parameter (" + << vld_ens_thresh << ") must be set between 0 and 1.\n\n"; + exit(1); + } + + // Conf: ens.vld_thresh + vld_data_thresh = conf.lookup_double(conf_key_ens_vld_thresh); + + // Check that the valid data threshold is between 0 and 1. + if(vld_data_thresh < 0.0 || vld_data_thresh > 1.0) { + mlog << Error << "\nGenEnsProdConfInfo::process_config() -> " + << "The \"" << conf_key_ens_vld_thresh << "\" parameter (" + << vld_data_thresh << ") must be set between 0 and 1.\n\n"; + exit(1); + } + + // Conf: nbrhd_prob + nbrhd_prob = parse_conf_nbrhd(edict, conf_key_nbrhd_prob); + + // Conf: nmep_smooth + nmep_smooth = parse_conf_interp(edict, conf_key_nmep_smooth); + + // Loop through the neighborhood probability smoothing options + for(i=0; i " + << "Neighborhood probability smoothing methods DW_MEAN, " + << "LS_FIT, and BILIN are not supported for \"" + << conf_key_nmep_smooth << "\".\n\n"; + exit(1); + } + + // Check for valid neighborhood probability interpolation widths + if(nmep_smooth.width[i] < 1 || nmep_smooth.width[i]%2 == 0) { + mlog << Error << "\nGenEnsProdConfInfo::process_config() -> " + << "Neighborhood probability smoothing widths must be set " + << "to odd values greater than or equal to 1 (" + << nmep_smooth.width[i] << ") for \"" + << conf_key_nmep_smooth << "\".\n\n"; + exit(1); + } + } // end for i + + return; +} + +//////////////////////////////////////////////////////////////////////// + +GenEnsProdNcOutInfo GenEnsProdConfInfo::parse_nc_info(Dictionary *dict) { + GenEnsProdNcOutInfo cur; + + // Parse the ensemble flag + const DictionaryEntry *e = dict->lookup(conf_key_ensemble_flag); + + if(!e) { + mlog << Error + << "\nGenEnsProdConfInfo::parse_nc_info() -> " + << "lookup failed for key \"" << conf_key_ensemble_flag + << "\"\n\n"; + exit(1); + } + + // Boolean type + if(e->type() == BooleanType) { + if(e->b_value()) cur.set_all_true(); + else cur.set_all_false(); + } + // Dictionary type + else { + + // Check the type + if(e->type() != DictionaryType) { + mlog << Error << "\nGenEnsProdConfInfo::parse_nc_info() -> " + << "bad type (" << configobjecttype_to_string(e->type()) + << ") for key \"" << conf_key_ensemble_flag << "\"\n\n"; + exit(1); + } + + // Parse the various entries + Dictionary * d = e->dict_value(); + + cur.do_latlon = d->lookup_bool(conf_key_latlon_flag); + cur.do_mean = d->lookup_bool(conf_key_mean_flag); + cur.do_stdev = d->lookup_bool(conf_key_stdev_flag); + cur.do_minus = d->lookup_bool(conf_key_minus_flag); + cur.do_plus = d->lookup_bool(conf_key_plus_flag); + cur.do_min = d->lookup_bool(conf_key_min_flag); + cur.do_max = d->lookup_bool(conf_key_max_flag); + cur.do_range = d->lookup_bool(conf_key_range_flag); + cur.do_vld = d->lookup_bool(conf_key_vld_count_flag); + cur.do_freq = d->lookup_bool(conf_key_frequency_flag); + cur.do_nep = d->lookup_bool(conf_key_nep_flag); + cur.do_nmep = d->lookup_bool(conf_key_nmep_flag); + cur.do_climo = d->lookup_bool(conf_key_climo_flag); + cur.do_climo_cdp = d->lookup_bool(conf_key_climo_cdp_flag); + } + + return(cur); +} + +//////////////////////////////////////////////////////////////////////// +// +// Code for struct GenEnsProdNcOutInfo +// +//////////////////////////////////////////////////////////////////////// + +GenEnsProdNcOutInfo::GenEnsProdNcOutInfo() { + clear(); +} + +//////////////////////////////////////////////////////////////////////// + +void GenEnsProdNcOutInfo::clear() { + + set_all_true(); + + return; +} + +//////////////////////////////////////////////////////////////////////// + +bool GenEnsProdNcOutInfo::all_false() const { + + bool status = do_latlon || do_mean || do_stdev || do_minus || + do_plus || do_min || do_max || do_range || + do_vld || do_freq || do_nep || do_nmep || + do_climo || do_climo_cdp; + + return(!status); +} + +//////////////////////////////////////////////////////////////////////// + +void GenEnsProdNcOutInfo::set_all_false() { + + do_latlon = false; + do_mean = false; + do_stdev = false; + do_minus = false; + do_plus = false; + do_min = false; + do_max = false; + do_range = false; + do_vld = false; + do_freq = false; + do_nep = false; + do_nmep = false; + do_climo = false; + do_climo_cdp = false; + + return; +} + +//////////////////////////////////////////////////////////////////////// + +void GenEnsProdNcOutInfo::set_all_true() { + + do_latlon = true; + do_mean = true; + do_stdev = true; + do_minus = true; + do_plus = true; + do_min = true; + do_max = true; + do_range = true; + do_vld = true; + do_freq = true; + do_nep = true; + do_nmep = true; + do_climo = true; + do_climo_cdp = true; + + return; +} + +//////////////////////////////////////////////////////////////////////// diff --git a/met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.h b/met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.h new file mode 100644 index 0000000000..2ec6014824 --- /dev/null +++ b/met/src/tools/other/gen_ens_prod/gen_ens_prod_conf_info.h @@ -0,0 +1,121 @@ +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* +// ** Copyright UCAR (c) 1992 - 2021 +// ** University Corporation for Atmospheric Research (UCAR) +// ** National Center for Atmospheric Research (NCAR) +// ** Research Applications Lab (RAL) +// ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + +//////////////////////////////////////////////////////////////////////// + +#ifndef __GEN_ENS_PROD_CONF_INFO_H__ +#define __GEN_ENS_PROD_CONF_INFO_H__ + +//////////////////////////////////////////////////////////////////////// + +#include + +#include "vx_config.h" +#include "vx_data2d.h" +#include "vx_grid.h" +#include "vx_util.h" +#include "vx_cal.h" +#include "vx_math.h" + +//////////////////////////////////////////////////////////////////////// + +struct GenEnsProdNcOutInfo { + + bool do_latlon; + bool do_mean; + bool do_stdev; + bool do_minus; + bool do_plus; + bool do_min; + bool do_max; + bool do_range; + bool do_vld; + bool do_freq; + bool do_nep; + bool do_nmep; + bool do_climo; + bool do_climo_cdp; + + GenEnsProdNcOutInfo(); + + void clear(); + + bool all_false() const; + + void set_all_false(); + void set_all_true(); +}; + +//////////////////////////////////////////////////////////////////////// + +class GenEnsProdConfInfo { + + private: + + void init_from_scratch(); + + // Ensemble processing + int n_var; // Number of ensemble fields to be processed + int max_n_cat; // Maximum number of ensemble thresholds + + public: + + GenEnsProdConfInfo(); + ~GenEnsProdConfInfo(); + + ////////////////////////////////////////////////////////////////// + + // Gen-Ens-Prod configuration object + MetConfig conf; + + // Data parsed from the Gen-Ens-Prod configuration object + ConcatString model; // Model name + ConcatString desc; // Description + + vector ens_info; // Array of VarInfo pointers (allocated) + vector cdf_info; // Array of climo CDF info objects + vector cat_ta; // Array for ensemble categorical thresholds + StringArray nc_var_str; // Array of ensemble variable name strings + vector nc_info; // Array of ensemble product outputs + + NbrhdInfo nbrhd_prob; // Neighborhood probability definition + InterpInfo nmep_smooth; // Neighborhood maximum smoothing information + + double vld_ens_thresh; // Required ratio of valid input files + double vld_data_thresh; // Required ratio of valid data for each point + + ConcatString version; // Config file version + + ////////////////////////////////////////////////////////////////// + + void clear(); + + void read_config (const ConcatString, const ConcatString); + void process_config(GrdFileType); + + GenEnsProdNcOutInfo parse_nc_info(Dictionary *); + + // Accessor functions + int get_n_var() const; + int get_max_n_cat() const; + int get_n_nbrhd() const; + int get_compression_level(); +}; + +//////////////////////////////////////////////////////////////////////// + +inline int GenEnsProdConfInfo::get_n_var() const { return(n_var); } +inline int GenEnsProdConfInfo::get_max_n_cat() const { return(max_n_cat); } +inline int GenEnsProdConfInfo::get_n_nbrhd() const { return(nbrhd_prob.width.n()); } +inline int GenEnsProdConfInfo::get_compression_level() { return(conf.nc_compression()); } + +//////////////////////////////////////////////////////////////////////// + +#endif /* __GEN_ENS_PROD_CONF_INFO_H__ */ + +//////////////////////////////////////////////////////////////////////// diff --git a/test/bin/unit_test.sh b/test/bin/unit_test.sh index 2b4bfac34b..5e48e3bb44 100755 --- a/test/bin/unit_test.sh +++ b/test/bin/unit_test.sh @@ -34,6 +34,7 @@ UNIT_XML="unit_ascii2nc.xml \ unit_trmm2nc.xml \ unit_pb2nc.xml \ unit_gen_vx_mask.xml \ + unit_gen_ens_prod.xml \ unit_pcp_combine.xml \ unit_wwmca_regrid.xml \ unit_point_stat.xml \ diff --git a/test/config/GenEnsProdConfig b/test/config/GenEnsProdConfig new file mode 100644 index 0000000000..33fcc2337f --- /dev/null +++ b/test/config/GenEnsProdConfig @@ -0,0 +1,158 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Gen-Ens-Prod configuration file. +// +// For additional information, please see the MET User's Guide. +// +//////////////////////////////////////////////////////////////////////////////// + +// +// Output model name to be written +// +model = "WRF"; + +// +// Output description to be written +// May be set separately in each "obs.field" entry +// +desc = "NA"; + +//////////////////////////////////////////////////////////////////////////////// + +// +// Verification grid +// May be set separately in each "field" entry +// +regrid = { + to_grid = NONE; + method = NEAREST; + width = 1; + vld_thresh = 0.5; + shape = SQUARE; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// May be set separately in each "field" entry +// +censor_thresh = []; +censor_val = []; +cat_thresh = []; +nc_var_str = ""; + +// +// Ensemble fields to be processed +// +ens = { + ens_thresh = 0.80; + vld_thresh = 1.0; + + field = [ + { + name = "REFC"; + level = [ "L0" ]; + cat_thresh = [ >=35.0 ]; + GRIB1_ptv = 129; + ensemble_flag = TRUE; + }, + { + name = "UGRD"; + level = [ "Z10" ]; + cat_thresh = [ CDP75 ]; + }, + { + name = "WIND"; + level = [ "Z10" ]; + cat_thresh = [ >=CDP25&&<=CDP75 ]; + } + ]; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Neighborhood ensemble probabilities +// +nbrhd_prob = { + width = [ 5 ]; + shape = CIRCLE; + vld_thresh = 0.0; +} + +// +// NMEP smoothing methods +// +nmep_smooth = { + vld_thresh = 0.0; + shape = CIRCLE; + gaussian_dx = 81.27; + gaussian_radius = 120; + type = [ + { + method = GAUSSIAN; + width = 1; + } + ]; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Climatology data +// +climo_mean = ens; +climo_mean = { + + file_name = [ "${CLIMO_MEAN_FILE}" ]; + + regrid = { + method = NEAREST; + width = 1; + vld_thresh = 0.5; + shape = SQUARE; + } + + time_interp_method = DW_MEAN; + day_interval = 1; + hour_interval = 6; +} + +climo_stdev = climo_mean; +climo_stdev = { + file_name = [ "${CLIMO_STDEV_FILE}" ]; +} + +//////////////////////////////////////////////////////////////////////////////// + +// +// Ensemble product output types +// May be set separately in each "ens.field" entry +// +ensemble_flag = { + latlon = TRUE; + mean = TRUE; + stdev = TRUE; + minus = FALSE; + plus = FALSE; + min = FALSE; + max = FALSE; + range = FALSE; + vld_count = FALSE; + frequency = TRUE; + nep = TRUE; + nmep = TRUE; + climo = TRUE; + climo_cdp = TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +version = "V10.1.0"; + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/xml/unit_gen_ens_prod.xml b/test/xml/unit_gen_ens_prod.xml new file mode 100644 index 0000000000..9d3f2b0c75 --- /dev/null +++ b/test/xml/unit_gen_ens_prod.xml @@ -0,0 +1,78 @@ + + + + + + + + + +]> + + + + &TEST_DIR; + true + + + + + + + + + + echo "&DATA_DIR_MODEL;/grib1/arw-fer-gep1/arw-fer-gep1_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-sch-gep2/arw-sch-gep2_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-tom-gep3/arw-tom-gep3_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/nmm-fer-gep4/nmm-fer-gep4_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-fer-gep5/arw-fer-gep5_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-sch-gep6/arw-sch-gep6_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-tom-gep7/arw-tom-gep7_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/nmm-fer-gep8/nmm-fer-gep8_2012040912_F024.grib" \ + > &OUTPUT_DIR;/gen_ens_prod/input_file_list; \ + &MET_BIN;/gen_ens_prod + + CLIMO_MEAN_FILE &DATA_DIR_CLIMO;/NCEP_1.0deg/cmean_1d.19790410 + CLIMO_STDEV_FILE &DATA_DIR_CLIMO;/NCEP_1.0deg/cstdv_1d.19790410 + + \ + -ens &OUTPUT_DIR;/gen_ens_prod/input_file_list \ + -config &CONFIG_DIR;/GenEnsProdConfig \ + -out &OUTPUT_DIR;/gen_ens_prod/gen_ens_prod_NO_CTRL_20120410_120000V.nc \ + -v 2 + + + &OUTPUT_DIR;/gen_ens_prod/gen_ens_prod_NO_CTRL_20120410_120000V.nc + + + + + echo "&DATA_DIR_MODEL;/grib1/arw-fer-gep1/arw-fer-gep1_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-sch-gep2/arw-sch-gep2_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-tom-gep3/arw-tom-gep3_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/nmm-fer-gep4/nmm-fer-gep4_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-fer-gep5/arw-fer-gep5_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-sch-gep6/arw-sch-gep6_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/arw-tom-gep7/arw-tom-gep7_2012040912_F024.grib \ + &DATA_DIR_MODEL;/grib1/nmm-fer-gep8/nmm-fer-gep8_2012040912_F024.grib" \ + > &OUTPUT_DIR;/gen_ens_prod/input_file_list; \ + &MET_BIN;/gen_ens_prod + + CLIMO_MEAN_FILE &DATA_DIR_CLIMO;/NCEP_1.0deg/cmean_1d.19790410 + CLIMO_STDEV_FILE &DATA_DIR_CLIMO;/NCEP_1.0deg/cstdv_1d.19790410 + + \ + -ens &OUTPUT_DIR;/gen_ens_prod/input_file_list \ + -ctrl &DATA_DIR_MODEL;/grib1/arw-tom-gep0/arw-tom-gep0_2012040912_F024.grib \ + -config &CONFIG_DIR;/GenEnsProdConfig \ + -out &OUTPUT_DIR;/gen_ens_prod/gen_ens_prod_WITH_CTRL_20120410_120000V.nc \ + -v 2 + + + &OUTPUT_DIR;/gen_ens_prod/gen_ens_prod_WITH_CTRL_20120410_120000V.nc + + + + diff --git a/test/xml/unit_met_test_scripts.xml b/test/xml/unit_met_test_scripts.xml index 2812eeeaaa..1d73214f86 100644 --- a/test/xml/unit_met_test_scripts.xml +++ b/test/xml/unit_met_test_scripts.xml @@ -23,7 +23,7 @@ true - + @@ -52,6 +52,23 @@ + + + + + + &MET_BIN;/gen_ens_prod + \ + -ens &MET_DATA;/sample_fcst/2009123112/*gep*/d01_2009123112_02400.grib \ + -config &MET_SCRIPTS;/config/GenEnsProdConfig \ + -out &OUTPUT_DIR;/gen_ens_prod/gen_ens_prod_20100101_120000V_ens.nc \ + -v 2 + + + &OUTPUT_DIR;/gen_ens_prod/gen_ens_prod_20100101_120000V_ens.nc + + +