UFO
TrackCheckShip.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2021 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 <Eigen/Core> // for easy array addition/subtraction
11 
12 #include <algorithm>
13 #include <cassert>
14 #include <cmath>
15 #include <functional>
16 #include <map>
17 #include <string>
18 #include <tuple>
19 #include <utility>
20 #include <vector>
21 
22 #include "eckit/config/Configuration.h"
23 #include "ioda/ObsDataVector.h"
24 #include "ioda/ObsSpace.h"
25 #include "oops/base/Variables.h"
26 #include "oops/util/DateTime.h"
27 #include "oops/util/Duration.h"
28 #include "oops/util/Logger.h"
32 #include "ufo/utils/Constants.h"
34 
35 namespace ufo {
36 
37 TrackCheckShip::TrackCheckShip(ioda::ObsSpace &obsdb, const Parameters_ &parameters,
38  std::shared_ptr<ioda::ObsDataVector<int> > flags,
39  std::shared_ptr<ioda::ObsDataVector<float> > obserr)
40  : FilterBase(obsdb, parameters, flags, obserr), options_(parameters)
41 {
42  oops::Log::debug() << "TrackCheckShip: config = " << options_ << std::endl;
43  assert(options_.core.maxSpeed.value() > 0);
46 }
47 
48 /// \brief Estimate of speed as calculated in Ops_CheckShipTrack.
49 ///
50 /// Speed is calculated between \p obs1 and \p obs2, accounting for spatial/temporal
51 /// uncertainty using the resolution values stored in \p options.
55  const TrackCheckShipParameters& options) {
56  util::Duration temporalDistance = abs(obs1.getTime() -
57  obs2.getTime());
58  util::Duration tempRes = options.core.temporalResolution;
59  auto dist = distance(obs1, obs2);
60  auto spatialRes = options.core.spatialResolution;
61  double speedEst = 0.0;
62  if (dist > spatialRes) {
63  speedEst = (dist - 0.5 * spatialRes) /
64  std::max(temporalDistance.toSeconds(),
65  (tempRes.toSeconds()));
66  speedEst *= 1000.0; // convert units from km/s to m/s
67  }
68  return speedEst;
69 }
70 
71 /// \brief Returns the angle in degrees (rounded to the nearest .1 degree)
72 /// between displacement vectors going from observations \p a to \p b
73 /// and \p b to \p c.
77  auto locA = a.getLocation();
78  auto locB = b.getLocation();
79  auto locC = c.getLocation();
80  Eigen::Vector3f disp1{locB[0]-locA[0], locB[1]-locA[1], locB[2]-locA[2]};
81  Eigen::Vector3f disp2{locC[0]-locB[0], locC[1]-locB[1], locC[2]-locB[2]};
82  auto retValue = std::acos(disp1.dot(disp2) / (disp1.norm() * disp2.norm()));
83  retValue *= static_cast<float>(Constants::rad2deg);
84  return std::round(retValue * 10.0f) * 0.1f;
85 }
86 
88 {
89  return diagnostics_.get();
90 }
91 
93  double latitude, double longitude,
94  const util::DateTime &time,
95  std::shared_ptr<TrackStatistics> const& ts,
96  std::shared_ptr<TrackCheckUtils::CheckCounter> const &checkCounter,
97  size_t observationNumber)
98  : obsLocationTime_(latitude, longitude, time), fullTrackStatistics_(ts),
99  observationNumber_(observationNumber), rejected_(false) {}
100 
102 {
103  return observationNumber_;
104 }
105 
106 // Required for the correct destruction of options_.
108 {}
109 
110 /// Detects whether each observation in a track has been rejected, and marks
111 /// the corresponding space in \p isRejected as true if so.
112 /// \param trackObsIndicesBegin the begin iterator of the track
113 /// \param trackObsIndicesEnd the end iterator
114 /// \param validObsIds the vector marking all of the observations' global
115 /// positions
116 /// \param trackObservations the full vector of observations within
117 /// the single track
118 /// \param isRejected the boolean vector whose indices correspond to all of
119 /// the rejected observations in the full input dataset
121  std::vector<size_t>::const_iterator trackObsIndicesBegin,
122  std::vector<size_t>::const_iterator trackObsIndicesEnd,
123  const std::vector<size_t> &validObsIds,
124  const std::vector<TrackObservation> &trackObservations,
125  std::vector<bool> &isRejected) const {
126  auto trackObsIndexIt = trackObsIndicesBegin;
127  auto trackObsIt = trackObservations.begin();
128  for (; trackObsIndexIt != trackObsIndicesEnd; ++trackObsIndexIt, ++trackObsIt)
129  isRejected[validObsIds[*trackObsIndexIt]] = trackObsIt->rejected();
130 }
131 
132 void TrackCheckShip::print(std::ostream & os) const {
133  os << "TrackCheckShip: config = " << options_ << std::endl;
134 }
135 
136 
137 /// The filter begins with separating observations into tracks based on \p Station_Id. Various
138 /// calculations are then performed between consecutive observations: distances/speeds between
139 /// observation pairs, and angles between the displacement vectors formed by triplets of
140 /// consecutive observations. Track-specific counters are incremented for various metrics that
141 /// indicate an unexpected track path.
142 ///
143 /// Based on these counter values, the filter may ignore the current track and move on to the next
144 /// one. Using the described calculations and additional ones if necessary, the observation
145 /// directly before and/or after the fastest segment is chosen for rejection. This will continue
146 /// until all remaining segments that link accepted observations are either:
147 /// 1. slower than a user-defined "max speed (m/s)" where the angles between the fastest accepted
148 /// segment's displacement vector and its two neighbouring segments' displacement vectors are
149 /// both less than 90 degrees.
150 /// 2. slower than 80 percent of this user-defined speed.
151 ///
152 /// The full track will be rejected if the number of observations removed is more than the
153 /// user-defined "rejection threshold".
154 
155 void TrackCheckShip::applyFilter(const std::vector<bool> & apply,
156  const Variables & filtervars,
157  std::vector<std::vector<bool>> & flagged) const {
159 
160  const std::vector<size_t> validObsIds
161  = obsAccessor.getValidObservationIds(apply, *flags_, filtervars);
162 
163  RecursiveSplitter splitter = obsAccessor.splitObservationsIntoIndependentGroups(validObsIds);
164  TrackCheckUtils::sortTracksChronologically(validObsIds, obsAccessor, splitter);
165 
168 
169  std::vector<bool> isRejected(obsLocTime.latitudes.size(), false);
170  size_t trackNumber = 0;
171  for (auto track : splitter.multiElementGroups()) {
172  trackNumber++;
173  std::string stationId = std::to_string(trackNumber);
174  std::vector<TrackObservation> trackObservations = collectTrackObservations(
175  track.begin(), track.end(), validObsIds, obsLocTime);
176  std::vector<std::reference_wrapper<TrackObservation>> trackObservationsReferences;
177  trackObservationsReferences.reserve(trackObservations.size());
178  std::transform(trackObservations.begin(), trackObservations.end(),
179  std::back_inserter(trackObservationsReferences),
180  [](TrackObservation &obs) {
181  return std::ref<TrackObservation>(obs); });
182  calculateTrackSegmentProperties(trackObservationsReferences,
183  CalculationMethod::FIRSTITERATION);
184  if (!trackObservationsReferences.empty() &&
185  this->options_.core.earlyBreakCheck &&
186  TrackCheckShip::earlyBreak(trackObservationsReferences, stationId)) {
187  continue;
188  }
189  bool firstIterativeRemoval = true;
190  while (trackObservationsReferences.size() >= 3) {
191  // Initial loop: fastest (as determined by set of comparisons) observation removed
192  // until all segments show slower speed than max threshold
193  auto maxSpeedReferenceIterator = std::max_element(
194  trackObservationsReferences.begin(), trackObservationsReferences.end(),
196  return a.getObservationStatistics().speed <
197  b.getObservationStatistics().speed;});
198  auto maxSpeedValue = maxSpeedReferenceIterator->get().getObservationStatistics().speed;
199  if (maxSpeedValue <= (0.8 * options_.core.maxSpeed.value())) {
200  break;
201  } else if (maxSpeedValue < options_.core.maxSpeed.value()) {
202  auto maxSpeedAngle = std::max(
203  (maxSpeedReferenceIterator - 1)->get().getObservationStatistics().angle,
204  maxSpeedReferenceIterator->get().getObservationStatistics().angle);
205  if (maxSpeedAngle <= 90.0) {
206  break;
207  }
208  }
210  trackObservationsReferences, maxSpeedReferenceIterator, firstIterativeRemoval,
211  stationId);
212  firstIterativeRemoval = false;
213  calculateTrackSegmentProperties(trackObservationsReferences, CalculationMethod::MAINLOOP);
214  }
215  auto rejectedCount = std::count_if(trackObservations.begin(), trackObservations.end(),
216  [](const TrackObservation& a) {return a.rejected();});
217  if (rejectedCount >= options_.core.rejectionThreshold.value() * trackObservations.size()) {
218  oops::Log::trace() << "CheckShipTrack: track " << stationId << " NumRej " <<
219  rejectedCount << " out of " << trackObservations.size() <<
220  " reports rejected. *** Reject whole track ***\n";
221  for (TrackObservation &obs : trackObservations)
222  obs.setRejected(true);
223  }
225  validObsIds, trackObservations, isRejected);
226  }
227  obsAccessor.flagRejectedObservations(isRejected, flagged);
228 }
229 
230 /// \returns a \p vector of \p TrackObservations that all hold a \p shared_ptr to an instance
231 /// of \p TrackStatistics, which holds all of the track-specific counters.
232 std::vector<TrackCheckShip::TrackObservation> TrackCheckShip::collectTrackObservations(
233  std::vector<size_t>::const_iterator trackObsIndicesBegin,
234  std::vector<size_t>::const_iterator trackObsIndicesEnd,
235  const std::vector<size_t> &validObsIds,
236  const TrackCheckUtils::ObsGroupLocationTimes &obsLocTime) const {
237  std::vector<TrackObservation> trackObservations;
238  trackObservations.reserve(trackObsIndicesEnd - trackObsIndicesBegin);
239  std::shared_ptr<TrackStatistics> trackStatistics(new TrackStatistics());
240  std::shared_ptr<TrackCheckUtils::CheckCounter> checkCounter(new TrackCheckUtils::CheckCounter);
241  size_t observationNumber = 0;
242  for (std::vector<size_t>::const_iterator it = trackObsIndicesBegin;
243  it != trackObsIndicesEnd; ++it) {
244  const size_t obsId = validObsIds[*it];
245  trackObservations.push_back(TrackObservation(obsLocTime.latitudes[obsId],
246  obsLocTime.longitudes[obsId],
247  obsLocTime.datetimes[obsId], trackStatistics,
248  checkCounter,
249  observationNumber));
250  observationNumber++;
251  }
252  return trackObservations;
253 }
254 
255 /// \brief \returns true if at least half of the track segments have
256 /// incremented the relevant rejection counters
257 ///
258 /// This filter is best for mostly-accurate observation tracks. If the track has
259 /// many "errors" (as indicated by the counters that are incremented before
260 /// any observations are removed), it will stop early by default.
261 ///
262 /// Sometimes caused by two ships with same callsign or various reports with wrong time,
263 /// the check gives up. This is particularly a problem with WOD01 data - case studies
264 /// suggest that most suspect data is reasonable.
265 bool TrackCheckShip::earlyBreak(const std::vector<std::reference_wrapper<TrackObservation>>
266  &trackObs, const std::string trackId) const {
267  bool breakResult = false;
268  const auto& trackStats = *(trackObs[0].get().getFullTrackStatistics());
269  // if at least half of the track segments have a time difference of less than an hour
270  // (if non-buoy), are faster than a configured maximum speed, or exhibit at least a 90
271  // degree bend
274  * trackStats.numShort_ + trackStats.numFast_) + trackStats.numBends_)
275  >= (trackObs.size() - 1)) {
276  oops::Log::trace() << "ShipTrackCheck: " << trackId << "\n" <<
277  "Time difference < 1 hour: " << trackStats.numShort_ << "\n" <<
278  "Fast: " << trackStats.numFast_ << "\n" <<
279  "Bends: " << trackStats.numBends_ << "\n" <<
280  "Total observations: " << trackObs.size() << "\n" <<
281  "Track was not checked." << std::endl;
282 
283  breakResult = true;
284  }
285  if (options_.testingMode)
286  diagnostics_->storeEarlyBreakResult(breakResult);
287  return breakResult;
288 }
289 
290 /// \brief Chooses which of the observations surrounding the speediest segment to remove,
291 /// flagging it accordingly.
293  std::vector<std::reference_wrapper<TrackObservation>> &track,
294  const std::vector<std::reference_wrapper<TrackObservation>>::iterator
295  &observationAfterFastestSegment,
296  bool firstIterativeRemoval, const std::string trackId) const {
297  int errorCategory = 0;
298  util::Duration four_days{"P4D"};
299  auto rejectedObservation = observationAfterFastestSegment;
300  // lambda function to "fail" an observation that should be rejected
301  auto fail = [&rejectedObservation](
302  const std::vector<std::reference_wrapper<TrackObservation>>::iterator &iter) {
303  iter->get().setRejected(true);
304  rejectedObservation = iter;
305  };
306  auto neighborObservationStatistics = [&observationAfterFastestSegment](int index) ->
307  const ObservationStatistics & {
308  return (observationAfterFastestSegment + index)->get().getObservationStatistics();
309  };
310  auto meanSpeed = observationAfterFastestSegment->get().getFullTrackStatistics()->meanSpeed_;
311  if (observationAfterFastestSegment == track.begin() + 1) {
312  // Decide whether ob 0 or 1 agrees best with ob 2
313  if (neighborObservationStatistics(0).speedAveraged <=
315  (neighborObservationStatistics(1).speed >
317  neighborObservationStatistics(1).angle > 45.0)) {
318  fail(observationAfterFastestSegment);
319  errorCategory = 2;
320  } else {
321  fail(observationAfterFastestSegment - 1);
322  errorCategory = 1;
323  }
324  } else if (observationAfterFastestSegment == track.end() - 1) {
325  if (neighborObservationStatistics(-1).speedAveraged <=
327  (neighborObservationStatistics(-1).speed >
329  neighborObservationStatistics(-2).angle > 45.0)) {
330  fail(observationAfterFastestSegment - 1);
331  errorCategory = 2;
332  } else {
333  fail(observationAfterFastestSegment);
334  errorCategory = 1;
335  }
336  } else if (neighborObservationStatistics(-1).speed >
338  fail(observationAfterFastestSegment - 1);
339  errorCategory = 4;
340  // Category 4: both segments surrounding observation have excessive speed
341  } else if (neighborObservationStatistics(1).speed >
343  fail(observationAfterFastestSegment);
344  errorCategory = 4;
345  } else if (neighborObservationStatistics(0).speedAveraged >
347  fail(observationAfterFastestSegment - 1);
348  errorCategory = 5;
349  // Category 5: observation before fastest segment would still begin a fast segment
350  // if observation after fastest segment were removed
351  } else if (neighborObservationStatistics(-1).speedAveraged >
353  fail(observationAfterFastestSegment);
354  errorCategory = 5;
355  // Category 5: observation after fastest segment would still end a fast segment if
356  // observation before fastest segment were removed
357  } else if (neighborObservationStatistics(-1).angle >
358  (45.0 + neighborObservationStatistics(0).angle)) {
359  fail(observationAfterFastestSegment - 1);
360  errorCategory = 6;
361  } else if (neighborObservationStatistics(0).angle >
362  45.0 + neighborObservationStatistics(-1).angle) {
363  fail(observationAfterFastestSegment);
364  errorCategory = 6;
365  } else if (neighborObservationStatistics(-2).angle > 45.0 &&
366  neighborObservationStatistics(-2).angle >
367  neighborObservationStatistics(1).angle) {
368  fail(observationAfterFastestSegment - 1);
369  errorCategory = 7;
370  } else if (neighborObservationStatistics(1).angle > 45.0) {
371  fail(observationAfterFastestSegment);
372  errorCategory = 7;
373  } else if (neighborObservationStatistics(-1).speed <
374  0.5 * std::min(
375  neighborObservationStatistics(1).speed,
376  meanSpeed)) {
377  fail(observationAfterFastestSegment - 1);
378  errorCategory = 8;
379  } else if (neighborObservationStatistics(1).speed <
380  0.5 * std::min(neighborObservationStatistics(-1).speed, meanSpeed)) {
381  fail(observationAfterFastestSegment);
382  errorCategory = 8;
383  } else {
384  double distanceSum = 0.0;
385  distanceSum = std::accumulate(observationAfterFastestSegment - 1,
386  observationAfterFastestSegment + 2, distanceSum,
387  [](double summed, TrackObservation& obs) {
388  return summed + obs.getObservationStatistics().distance;
389  });
390 
391  double distancePrevObsOmitted =
392  neighborObservationStatistics(-1).distanceAveraged +
393  neighborObservationStatistics(1).distance;
394  double distanceCurrentObsOmitted =
395  neighborObservationStatistics(-1).distance +
396  neighborObservationStatistics(0).distanceAveraged;
397  util::Duration timeSum = (observationAfterFastestSegment + 1)->get().getTime() - (
398  observationAfterFastestSegment - 2)->get().getTime();
399  if (options_.testingMode.value()) {
400  diagnostics_->storeDistanceSum(distanceSum);
401  diagnostics_->storeDistancePrevObsOmitted(distancePrevObsOmitted);
402  diagnostics_->storeDistanceCurrentObsOmitted(distanceCurrentObsOmitted);
403  double timeDouble = timeSum.toSeconds();
404  diagnostics_->storeTimeSum(timeDouble);
405  }
406  if (distancePrevObsOmitted < distanceCurrentObsOmitted - std::max(
407  options_.core.spatialResolution.value(), 0.1 * distanceSum)) {
408  fail(observationAfterFastestSegment - 1);
409  errorCategory = 9;
410  } else if (distanceCurrentObsOmitted < (
411  distancePrevObsOmitted - std::max(
412  options_.core.spatialResolution.value(), 0.1 * distanceSum))) {
413  fail(observationAfterFastestSegment);
414  errorCategory = 9;
415  } else if (timeSum <= four_days && timeSum.toSeconds() > 0 &&
416  std::min(distancePrevObsOmitted, distanceCurrentObsOmitted) > 0.0) {
417  double previousSegmentDistanceProportion =
418  // Prev segment dist/(prev segment distance + distAveragedCurrentObservation)
419  neighborObservationStatistics(-1).distance /
420  distanceCurrentObsOmitted;
421  double previousObservationDistanceAveragedProportion =
422  // Previous observation distAveraged / (prev obs distAveraged + next segment distance)
423  neighborObservationStatistics(-1).
424  distanceAveraged / distancePrevObsOmitted;
425  double previousSegmentTimeProportion =
426  static_cast<double>(((observationAfterFastestSegment - 1)->get().getTime() -
427  (observationAfterFastestSegment - 2)->get().getTime()).toSeconds()) /
428  timeSum.toSeconds();
429  double previousAndFastestSegmentTimeProportion =
430  static_cast<double>(((observationAfterFastestSegment->get().getTime()) -
431  (observationAfterFastestSegment - 2)->get().getTime()).toSeconds()) /
432  timeSum.toSeconds();
433  if (options_.testingMode.value()) {
434  diagnostics_->storePreviousSegmentDistanceProportion(previousSegmentDistanceProportion);
435  diagnostics_->storePreviousObservationDistanceAveragedProportion(
436  previousObservationDistanceAveragedProportion);
437  diagnostics_->storePreviousSegmentTimeProportion(previousSegmentTimeProportion);
438  diagnostics_->storePreviousAndFastestSegmentTimeProportion(
439  previousAndFastestSegmentTimeProportion);
440  }
441  if (std::abs(previousSegmentDistanceProportion - previousSegmentTimeProportion) >
442  0.1 + std::abs(previousObservationDistanceAveragedProportion -
443  previousAndFastestSegmentTimeProportion)) {
444  // previous segment's spatial and temporal lengths are significantly more disproportionate
445  // when compared to a larger portion of track
446  // than the equivalents for the post-fastest segment
447  fail(observationAfterFastestSegment - 1);
448  errorCategory = 10;
449  } else if (std::abs(previousObservationDistanceAveragedProportion -
450  previousAndFastestSegmentTimeProportion) > 0.1 +
451  std::abs(previousSegmentDistanceProportion - previousSegmentTimeProportion)) {
452  // next segment after fastest has spatial and temporal lengths that are significantly more
453  // disproportionate than the segment before the fastest
454  fail(observationAfterFastestSegment);
455  errorCategory = 10;
456  } else {
457  fail(observationAfterFastestSegment);
458  }
459  oops::Log::trace() << "CheckShipTrack: proportions " << previousSegmentDistanceProportion <<
460  " " << previousSegmentTimeProportion <<
461  " " << previousObservationDistanceAveragedProportion << " "
462  << previousAndFastestSegmentTimeProportion << " speeds: " << meanSpeed
463  << " " << neighborObservationStatistics(-1).speed << " " <<
464  neighborObservationStatistics(0).speed << " " <<
465  neighborObservationStatistics(1).speed << " [m/s]" << std::endl;
466  }
467  if (errorCategory == 9 || std::min(distancePrevObsOmitted, distanceCurrentObsOmitted) == 0.0) {
468  oops::Log::trace() << "CheckShipTrack: Dist check, station id: " <<
469  trackId << std::endl <<
470  " error category: " << errorCategory << std::endl <<
471  " distances: " << distanceSum * 0.001 << " " <<
472  distancePrevObsOmitted * 0.001 << " " <<
473  distanceCurrentObsOmitted * 0.001 << " " <<
474  (distancePrevObsOmitted - distanceCurrentObsOmitted) * 0.001 <<
475  " " << std::max(options_.core.spatialResolution.value(),
476  0.1 * distanceSum) *
477  0.001 << "[km]" << std::endl;
478  }
479  }
480  if (errorCategory == 0 || ((rejectedObservation->get().getObservationStatistics().
481  speedAveraged) >
482  options_.core.maxSpeed.value())) {
483  oops::Log::trace() << "CheckShipTrack: cannot decide between station id " <<
484  trackId << " observations " <<
485  (observationAfterFastestSegment - 1)->get().getObservationNumber() <<
486  " " << observationAfterFastestSegment->get().getObservationNumber() <<
487  " rejecting both." << std::endl;
488  errorCategory += 100;
489  if (options_.testingMode.value() && firstIterativeRemoval) {
490  std::vector<size_t> observationNumbersAroundFastest{
491  (observationAfterFastestSegment - 1)->get().getObservationNumber(),
492  observationAfterFastestSegment->get().getObservationNumber()};
493  diagnostics_->storeFirstIterativeRemovalInfo(
494  std::make_pair(observationNumbersAroundFastest, errorCategory));
495  }
496  if (rejectedObservation == observationAfterFastestSegment) {
497  fail(observationAfterFastestSegment - 1);
498  } else {
499  fail(observationAfterFastestSegment);
500  }
501  track.erase(observationAfterFastestSegment - 1, observationAfterFastestSegment);
502  } else {
503  if (options_.testingMode.value() && firstIterativeRemoval) {
504  std::vector<size_t> rejectedObservationNumber{rejectedObservation->get().
505  getObservationNumber()};
506  diagnostics_->storeFirstIterativeRemovalInfo(
507  std::make_pair(rejectedObservationNumber,
508  errorCategory));
509  }
510  oops::Log::trace() << "CheckShipTrack: rejecting station " << trackId << " observation " <<
511  rejectedObservation->get().getObservationNumber() << "\n" <<
512  "Error category: " << errorCategory << "\n" <<
513  "rejection candidates: " <<
514  (observationAfterFastestSegment - 1)->get().getObservationNumber() <<
515  " " << observationAfterFastestSegment->get().getObservationNumber() <<
516  "\n" << "speeds: " << (observationAfterFastestSegment - 1)->get().
517  getObservationStatistics().speed << " " <<
518  observationAfterFastestSegment->get().getObservationStatistics().speed <<
519  "\n" << (observationAfterFastestSegment - 1)->get().
520  getObservationStatistics().angle << " " <<
521  observationAfterFastestSegment->get().
522  getObservationStatistics().angle << "\n";
523  track.erase(rejectedObservation);
524  }
525 }
526 /// \todo Trace output will need to be changed to match that of OPS (indices, LWin)
527 
528 /// \brief Calculates all of the statistics that require only two
529 /// adjacent \p TrackObservations, storing within the righthand observation.
530 ///
531 /// This includes
532 /// distance between the two observations,
533 /// time difference between the observations, speed between the
534 /// observations, and if the
535 /// observations are recorded for the same time. Calls function to
536 /// increment track-wise counters based on
537 /// results.
539  TrackObservation& prevObs, bool firstIteration,
540  const TrackCheckShipParameters &options) {
541  this->setDistance(TrackCheckShip::distance(prevObs, *this));
542  (this->observationStatistics_.distance > options.core.spatialResolution) ?
543  this->setSpeed(TrackCheckShip::speedEstimate(*this, prevObs, options)) :
544  this->setSpeed(0.0);
545  this->setTimeDifference(getTime() - prevObs.getTime());
546  if (firstIteration) {
547  adjustTwoObservationStatistics(options);
548  }
549 }
550 
552  this->setDistance(0.0);
553  this->setSpeed(0.0);
554  this->setDistanceAveraged(0.0);
555  this->setSpeedAveraged(0.0);
556  this->setAngle(0.0);
557 }
558 
559 /// Calculates all of the statistics that require three
560 /// consecutive \p TrackObservations,
561 /// storing within the middle observation. This includes
562 /// distance between two alternating observations,
563 /// speed between these alternating observations (if the middle
564 /// observation was not recorded), and the
565 /// angle formed by the three-observation track segment.
566 /// Calls function to increment track-wise counters based on results.
568  const TrackObservation& prevObs, const TrackObservation& nextObs,
569  bool firstIteration, const TrackCheckShipParameters &options) {
570  this->setDistanceAveraged(TrackCheckShip::distance(prevObs, nextObs));
571  this->setSpeedAveraged(speedEstimate(prevObs, nextObs, options));
572  if (std::min(this->observationStatistics_.distance,
574  options.core.spatialResolution) {
575  this->observationStatistics_.angle = angle(prevObs, *this, nextObs);
576  }
577  if (firstIteration) {
578  adjustThreeObservationStatistics();
579  }
580 }
581 
582 /// This performs all of the necessary calculations based on the
583 /// observations' locations and timestamps by calling
584 /// \p calculateTwoObservationValues and \p calculateThreeObservationValues for the
585 /// non-edge-case observations.
587  const std::vector<std::reference_wrapper<TrackObservation>> &trackObservations,
588  CalculationMethod calculationMethod) const {
589  if (trackObservations.size()) {
590  if (calculationMethod == MAINLOOP)
591  trackObservations[0].get().resetObservationCalculations();
592  for (size_t obsIdx = 1; obsIdx < trackObservations.size(); obsIdx++) {
593  TrackObservation &obs = trackObservations[obsIdx].get();
594  TrackObservation &prevObs = trackObservations[obsIdx - 1].get();
595  obs.calculateTwoObservationValues(prevObs, calculationMethod == FIRSTITERATION, options_);
596  if (obsIdx > 1) {
597  const TrackObservation &prevPrevObs = trackObservations[obsIdx - 2].get();
598  prevObs.calculateThreeObservationValues(prevPrevObs, obs,
599  calculationMethod == FIRSTITERATION, options_);
600  }
601  if (calculationMethod == FIRSTITERATION && (obsIdx == trackObservations.size() - 1)) {
602  int potentialDenominator = trackObservations.size() - 1 -
603  obs.getFullTrackStatistics()->numShort_ - obs.getFullTrackStatistics()->numFast_;
604  (obs.getFullTrackStatistics()->meanSpeed_) = (obs.getFullTrackStatistics()->sumSpeed_) /
605  std::max(1, potentialDenominator);
606  }
607  }
608  if (options_.testingMode.value() && calculationMethod != MAINLOOP) {
609  std::vector<TrackCheckShip::ObservationStatistics> obsStats;
610  for (size_t obsIdx = 0; obsIdx < trackObservations.size(); ++obsIdx) {
611  obsStats.push_back(trackObservations[obsIdx].get().getObservationStatistics());
612  }
613  auto trackStats = *(trackObservations[0].get().getFullTrackStatistics());
614  if (calculationMethod == FIRSTITERATION)
615  diagnostics_->storeInitialCalculationResults(std::make_pair(obsStats, trackStats));
616  }
617  }
618 }
619 
620 /// \brief Keeps track of 0-distanced, short, and fast track segments,
621 /// as well as incrementing \p sumSpeed_ for normal track segments.
623 (const TrackCheckShipParameters &options) const {
624  util::Duration hour{"PT1H"};
625  if (getObservationStatistics().timeDifference < hour) {
626  getFullTrackStatistics()->numShort_++;
627  } else if (getObservationStatistics().speed >= options.core.maxSpeed) {
628  getFullTrackStatistics()->numFast_++;
629  } else {
630  getFullTrackStatistics()->sumSpeed_ += getObservationStatistics().speed;
631  }
632 }
633 
634 /// \brief Increments number of bends if angle is greater than or equal to 90 degrees
636  if (getObservationStatistics().angle >= 90.0)
637  getFullTrackStatistics()->numBends_++;
638 }
639 
642 {
643  return observationStatistics_;
644 }
645 
646 const std::shared_ptr<TrackCheckShip::TrackStatistics>
648 {
649  return fullTrackStatistics_;
650 }
651 
653  this->observationStatistics_.distance = dist;
654 }
656  this->observationStatistics_.timeDifference = tDiff;
657 }
659  this->observationStatistics_.speed = speed;
660 }
662  this->observationStatistics_.angle = angle;
663 }
665  this->observationStatistics_.distanceAveraged = distAvg;
666 }
668  this->observationStatistics_.speedAveraged = speedAvg;
669 }
670 } // namespace ufo
Base class for UFO QC filters.
Definition: FilterBase.h:45
This class provides access to observations that may be held on multiple MPI ranks.
Definition: ObsAccessor.h:56
RecursiveSplitter splitObservationsIntoIndependentGroups(const std::vector< size_t > &validObsIds, bool opsCompatibilityMode=false) const
Definition: ObsAccessor.cc:197
std::vector< size_t > getValidObservationIds(const std::vector< bool > &apply, const ioda::ObsDataVector< int > &flags, const Variables &filtervars, bool validIfAnyFilterVariablePassedQC=true) const
Return the IDs of observation locations that should be treated as valid by a filter.
Definition: ObsAccessor.cc:99
void flagRejectedObservations(const std::vector< bool > &isRejected, std::vector< std::vector< bool > > &flagged) const
Update flags of observations held on the current MPI rank.
Definition: ObsAccessor.cc:243
ioda::ObsSpace & obsdb_
std::shared_ptr< ioda::ObsDataVector< int > > flags_
Partitions an array into groups of elements equivalent according to certain criteria.
MultiElementGroupRange multiElementGroups() const
Return the range of equivalence classes consisting of more than one element.
const ObservationStatistics & getObservationStatistics() const
TrackObservation(double latitude, double longitude, const util::DateTime &time, const std::shared_ptr< TrackStatistics > &trackStatistics, const std::shared_ptr< TrackCheckUtils::CheckCounter > &checkCounter, size_t observationNumber)
void calculateThreeObservationValues(const TrackObservation &prevObs, const TrackObservation &nextObs, bool firstIteration, const TrackCheckShipParameters &options)
const std::shared_ptr< TrackStatistics > getFullTrackStatistics() const
void adjustThreeObservationStatistics() const
Increments number of bends if angle is greater than or equal to 90 degrees.
void setTimeDifference(util::Duration tDiff)
void calculateTwoObservationValues(TrackObservation &prevObs, bool firstIteration, const TrackCheckShipParameters &options)
Calculates all of the statistics that require only two adjacent TrackObservations,...
void adjustTwoObservationStatistics(const TrackCheckShipParameters &options) const
Keeps track of 0-distanced, short, and fast track segments, as well as incrementing sumSpeed_ for nor...
const TrackCheckUtils::Point & getLocation() const
oops::RequiredParameter< double > spatialResolution
oops::RequiredParameter< float > rejectionThreshold
oops::RequiredParameter< util::Duration > temporalResolution
oops::RequiredParameter< double > maxSpeed
Maximum speed (before marking as fast) in m/s.
~TrackCheckShip() override
void removeFaultyObservation(std::vector< std::reference_wrapper< TrackObservation > > &track, const std::vector< std::reference_wrapper< TrackObservation > >::iterator &it, bool firstIterativeRemoval, const std::string trackId) const
Chooses which of the observations surrounding the speediest segment to remove, flagging it accordingl...
void calculateTrackSegmentProperties(const std::vector< std::reference_wrapper< TrackObservation >> &trackObservations, CalculationMethod calculationMethod=MAINLOOP) const
TrackCheckShip(ioda::ObsSpace &obsdb, const Parameters_ &parameters, std::shared_ptr< ioda::ObsDataVector< int > > flags, std::shared_ptr< ioda::ObsDataVector< float > > obserr)
void print(std::ostream &) const override
static double distance(const TrackObservation &a, const TrackObservation &b)
Extends distance for TrackObservation inputs a and b.
static double speedEstimate(const TrackCheckShip::TrackObservation &obs1, const TrackCheckShip::TrackObservation &obs2, const TrackCheckShipParameters &options)
Estimate of speed as calculated in Ops_CheckShipTrack.
std::vector< TrackObservation > collectTrackObservations(std::vector< size_t >::const_iterator trackObsIndicesBegin, std::vector< size_t >::const_iterator trackObsIndicesEnd, const std::vector< size_t > &validObsIds, const TrackCheckUtils::ObsGroupLocationTimes &obsLoc) const
std::unique_ptr< TrackCheckShipDiagnostics > diagnostics_
bool earlyBreak(const std::vector< std::reference_wrapper< TrackObservation >> &trackObs, const std::string trackId) const
void flagRejectedTrackObservations(std::vector< size_t >::const_iterator trackObsIndicesBegin, std::vector< size_t >::const_iterator trackObsIndicesEnd, const std::vector< size_t > &validObsIds, const std::vector< TrackObservation > &trackObservations, std::vector< bool > &isRejected) const
static float angle(const TrackCheckShip::TrackObservation &a, const TrackCheckShip::TrackObservation &b, const TrackCheckShip::TrackObservation &c)
Returns the angle in degrees (rounded to the nearest .1 degree) between displacement vectors going fr...
void applyFilter(const std::vector< bool > &, const Variables &, std::vector< std::vector< bool >> &) const override
const TrackCheckShipDiagnostics * diagnostics() const
Options controlling the operation of the ship track check filter.
oops::Parameter< bool > testingMode
To be set to true if the filter's unit tests are being run.
TrackCheckShipCoreParameters core
oops::Parameter< SurfaceObservationSubtype > inputCategory
Locations/times of all observations processed by the track checking filter.
std::vector< util::DateTime > datetimes
oops::OptionalParameter< Variable > stationIdVariable
constexpr int track
Definition: QCflags.h:31
ObsGroupLocationTimes collectObservationsLocations(const ObsAccessor &obsAccessor)
ObsAccessor createObsAccessor(const boost::optional< Variable > &stationIdVariable, const ioda::ObsSpace &obsdb)
Create an ObsAccessor object providing access to observations that need to be checked by the current ...
void sortTracksChronologically(const std::vector< size_t > &validObsIds, const ObsAccessor &obsAccessor, RecursiveSplitter &splitter)
Definition: RunCRTM.h:27
util::Duration abs(const util::Duration &duration)
static constexpr double rad2deg
Definition: Constants.h:22
Relevant calculated values that are specific to each pair or triplet of adjacent/alternating observat...
A container for all track-wise counters and calculations that indicate the overall quality of the tra...