Ocean
SampleMap.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  */
7 
8 #ifndef META_OCEAN_MATH_SAMPLE_MAP_H
9 #define META_OCEAN_MATH_SAMPLE_MAP_H
10 
11 #include "ocean/math/Math.h"
13 #include "ocean/math/Numeric.h"
14 
15 #include "ocean/base/Lock.h"
16 
17 namespace Ocean
18 {
19 
20 /**
21  * This class stores samples of e.g., sensor or tracking data in a map.
22  * Samples are stored in a round robin method.<br>
23  * Whenever the map does not have an empty spot left, the oldest sample will be replaced by the newest sample.<br>
24  * The implementation is thread-safe.
25  * @tparam T The type of the sample to be stored
26  * @ingroup math
27  */
28 template <typename T>
29 class SampleMap
30 {
31  public:
32 
33  /**
34  * Definition of a map mapping timestamps to samples.
35  */
36  typedef std::map<double, T> Map;
37 
38  /**
39  * Definition of individual interpolation strategies for samples.
40  */
42  {
43  /// An invalid strategy.
45  /// The sample with nearest/closest timestamp is used.
47  /// The sample is interpolated based on two samples.
49  };
50 
51  public:
52 
53  /**
54  * Creates a new map with default capacity.
55  */
56  SampleMap() noexcept;
57 
58  /**
59  * Creates a new amp with specified capacity.
60  * @param capacity The number of samples the new map can store, with range [1, infinity), -1 to create a map without capacity restrictions
61  */
62  explicit SampleMap(const size_t capacity) noexcept;
63 
64  /**
65  * Copy constructor.
66  * @param sampleMap The map to be copied
67  */
68  SampleMap(const SampleMap& sampleMap) noexcept;
69 
70  /**
71  * Move constructor.
72  * @param sampleMap The map to be moved
73  */
74  SampleMap(SampleMap&& sampleMap) noexcept;
75 
76  /**
77  * Inserts a new sample with corresponding timestamp.
78  * In case, a sample with same timestamp exists already, the given sample will replace the existing sample.<br>
79  * In case, the map does not have an empty spot left, the oldest sample will be replaced by the given sample.
80  * @param sample The new sample to be inserted, must be valid
81  * @param timestamp The timestamp of the sample, must be valid
82  */
83  void insert(const T& sample, const double& timestamp);
84 
85  /**
86  * Returns the most recent sample.
87  * @param value The resulting sample
88  * @param sampleTimestamp The timestamp of the returning sample; nullptr if not of interest
89  * @return True, if succeeded
90  */
91  bool sample(T& value, double* sampleTimestamp = nullptr) const;
92 
93  /**
94  * Returns the sample with a specific timestamp.
95  * @param timestamp Timestamp of the sample to be returned
96  * @param value The resulting sample with the requested timestamp
97  * @return True, if succeeded (if a sample with the timestamp existed)
98  */
99  bool sample(const double& timestamp, T& value) const;
100 
101  /**
102  * Returns the sample best matching with a specified timestamp.
103  * In case, the given timestamp does not fit to an existing sample, the resulting sample will be based on the specified interpolation strategy.
104  * @param timestamp The timestamp for which a sample will be determined, must be valid
105  * @param interpolationStrategy The interpolation strategy to be applied in case the timestamp does not fit with an existing sample
106  * @param value The resulting sample
107  * @param timestampDistance Optional resulting minimal time distance between the requested timestamp and the sample(s) used to create the resulting value, with range [0, infinity)
108  * @param sampleTimestamp The timestamp of the returning sample; nullptr if not of interest
109  * @return True, if a sample could be determined
110  */
111  bool sample(const double& timestamp, const InterpolationStrategy interpolationStrategy, T& value, double* timestampDistance = nullptr, double* sampleTimestamp = nullptr) const;
112 
113  /**
114  * Returns all samples stores in this map.
115  * @return All samples as vector of pairs
116  */
117  std::vector<std::pair<double, T>> samples() const;
118 
119  /**
120  * Returns all samples stores in this map.
121  * @return All samples as a standard map
122  */
123  Map data() const;
124 
125  /**
126  * Returns the number of samples currently stored in this map.
127  * @return The map's samples, with range [0, capacity()]
128  */
129  inline size_t size() const;
130 
131  /**
132  * Returns the capacity of this map (the number of samples this map can store).
133  * The capacity of this map, with range [1, infinity)
134  */
135  inline size_t capacity() const;
136 
137  /**
138  * Returns whether this map is empty.
139  * @return True, if so
140  */
141  inline bool isEmpty() const;
142 
143  /**
144  * Removes all samples from this map.
145  */
146  inline void clear();
147 
148  /**
149  * Copy operator.
150  * @param sampleMap The map to be copied
151  * @return Reference to this object
152  */
153  SampleMap<T>& operator=(const SampleMap<T>& sampleMap) noexcept;
154 
155  /**
156  * Move operator.
157  * @param sampleMap The map to be moved
158  * @return Reference to this object
159  */
160  SampleMap<T>& operator=(SampleMap<T>&& sampleMap) noexcept;
161 
162  protected:
163 
164  /// The map holding the actual samples.
166 
167  /// The capacity of this map, with range [1, infinity).
168  size_t capacity_;
169 
170  /// The lock of this map.
171  mutable Lock lock_;
172 };
173 
174 template <typename T>
175 SampleMap<T>::SampleMap() noexcept :
176  capacity_(100)
177 {
178  // nothing to do here
179 }
180 
181 template <typename T>
182 SampleMap<T>::SampleMap(const size_t capacity) noexcept :
184 {
185  ocean_assert(capacity_ >= 1);
186 }
187 
188 template <typename T>
189 SampleMap<T>::SampleMap(const SampleMap& sampleMap) noexcept :
190  map_(sampleMap.map_),
191  capacity_(sampleMap.capacity_)
192 {
193  // nothing to do here
194 }
195 
196 template <typename T>
197 SampleMap<T>::SampleMap(SampleMap&& sampleMap) noexcept
198 {
199  *this = std::move(sampleMap);
200 }
201 
202 template <typename T>
203 void SampleMap<T>::insert(const T& sample, const double& timestamp)
204 {
205  const ScopedLock scopedLock(lock_);
206 
207  if (map_.size() >= capacity_)
208  {
209  typename Map::iterator i = map_.begin();
210  ocean_assert(i != map_.end());
211 
212  map_.erase(i);
213  }
214 
215  map_[timestamp] = sample; // not using insert to ensure that identical timestamps overwrite previous entries
216 }
217 
218 template <typename T>
219 bool SampleMap<T>::sample(T& value, double* sampleTimestamp) const
220 {
221  const ScopedLock scopedLock(lock_);
222 
223  // looking for the most recent sample
224  typename Map::const_reverse_iterator i = map_.rbegin();
225 
226  if (i == map_.rend())
227  {
228  return false;
229  }
230 
231  value = i->second;
232 
233  if (sampleTimestamp)
234  {
235  *sampleTimestamp = i->first;
236  }
237 
238  return true;
239 }
240 
241 template <typename T>
242 bool SampleMap<T>::sample(const double& timestamp, T& value) const
243 {
244  const ScopedLock scopedLock(lock_);
245 
246  const typename Map::const_iterator i = map_.find(timestamp);
247 
248  if (i == map_.cend())
249  {
250  return false;
251  }
252 
253  value = i->second;
254 
255  return true;
256 }
257 
258 template <typename T>
259 bool SampleMap<T>::sample(const double& timestamp, const InterpolationStrategy interpolationStrategy, T& value, double* timestampDistance, double* sampleTimestamp) const
260 {
261  const ScopedLock scopedLock(lock_);
262 
263  if (map_.empty())
264  {
265  return false;
266  }
267 
268  if (map_.size() == 1)
269  {
270  // we just have one sample, so we return this one without any further handling
271 
272  ocean_assert(map_.rbegin() != map_.rend());
273  value = map_.rbegin()->second;
274 
275  if (timestampDistance)
276  {
277  // distance is |timestamp - sample.timestamp|
278  *timestampDistance = NumericD::abs(timestamp - map_.rbegin()->first);
279  }
280 
281  if (sampleTimestamp)
282  {
283  *sampleTimestamp = map_.rbegin()->first;
284  }
285 
286  return true;
287  }
288 
289  // let's find the sample with timestamp bigger (younger) than the specified timestamp
290  const typename Map::const_iterator iUpper = map_.upper_bound(timestamp);
291 
292  if (iUpper == map_.cend())
293  {
294  // our timestamp is too new so that we simply return the sample of the most recent timestamp
295 
296  ocean_assert(map_.rbegin() != map_.rend());
297  value = map_.rbegin()->second;
298 
299  if (timestampDistance)
300  {
301  // distance is |timestamp - newestSample.timestamp|
302  *timestampDistance = NumericD::abs(timestamp - map_.rbegin()->first);
303  }
304 
305  if (sampleTimestamp)
306  {
307  *sampleTimestamp = map_.rbegin()->first;
308  }
309 
310  return true;
311  }
312 
313  ocean_assert(iUpper->first > timestamp);
314 
315  if (iUpper == map_.cbegin())
316  {
317  // our timestamp is too old so that we simply return the oldest sample we have
318 
319  ocean_assert(map_.begin() != map_.end());
320 
321  value = map_.begin()->second;
322 
323  if (timestampDistance)
324  {
325  // distance is |timestamp - oldestSample.timestamp|
326  *timestampDistance = NumericD::abs(timestamp - map_.begin()->first);
327  }
328 
329  if (sampleTimestamp)
330  {
331  *sampleTimestamp = map_.begin()->first;
332  }
333 
334  return true;
335  }
336 
337  typename Map::const_iterator iLower(iUpper);
338 
339  ocean_assert(iLower != map_.begin());
340  iLower--;
341 
342  ocean_assert(iLower->first <= timestamp);
343 
344  const double lowerDelta = timestamp - iLower->first;
345  const double upperDelta = iUpper->first - timestamp;
346  ocean_assert(lowerDelta >= 0.0 && upperDelta >= 0.0);
347 
348  if (interpolationStrategy == IS_TIMESTAMP_INTERPOLATE)
349  {
350  const double delta = lowerDelta + upperDelta;
351 
352  if (NumericD::isEqualEps(delta))
353  {
354  // both samples are almost identical, so that we return the sample from the past (not from the future)
355  value = iLower->second;
356 
357  if (timestampDistance)
358  {
359  // distance is 0
360  *timestampDistance = 0.0;
361  }
362 
363  if (sampleTimestamp)
364  {
365  *sampleTimestamp = iLower->first;
366  }
367 
368  return true;
369  }
370 
371  const double interpolationFactor = lowerDelta / delta;
372  ocean_assert(interpolationFactor >= 0.0 && interpolationFactor <= 1.0);
373 
374  const T& lowerSample = iLower->second;
375  const T& upperSample = iUpper->second;
376 
377  value = Interpolation::linear(lowerSample, upperSample, interpolationFactor);
378 
379  if (timestampDistance)
380  {
381  // distance is min(|upperTimestamp - timestamp|, |timestamp - lowerTimestamp|)
382  *timestampDistance = std::min(iUpper->first - timestamp, timestamp - iLower->first);
383  }
384 
385  if (sampleTimestamp)
386  {
387  *sampleTimestamp = iLower->first * interpolationFactor + iUpper->first * (1.0 - interpolationFactor);
388  }
389 
390  return true;
391  }
392 
393  ocean_assert(interpolationStrategy == IS_TIMESTAMP_NEAREST);
394 
395  // let's return the sample with timestamp closest to the specified timestamp
396 
397  if (lowerDelta < upperDelta)
398  {
399  value = iLower->second;
400 
401  if (timestampDistance)
402  {
403  // distance is min(|upperTimestamp - timestamp|, |timestamp - lowerTimestamp|)
404  *timestampDistance = std::min(iUpper->first - timestamp, timestamp - iLower->first);
405  }
406 
407  if (sampleTimestamp)
408  {
409  *sampleTimestamp = iLower->first;
410  }
411 
412  return true;
413  }
414 
415  value = iUpper->second;
416 
417  if (timestampDistance)
418  {
419  // distance is min(|upperTimestamp - timestamp|, |timestamp - lowerTimestamp|)
420  *timestampDistance = std::min(iUpper->first - timestamp, timestamp - iLower->first);
421  }
422 
423  if (sampleTimestamp)
424  {
425  *sampleTimestamp = iUpper->first;
426  }
427 
428  return true;
429 }
430 
431 template <typename T>
432 std::vector<std::pair<double, T>> SampleMap<T>::samples() const
433 {
434  const ScopedLock scopedLock(lock_);
435 
436  std::vector<std::pair<double, T>> result;
437  result.reserve(map_.size());
438 
439  for (typename Map::const_iterator i = map_.cbegin(); i != map_.cend(); ++i)
440  {
441  result.emplace_back(i->first, i->second);
442  }
443 
444  return result;
445 }
446 
447 template <typename T>
449 {
450  const ScopedLock scopedLock(lock_);
451 
452  Map result(map_);
453 
454  return result;
455 }
456 
457 template <typename T>
458 inline size_t SampleMap<T>::size() const
459 {
460  const ScopedLock scopedLock(lock_);
461 
462  return map_.size();
463 }
464 
465 template <typename T>
466 inline size_t SampleMap<T>::capacity() const
467 {
468  const ScopedLock scopedLock(lock_);
469 
470  return capacity_;
471 }
472 
473 template <typename T>
474 inline bool SampleMap<T>::isEmpty() const
475 {
476  const ScopedLock scopedLock(lock_);
477 
478  return map_.empty();
479 }
480 
481 template <typename T>
482 inline void SampleMap<T>::clear()
483 {
484  const ScopedLock scopedLock(lock_);
485 
486  map_.clear();
487 }
488 
489 template <typename T>
491 {
492  map_ = sampleMap.map_;
493  capacity_ = sampleMap.capacity_;
494 
495  return *this;
496 }
497 
498 template <typename T>
500 {
501  if (this != &sampleMap)
502  {
503  map_ = std::move(sampleMap.map_);
504 
505  capacity_ = sampleMap.capacity_;
506  sampleMap.capacity_ = 0;
507  }
508 
509  return *this;
510 }
511 
512 }
513 
514 #endif // META_OCEAN_MATH_SAMPLE_MAP_H
static T linear(const T &v0, const T &v1, const TFactor &t)
Performs a linear interpolation between two values.
Definition: Interpolation.h:508
This class implements a recursive lock object.
Definition: Lock.h:31
static T abs(const T value)
Returns the absolute value of a given value.
Definition: Numeric.h:1220
static constexpr bool isEqualEps(const T value)
Returns whether a value is smaller than or equal to a small epsilon.
Definition: Numeric.h:2087
This class stores samples of e.g., sensor or tracking data in a map.
Definition: SampleMap.h:30
Lock lock_
The lock of this map.
Definition: SampleMap.h:171
size_t size() const
Returns the number of samples currently stored in this map.
Definition: SampleMap.h:458
Map data() const
Returns all samples stores in this map.
Definition: SampleMap.h:448
std::map< double, T > Map
Definition of a map mapping timestamps to samples.
Definition: SampleMap.h:36
bool isEmpty() const
Returns whether this map is empty.
Definition: SampleMap.h:474
Map map_
The map holding the actual samples.
Definition: SampleMap.h:165
void clear()
Removes all samples from this map.
Definition: SampleMap.h:482
std::vector< std::pair< double, T > > samples() const
Returns all samples stores in this map.
Definition: SampleMap.h:432
void insert(const T &sample, const double &timestamp)
Inserts a new sample with corresponding timestamp.
Definition: SampleMap.h:203
SampleMap< T > & operator=(const SampleMap< T > &sampleMap) noexcept
Copy operator.
Definition: SampleMap.h:490
size_t capacity() const
Returns the capacity of this map (the number of samples this map can store).
Definition: SampleMap.h:466
bool sample(T &value, double *sampleTimestamp=nullptr) const
Returns the most recent sample.
Definition: SampleMap.h:219
SampleMap() noexcept
Creates a new map with default capacity.
Definition: SampleMap.h:175
InterpolationStrategy
Definition of individual interpolation strategies for samples.
Definition: SampleMap.h:42
@ IS_TIMESTAMP_NEAREST
The sample with nearest/closest timestamp is used.
Definition: SampleMap.h:46
@ IS_INVALID
An invalid strategy.
Definition: SampleMap.h:44
@ IS_TIMESTAMP_INTERPOLATE
The sample is interpolated based on two samples.
Definition: SampleMap.h:48
size_t capacity_
The capacity of this map, with range [1, infinity).
Definition: SampleMap.h:168
This class implements a scoped lock object for recursive lock objects.
Definition: Lock.h:135
The namespace covering the entire Ocean framework.
Definition: Accessor.h:15