UFO
SolarZenith.cc
Go to the documentation of this file.
1 /*
2  * (C) Crown copyright 2021, Met Office
3  *
4  * This software is licensed under the terms of the Apache Licence Version 2.0
5  * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6  */
7 
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <string>
13 #include <vector>
14 
15 #include "ioda/ObsDataVector.h"
16 #include "ufo/filters/QCflags.h"
17 #include "ufo/filters/Variable.h"
18 #include "ufo/utils/Constants.h"
19 
20 namespace ufo {
21 
22 namespace {
23 
24 /// Return a vector whose ith element is set to true if and only if observations of all simulated
25 /// variables at the ith location have been rejected.
26 std::vector<bool> identifyRejectedObservations(const ObsFilterData &data) {
27  const size_t nlocs = data.nlocs();
28  const oops::Variables &simulatedVars = data.obsspace().obsvariables();
29  std::vector<bool> rejected(nlocs, true);
30 
31  std::vector<int> qcflags(nlocs);
32  for (size_t iv = 0; iv < simulatedVars.size(); ++iv) {
33  data.get(Variable(simulatedVars[iv] + "@QCflagsData"), qcflags);
34  for (size_t iloc = 0; iloc < nlocs; ++iloc)
35  if (qcflags[iloc] == QCflags::pass)
36  rejected[iloc] = false;
37  }
38 
39  return rejected;
40 }
41 
42 } // namespace
43 
44 static ObsFunctionMaker<SolarZenith> maker_("SolarZenith");
45 
46 SolarZenith::SolarZenith(const eckit::LocalConfiguration & conf) {
47  options_.validateAndDeserialize(conf);
48 
49  // List of required ObsSpace variables
50  invars_ += Variable("latitude@MetaData");
51  invars_ += Variable("longitude@MetaData");
52  invars_ += Variable("datetime@MetaData");
53 }
54 
56  const float missingFloat = util::missingValue(float());
57  const util::DateTime missingDateTime = util::missingValue(util::DateTime());
58 
59  const int secondsPerDay = 60 * 60 * 24;
60  const double centuriesPerDay = 1.0 / 36525.0;
61  const double hoursPerSecond = 1.0 / 3600.0;
62  const double degreesLongitudePerHour = 15.0;
63  const double hoursPerDegreeLongitude = 1.0 / degreesLongitudePerHour;
64  const double one_over_360 = 1.0 / 360.0;
65 
66  const util::DateTime startOfLastDayOf19thCentury(1899, 12, 31, 0, 0, 0);
67 
68  const size_t nlocs = in.nlocs();
69 
70  // Inputs
71  std::vector<float> lats(nlocs), lons(nlocs);
72  std::vector<util::DateTime> datetimes(nlocs);
73  in.get(Variable("latitude@MetaData"), lats);
74  in.get(Variable("longitude@MetaData"), lons);
75  in.get(Variable("datetime@MetaData"), datetimes);
76 
77  std::vector<bool> rejected;
78  const bool skipRejected = options_.skipRejected;
79  if (skipRejected)
80  rejected = identifyRejectedObservations(in);
81 
82  // Output
83  std::vector<float> &zenith = out[0];
84  std::fill(zenith.begin(), zenith.end(), missingFloat);
85 
86  // Statistics
87  size_t numRejected = 0;
88  size_t numMissingLats = 0;
89  size_t numMissingLons = 0;
90  size_t numMissingDatetimes = 0;
91  size_t numOutOfRangeLats = 0;
92  size_t numOutOfRangeDatetimes = 0;
93 
94  // Values dependent on day only (reused for all consecutive datetimes from the same day)
95  util::DateTime dayStart = missingDateTime;
96  util::DateTime dayEnd = missingDateTime;
97  // Equation of time (more details below)
98  double eqnt;
99  // Sine and cosine of declination
100  double sinDecl, cosDecl;
101 
102  for (size_t loc = 0; loc < nlocs; ++loc) {
103  if (skipRejected && rejected[loc]) {
104  ++numRejected;
105  oops::Log::debug() << "SolarZenith: ob " << loc << " has already been rejected"
106  << ". Output set to missing data\n";
107  continue;
108  }
109  if (lats[loc] == missingFloat) {
110  ++numMissingLats;
111  oops::Log::debug() << "SolarZenith: missing latitude encountered for ob " << loc
112  << ". Output set to missing data\n";
113  continue;
114  }
115  if (lons[loc] == missingFloat) {
116  ++numMissingLons;
117  oops::Log::debug() << "SolarZenith: missing longitude encountered for ob " << loc
118  << ". Output set to missing data\n";
119  continue;
120  }
121  if (datetimes[loc] == missingDateTime) {
122  ++numMissingDatetimes;
123  oops::Log::debug() << "SolarZenith: missing datetime encountered for ob " << loc
124  << ". Output set to missing data\n";
125  continue;
126  }
127  if (lats[loc] < -90 || lats[loc] > 90) {
128  ++numOutOfRangeLats;
129  oops::Log::debug() << "SolarZenith: latitude " << lats[loc] << " of ob " << loc
130  << " is out of range. Output set to missing data\n";
131  continue;
132  }
133 
134  const util::DateTime &datetime = datetimes[loc];
135 
136  if (datetime < dayStart || datetime >= dayEnd) {
137  // Calculate quantities dependent only on the date (not time).
138  int year, month, day, hour, minute, second;
139  datetime.toYYYYMMDDhhmmss(year, month, day, hour, minute, second);
140  if (year > 1950 && year <= 2200) {
141  dayStart = util::DateTime(year, month, day, 0, 0, 0);
142  dayEnd = dayStart + util::Duration(secondsPerDay);
143 
144  // Day since 31 Dec 1899 ("0 Jan 1900")
145  // (Used instead of 1 Jan 1900 since 2000 was a leap year.)
146  const std::size_t centuryDay =
147  (dayStart - startOfLastDayOf19thCentury).toSeconds() / secondsPerDay;
148  const double rcd = centuryDay * centuriesPerDay; // Fraction of days elapsed this century
149  const double rcd2 = rcd * rcd;
150  double ydeg = (rcd * 36000.769 + 279.697) * one_over_360;
151  ydeg = std::fmod(ydeg, 1.0) * 360.0;
152  const double yrad = ydeg * Constants::deg2rad;
153 
154  // Compute equation of time (in seconds) for this day
155  // (No reference for this but it gives the correct answers
156  // when compared with table in Norton's Star Atlas.)
157  // The linter protests about extra spaces used for alignment, so is disabled.
158  eqnt = - (( 93.0 + 14.23 * rcd - 0.0144 * rcd2) * std::sin(yrad)) // NOLINT
159  - ((432.5 - 3.71 * rcd - 0.2063 * rcd2) * std::cos(yrad)) // NOLINT
160  + ((596.9 - 0.81 * rcd - 0.0096 * rcd2) * std::sin(2.0 * yrad)) // NOLINT
161  - (( 1.4 + 0.28 * rcd) * std::cos(2.0 * yrad)) // NOLINT
162  + (( 3.8 + 0.6 * rcd) * std::sin(3.0 * yrad)) // NOLINT
163  + (( 19.5 - 0.21 * rcd - 0.0103 * rcd2) * std::cos(3.0 * yrad)) // NOLINT
164  - (( 12.8 - 0.03 * rcd) * std::sin(4.0 * yrad)); // NOLINT
165 
166  // Get solar declination for given day (radians)
167  const double sinalp = std::sin((ydeg - eqnt / 240.0) * Constants::deg2rad);
168  const double taneqn = 0.43382 - 0.00027 * rcd;
169  const double decl = std::atan(taneqn * sinalp);
170  eqnt *= hoursPerSecond; // Convert to hours
171 
172  // Sine and cosine of declination
173  sinDecl = std::sin(decl);
174  cosDecl = std::cos(decl);
175  } else {
176  ++numOutOfRangeDatetimes;
177  oops::Log::debug() << "SolarZenith: date/time " << datetime << " of ob " << loc
178  << "is out of range. Output set to missing data\n";
179  continue;
180  }
181  }
182 
183  const double lat = lats[loc];
184  const double lon = lons[loc];
185 
186  const double latInRadians = lat * Constants::deg2rad;
187  const double sinLat = std::sin(latInRadians);
188  const double cosLat = std::cos(latInRadians);
189 
190  const std::int64_t secondsSinceDayStart = (datetime - dayStart).toSeconds();
191  const double hoursSinceDayStart = secondsSinceDayStart * hoursPerSecond;
192  const double localSolarTimeInHours = lon * hoursPerDegreeLongitude + eqnt + hoursSinceDayStart;
193  // Local hour angle (when longitude is 0, this is the Greenwich hour angle given in the
194  // Air Almanac)
195  const double hourAngleInRadians =
196  (localSolarTimeInHours * degreesLongitudePerHour + 180.0) * Constants::deg2rad;
197 
198  const double sinEv = sinDecl * sinLat + cosDecl * cosLat * std::cos(hourAngleInRadians);
199  zenith[loc] = (M_PI / 2 - std::asin(sinEv)) * Constants::rad2deg;
200  }
201 
202  // Notify about "bad" observations
203  if (numRejected != 0)
204  oops::Log::trace() << "SolarZenith: " << numMissingLats << " obs had already been rejected\n";
205  if (numMissingLats != 0)
206  oops::Log::trace() << "SolarZenith: " << numMissingLats << " obs had missing latitude\n";
207  if (numMissingLons != 0)
208  oops::Log::trace() << "SolarZenith: " << numMissingLats << " obs had missing longitude\n";
209  if (numOutOfRangeLats != 0)
210  oops::Log::trace() << "SolarZenith: " << numOutOfRangeLats
211  << " obs had out-of-range latitude\n";
212  if (numOutOfRangeDatetimes != 0)
213  oops::Log::trace() << "SolarZenith: " << numOutOfRangeDatetimes
214  << " obs had out-of-range datetime\n";
215  oops::Log::trace().flush();
216 }
217 
219  return invars_;
220 }
221 
222 } // namespace ufo
ObsFilterData provides access to all data related to an ObsFilter.
size_t nlocs() const
Returns the number of locations in the associated ObsSpace.
ioda::ObsSpace & obsspace() const
Returns reference to ObsSpace associated with ObsFilterData.
void get(const Variable &varname, std::vector< float > &values) const
Fills a std::vector with values of the specified variable.
SolarZenith(const eckit::LocalConfiguration &conf)
Definition: SolarZenith.cc:46
SolarZenithParameters options_
Definition: SolarZenith.h:42
void compute(const ObsFilterData &, ioda::ObsDataVector< float > &) const override
compute the result of the function
Definition: SolarZenith.cc:55
ufo::Variables invars_
Definition: SolarZenith.h:43
const ufo::Variables & requiredVariables() const override
geovals required to compute the function
Definition: SolarZenith.cc:218
oops::Parameter< bool > skipRejected
Definition: SolarZenith.h:23
constexpr int pass
Definition: QCflags.h:14
std::vector< bool > identifyRejectedObservations(const ObsFilterData &data)
Definition: SolarZenith.cc:26
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27
static ObsFunctionMaker< SolarZenith > maker_("SolarZenith")
static constexpr double deg2rad
Definition: Constants.h:21
static constexpr double rad2deg
Definition: Constants.h:22