UFO
MetOfficeBuddyPairFinder.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2020 Met Office UK
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 
12 #include "oops/util/Logger.h"
17 
18 #include <boost/make_unique.hpp>
19 
20 namespace ufo {
21 
22 namespace {
23 
24 float zonalBandWidth(int numBands) {
25  return 180.0f / numBands;
26 }
27 
28 } // namespace
29 
31  const std::vector<float> &latitudes,
32  const std::vector<float> &longitudes,
33  const std::vector<util::DateTime> &datetimes,
34  const std::vector<float> *pressures,
35  const std::vector<int> &stationIds)
36  : options_(options), latitudes_(latitudes), longitudes_(longitudes),
37  datetimes_(datetimes), pressures_(pressures), stationIds_(stationIds)
38 {}
39 
40 std::vector<MetOfficeBuddyPair> MetOfficeBuddyPairFinder::findBuddyPairs(
41  const std::vector<size_t> & validObsIds) {
42  std::vector<int> validObsIdsInSortOrder;
43  std::vector<int> bandLbounds;
44  sortObservations(validObsIds, validObsIdsInSortOrder, bandLbounds);
45  return pairObservations(validObsIdsInSortOrder, bandLbounds);
46 }
47 
48 void MetOfficeBuddyPairFinder::sortObservations(const std::vector<size_t> & validObsIds,
49  std::vector<int> &validObsIdsInSortOrder,
50  std::vector<int> &bandLbounds)
51 {
52  // Initialize output parameters
53 
54  validObsIdsInSortOrder.clear();
55  validObsIdsInSortOrder.reserve(validObsIds.size());
56  bandLbounds.assign(options_.numZonalBands + 1, 0);
57 
58  // Identify the band containing each valid observation
59 
60  const float bandWidth = zonalBandWidth(options_.numZonalBands);
61  std::vector<int> bandIndices(validObsIds.size());
62  for (size_t validObsIndex = 0; validObsIndex < validObsIds.size(); ++validObsIndex) {
63  const size_t obsId = validObsIds[validObsIndex];
64  // Use 89.999 to round up values, e.g. ranges are 70.0 to 74.999
65  // (There may be a more elegant way to do this, but use this one for compatibility with OPS.)
66  bandIndices[validObsIndex] =
67  std::max(0, static_cast<int>((89.999f - latitudes_[obsId]) / bandWidth));
68  }
69 
70  // Sort observations
71 
72  RecursiveSplitter splitter(validObsIds.size());
73  splitter.groupBy(bandIndices);
74  splitter.sortGroupsBy(
75  [this, &validObsIds](size_t obsIndex)
76  {
77  size_t obsId = validObsIds[obsIndex];
78  return std::make_tuple(longitudes_[obsId], -latitudes_[obsId],
79  pressures_ ? (*pressures_)[obsId] : 0.0f, datetimes_[obsId]);
80  });
81 
82  // Fill the validObsIdsInSortOrder and bandLbounds vectors
83 
84  int currentBandIndex = -1;
85  for (auto group : splitter.groups()) {
86  for (size_t validObsIndex : group) {
87  const size_t obsId = validObsIds[validObsIndex];
88  for (int band = currentBandIndex + 1; band <= bandIndices[validObsIndex]; ++band) {
89  bandLbounds[band] = validObsIdsInSortOrder.size();
90  }
91  currentBandIndex = bandIndices[validObsIndex];
92  validObsIdsInSortOrder.push_back(obsId);
93  }
94  }
95  for (int band = currentBandIndex + 1; band < bandLbounds.size(); ++band) {
96  bandLbounds[band] = validObsIds.size();
97  }
98 
99  oops::Log::trace() << "Buddy check: " << validObsIds.size() << " input observations" << std::endl;
100 }
101 
102 std::vector<MetOfficeBuddyPair> MetOfficeBuddyPairFinder::pairObservations(
103  const std::vector<int> &validObsIdsInSortOrder,
104  const std::vector<int> &bandLbounds) {
105 
106  std::vector<MetOfficeBuddyPair> pairs;
107 
108  // Initialise variables
109  const float bandWidth = zonalBandWidth(options_.numZonalBands);
110  // eqn 3.1
112  const float searchDLatB = searchDLat + 0.5f * bandWidth;
113  const int numSearchBands = static_cast<int>(searchDLat / bandWidth) + 1;
114 
115  typedef std::vector<int>::const_iterator ObsIdIt;
116  typedef std::vector<int>::const_reverse_iterator ObsIdRevIt;
117 
118  std::vector<ObsIdIt> bandBegins(options_.numZonalBands);
119  std::vector<ObsIdIt> bandEnds(options_.numZonalBands);
120 
121  for (int bandIndex = 0; bandIndex < options_.numZonalBands; ++bandIndex) {
122  bandBegins[bandIndex] = validObsIdsInSortOrder.begin() + bandLbounds[bandIndex];
123  bandEnds[bandIndex] = validObsIdsInSortOrder.begin() + bandLbounds[bandIndex + 1];
124  }
125 
126  std::vector<ObsIdIt> firstObsToCheckInBands(options_.numZonalBands);
127 
128  // Collects buddies of a single observation. When we're done with that observation, the collected
129  // list of buddies is extracted into 'pairs' and the collector is reset.
130  std::unique_ptr<MetOfficeBuddyCollector> buddyCollector = makeBuddyCollector();
131 
132  // Iterate over all bands
133  for (int jBandA = 0; jBandA < options_.numZonalBands; ++jBandA) {
134  const float lonSearchRangeHalfWidth = getLongitudeSearchRangeHalfWidth(jBandA, bandWidth);
135 
136  const int firstBandToSearch = jBandA;
137  const int lastBandToSearch = std::min(options_.numZonalBands.value() - 1,
138  jBandA + numSearchBands);
139 
140  firstObsToCheckInBands = bandBegins;
141 
142  // Iterate over observations in (jBandA)th band
143  for (ObsIdIt obsIdItA = bandBegins[jBandA]; obsIdItA != bandEnds[jBandA]; ++obsIdItA) {
144  const int obsIdA = *obsIdItA;
145 
146  const float minLonToCheck = longitudes_[obsIdA] - lonSearchRangeHalfWidth;
147  const float maxLonToCheck = longitudes_[obsIdA] + lonSearchRangeHalfWidth;
148 
149  buddyCollector->reset(obsIdA);
150 
151  // Iterate over bands that may contain buddies
152  for (int jBandB = firstBandToSearch; jBandB <= lastBandToSearch; ++jBandB) {
153  float midBandLatB = 90.0f - bandWidth * (jBandB + 0.5f);
154  if (std::abs(latitudes_[obsIdA] - midBandLatB) > searchDLatB)
155  continue;
156 
157  ObsIdIt firstObsIdItB = firstObsToCheckInBands[jBandB];
158  if (jBandA == jBandB)
159  firstObsIdItB = obsIdItA + 1; // Iterate only over observations following observation A.
160 
161  // First loop: look for buddies at longitudes [minLonToCheck, maxLonToCheck]
162  bool firstObsInSearchRangeFound = false;
163  for (ObsIdIt obsIdItB = firstObsIdItB; obsIdItB != bandEnds[jBandB]; ++obsIdItB) {
164  const int obsIdB = *obsIdItB;
165  if (longitudes_[obsIdB] < minLonToCheck)
166  continue;
167  if (longitudes_[obsIdB] > maxLonToCheck)
168  break;
169  if (!firstObsInSearchRangeFound) {
170  firstObsToCheckInBands[jBandB] = obsIdItB;
171  firstObsInSearchRangeFound = true;
172  }
173 
174  buddyCollector->examinePotentialBuddy(obsIdB);
175  if (buddyCollector->foundEnoughBuddies())
176  goto FinishProcessingObsA;
177  if (buddyCollector->foundEnoughBuddiesInCurrentBand())
178  goto FinishProcessingBandB;
179  }
180 
181  // Optional second loop: look for buddies at longitudes [-180, maxLonToCheck - 360]
182  if (maxLonToCheck > 180 && (jBandA != jBandB || lonSearchRangeHalfWidth < 180)) {
183  // Observation A is near band end (+180); wrap around and check the band start too.
184  float wrappedMaxLonToCheck = maxLonToCheck - 360;
185  for (ObsIdIt obsIdItB = bandBegins[jBandB]; obsIdItB != bandEnds[jBandB]; ++obsIdItB) {
186  const int obsIdB = *obsIdItB;
187  if (longitudes_[obsIdB] > wrappedMaxLonToCheck ||
188  longitudes_[obsIdB] >= minLonToCheck /* visited already in the first loop */)
189  break;
190 
191  buddyCollector->examinePotentialBuddy(obsIdB);
192  if (buddyCollector->foundEnoughBuddies())
193  goto FinishProcessingObsA;
194  if (buddyCollector->foundEnoughBuddiesInCurrentBand())
195  goto FinishProcessingBandB;
196  }
197  }
198 
199  // Optional third loop: look for buddies at longitudes [minLonToCheck + 360, 180]
200  if (minLonToCheck < -180 && jBandA != jBandB) {
201  // Observation A is near band start (-180); wrap around and check the band end too.
202  float wrappedMinLonToCheck = minLonToCheck + 360;
203  for (ObsIdRevIt obsIdItB(bandEnds[jBandB]), reverseBandEnd(bandBegins[jBandB]);
204  obsIdItB != reverseBandEnd; ++obsIdItB) {
205  const int obsIdB = *obsIdItB;
206  if (longitudes_[obsIdB] < wrappedMinLonToCheck ||
207  longitudes_[obsIdB] <= maxLonToCheck /* visited already in the first loop */)
208  break;
209 
210  buddyCollector->examinePotentialBuddy(obsIdB);
211  if (buddyCollector->foundEnoughBuddies())
212  goto FinishProcessingObsA;
213  if (buddyCollector->foundEnoughBuddiesInCurrentBand())
214  goto FinishProcessingBandB;
215  }
216  }
217 
218 FinishProcessingBandB:
219  buddyCollector->startProcessingNextBand();
220  } // end of secondary loop over bands (jBandB)
221 FinishProcessingObsA:
222  buddyCollector->appendBuddyPairsTo(pairs);
223  } // end of main loop over observations (obsIdItA)
224  } // end of main loop over bands (jBandA)
225 
226  oops::Log::trace() << "Found " << pairs.size() << " buddy pairs.\n";
227 
228  return pairs;
229 }
230 
231 std::unique_ptr<MetOfficeBuddyCollector> MetOfficeBuddyPairFinder::makeBuddyCollector() const {
233  return boost::make_unique<MetOfficeBuddyCollectorV1>(options_, latitudes_,
235  else
236  return boost::make_unique<MetOfficeBuddyCollectorV2>(options_, latitudes_,
238 }
239 
241  float bandWidth) const {
242  const float earthRadius = Constants::mean_earth_rad;
243  const float deg2rad = static_cast<float>(Constants::deg2rad);
244  const float rad2deg = static_cast<float>(Constants::rad2deg);
245 
246  const float midBandLatA = 90.0f - bandWidth * (bandIndex + 0.5f);
247  // eqn 3.2a
248  const float rad = earthRadius * std::cos((std::abs(midBandLatA) + bandWidth * 0.5f) * deg2rad);
249  if (rad <= 10.0f)
250  return 360.0f; // Adjacent to pole
251  else
252  return rad2deg * options_.searchRadius / rad; // eqn 3.2b
253 }
254 
255 } // namespace ufo
256 
Options controlling the operation of the MetOfficeBuddyCheck filter.
oops::Parameter< float > searchRadius
Maximum distance between two observations that may be classified as buddies, in km.
std::unique_ptr< MetOfficeBuddyCollector > makeBuddyCollector() const
std::vector< MetOfficeBuddyPair > pairObservations(const std::vector< int > &validObsIdsInSortOrder, const std::vector< int > &bandLbounds)
Finds pairs of observations to be considered as buddies. Calculates the distance and mutual orientati...
const std::vector< util::DateTime > & datetimes_
MetOfficeBuddyPairFinder(const MetOfficeBuddyCheckParameters &options, const std::vector< float > &latitudes, const std::vector< float > &longitudes, const std::vector< util::DateTime > &datetimes, const std::vector< float > *pressures, const std::vector< int > &stationIds)
Constructor.
float getLongitudeSearchRangeHalfWidth(int bandIndex, float bandWidth) const
std::vector< MetOfficeBuddyPair > findBuddyPairs(const std::vector< size_t > &validObsIds)
Returns a list of MetOfficeBuddyPair objects representing pairs of "buddy" observations that should b...
const MetOfficeBuddyCheckParameters & options_
void sortObservations(const std::vector< size_t > &validObsIds, std::vector< int > &validObsIdsInSortOrder, std::vector< int > &bandLbounds)
Sorts observations in an order facilitating rapid search for buddies.
Partitions an array into groups of elements equivalent according to certain criteria.
GroupRange groups() const
Return the range of equivalence classes.
void groupBy(const std::vector< size_t > &categories)
Split existing equivalence classes according to a new criterion.
void sortGroupsBy(const UnaryOperation &key)
Sort the elements in each equivalence class in ascending order.
real(kind_real), parameter, public deg2rad
real(kind_real), parameter, public rad2deg
Definition: RunCRTM.h:27
util::Duration abs(const util::Duration &duration)
static constexpr double deg2rad
Definition: Constants.h:21
static constexpr double rad2deg
Definition: Constants.h:22
static constexpr double mean_earth_rad
Definition: Constants.h:40