MRPT  1.9.9
CMultiMetricMap.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | http://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2018, Individual contributors, see AUTHORS file |
6  | See: http://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in http://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 #ifndef CMultiMetricMap_H
10 #define CMultiMetricMap_H
11 
13 #include <mrpt/maps/COctoMap.h>
24 #include <mrpt/maps/CBeaconMap.h>
25 #include <mrpt/maps/CMetricMap.h>
30 #include <mrpt/obs/obs_frwds.h>
31 
32 namespace mrpt::maps
33 {
34 class TSetOfMetricMapInitializers;
35 
36 /** This class stores any customizable set of metric maps.
37  * The internal metric maps can be accessed directly by the user as smart
38  *pointers with CMultiMetricMap::getMapByIndex() or via `iterator`s.
39  * The utility of this container is to operate on several maps simultaneously:
40  *update them by inserting observations,
41  * evaluate the likelihood of one observation by fusing (multiplying) the
42  *likelihoods over the different maps, etc.
43  *
44  * <b>These kinds of metric maps can be kept inside</b> (list may be
45  *incomplete, refer to classes derived from mrpt::maps::CMetricMap):
46  * - mrpt::maps::CSimplePointsMap: For 2D or 3D range scans, ...
47  * - mrpt::maps::COccupancyGridMap2D: 2D, <b>horizontal</b> laser range
48  *scans, at different altitudes.
49  * - mrpt::maps::COctoMap: For 3D occupancy grids of variable resolution,
50  *with octrees (based on the library `octomap`).
51  * - mrpt::maps::CColouredOctoMap: The same than above, but nodes can store
52  *RGB data appart from occupancy.
53  * - mrpt::maps::CLandmarksMap: For visual landmarks,etc...
54  * - mrpt::maps::CGasConcentrationGridMap2D: For gas concentration maps.
55  * - mrpt::maps::CWirelessPowerGridMap2D: For wifi power maps.
56  * - mrpt::maps::CBeaconMap: For range-only SLAM.
57  * - mrpt::maps::CHeightGridMap2D: For elevation maps of height for each
58  *(x,y) location (Digital elevation model, DEM)
59  * - mrpt::maps::CHeightGridMap2D_MRF: DEMs as Markov Random Field (MRF)
60  * - mrpt::maps::CReflectivityGridMap2D: For maps of "reflectivity" for
61  *each
62  *(x,y) location.
63  * - mrpt::maps::CColouredPointsMap: For point map with color.
64  * - mrpt::maps::CWeightedPointsMap: For point map with weights (capable of
65  *"fusing").
66  *
67  * See CMultiMetricMap::setListOfMaps() for the method for initializing this
68  *class programmatically.
69  * See also TSetOfMetricMapInitializers::loadFromConfigFile for a template of
70  *".ini"-like configuration
71  * file that can be used to define which maps to create and all their
72  *parameters.
73  * Alternatively, the list of maps is public so it can be directly
74  *manipulated/accessed in CMultiMetricMap::maps
75  *
76  * Configuring the list of maps: Alternatives
77  * --------------------------------------------
78  *
79  * **Method #1: Using map definition structures**
80  * \code
81  * mrpt::maps::TSetOfMetricMapInitializers map_inits;
82  * {
83  * mrpt::maps::COccupancyGridMap2D::TMapDefinition def;
84  * def.resolution = 0.05;
85  * def.insertionOpts.maxOccupancyUpdateCertainty = 0.8;
86  * def.insertionOpts.maxDistanceInsertion = 30;
87  * map_inits.push_back(def);
88  * }
89  * {
90  * mrpt::maps::CSimplePointsMap::TMapDefinition def;
91  * map_inits.push_back(def);
92  * }
93  * mrpt::maps::CMultiMetricMap theMap;
94  * theMap.setListOfMaps(map_inits);
95  * \endcode
96  *
97  * **Method #2: Using a configuration file**
98  * See TSetOfMetricMapInitializers::loadFromConfigFile() for details on expected
99  *file format.
100  *
101  * \code
102  * mrpt::config::CConfigFile cfgFile("file.cfg");
103  * mrpt::maps::TSetOfMetricMapInitializers map_inits;
104  * map_inits.loadFromConfigFile(cfgFile, "MapDefinition");
105  *
106  * mrpt::maps::CMultiMetricMap theMap;
107  * theMap.setListOfMaps(map_inits);
108  * \endcode
109  *
110  * **Method #3: Manual manipulation**
111  *
112  * \code
113  * mrpt::maps::CMultiMetricMap theMap;
114  * {
115  * mrpt::maps::CSimplePointsMap::Ptr ptMap =
116  *mrpt::make_aligned_shared<mrpt::maps::CSimplePointsMap>();
117  * theMap.maps.push_back(ptMap);
118  * }
119  * \endcode
120  *
121  * \note [New in MRPT 1.3.0]: `likelihoodMapSelection`, which selected the map
122  *to be used when
123  * computing the likelihood of an observation, has been removed. Use the
124  *`enableObservationLikelihood`
125  * property of each individual map declaration.
126  *
127  * \note [New in MRPT 1.3.0]: `enableInsertion_{pointsMap,...}` have been also
128  *removed.
129  * Use the `enableObservationInsertion` property of each map declaration.
130  *
131  * \note [New in MRPT 1.3.0]: Plain list of maps is exposed in `maps` member.
132  *Proxies named `m_pointsMaps`,`m_gridMaps`, etc.
133  * are provided for backwards-compatibility and for their utility.
134  *
135  * \note This class belongs to [mrpt-slam] instead of [mrpt-maps] due to the
136  *dependency on map classes in mrpt-vision.
137  * \sa CMetricMap \ingroup mrpt_slam_grp
138  */
140 {
142  protected:
143  /** Deletes all maps and clears the internal lists of maps (with
144  * clear_unique(), so user copies remain alive) */
145  void deleteAllMaps();
146  /** Clear all elements of the map. */
147  void internal_clear() override;
148  // See base class docs
150  const mrpt::obs::CObservation* obs,
151  const mrpt::poses::CPose3D* robotPose = nullptr) override;
152  /** Returns true if any of the inner maps is able to compute a sensible
153  * likelihood function for this observation.
154  * \param obs The observation.
155  * \sa computeObservationLikelihood
156  */
158  const mrpt::obs::CObservation* obs) const override;
159  // See docs in base class
161  const mrpt::obs::CObservation* obs,
162  const mrpt::poses::CPose3D& takenFrom) override;
163 
164  public:
165  /** @name Access to internal list of maps: direct list, iterators, utility
166  methods and proxies
167  @{ */
168  using TListMaps = std::deque<
170 
171  /** The list of MRPT metric maps in this object. Use dynamic_cast or smart
172  * pointer-based downcast to access maps by their actual type.
173  * You can directly manipulate this list. Helper methods to initialize it
174  * are described in the docs of CMultiMetricMap
175  */
177 
180  iterator begin() { return maps.begin(); }
181  const_iterator begin() const { return maps.begin(); }
182  iterator end() { return maps.end(); }
183  const_iterator end() const { return maps.end(); }
184  /** Gets the i-th map \exception std::runtime_error On out-of-bounds */
185  mrpt::maps::CMetricMap::Ptr getMapByIndex(size_t idx) const;
186 
187  /** Returns the i'th observation of a given class (or of a descendant
188  * class), or nullptr if there is no such observation in the array.
189  * Example:
190  * \code
191  * CObservationImage::Ptr obs =
192  * m_SF->getObservationByClass<CObservationImage>();
193  * \endcode
194  * By default (ith=0), the first observation is returned.
195  */
196  template <typename T>
197  typename T::Ptr getMapByClass(const size_t& ith = 0) const
198  {
199  size_t foundCount = 0;
200  const mrpt::rtti::TRuntimeClassId* class_ID =
201  &T::GetRuntimeClassIdStatic();
202  for (const_iterator it = begin(); it != end(); ++it)
203  if ((*it)->GetRuntimeClass()->derivedFrom(class_ID))
204  if (foundCount++ == ith)
205  return std::dynamic_pointer_cast<T>(it->get_ptr());
206  return typename T::Ptr(); // Not found: return empty smart pointer
207  }
208 
209  /** Takes a const ref of a STL non-associative container of smart pointers
210  * at construction and exposes an interface
211  * mildly similar to that of another STL container containing only those
212  * elements
213  * in the original container that can be `dynamic_cast`ed to
214  * `SELECTED_CLASS_PTR` */
215  template <class SELECTED_CLASS_PTR, class CONTAINER>
217  {
218  using ptr_t = typename SELECTED_CLASS_PTR::element_type*;
219  using const_ptr_t = const typename SELECTED_CLASS_PTR::element_type*;
223  : m_source()
224  {
225  } // m_source init in parent copy ctor
226 
229  {
230  return *this;
231  } // Do nothing, we must keep refs to our own parent
234  : m_source()
235  {
236  } // m_source init in parent copy ctor
239  {
240  return *this;
241  } // Do nothing, we must keep refs to our own parent
242  bool empty() const { return size() == 0; }
243  size_t size() const
244  {
245  size_t cnt = 0;
246  for (typename CONTAINER::const_iterator it = m_source->begin();
247  it != m_source->end(); ++it)
248  if (dynamic_cast<const_ptr_t>(it->get())) cnt++;
249  return cnt;
250  }
251  SELECTED_CLASS_PTR operator[](size_t index) const
252  {
253  size_t cnt = 0;
254  for (typename CONTAINER::const_iterator it = m_source->begin();
255  it != m_source->end(); ++it)
256  if (dynamic_cast<const_ptr_t>(it->get()))
257  if (cnt++ == index)
258  {
259  return std::dynamic_pointer_cast<
260  typename SELECTED_CLASS_PTR::element_type>(
261  it->get_ptr());
262  }
263  throw std::out_of_range("Index is out of range");
264  }
265  template <typename ELEMENT>
266  void push_back(const ELEMENT& element)
267  {
268  m_source->push_back(element);
269  }
270 
271  private:
272  CONTAINER* m_source;
273  }; // end ProxyFilterContainerByClass
274 
275  /** A proxy like ProxyFilterContainerByClass, but it directly appears as if
276  * it was a single smart pointer (empty if no matching object is found in
277  * the container) */
278  template <class SELECTED_CLASS_PTR, class CONTAINER>
280  {
281  using pointee_t = typename SELECTED_CLASS_PTR::element_type;
282  using ptr_t = typename SELECTED_CLASS_PTR::element_type*;
283  using const_ptr_t = const typename SELECTED_CLASS_PTR::element_type*;
287  : m_source()
288  {
289  } // m_source init in parent copy ctor
292  o)
293  {
294  return *this;
295  } // Do nothing, we must keep refs to our own parent
298  : m_source()
299  {
300  } // m_source init in parent copy ctor
303  {
304  return *this;
305  } // Do nothing, we must keep refs to our own parent
306 
307  operator const SELECTED_CLASS_PTR&() const
308  {
310  return m_ret;
311  }
312  explicit operator bool() const
313  {
315  return m_ret ? true : false;
316  }
317  ptr_t get()
318  {
320  return m_ret.get();
321  }
323  {
325  if (m_ret)
326  return m_ret.get();
327  else
328  throw std::runtime_error("Tried to derefer nullptr pointer");
329  }
331  {
333  if (m_ret)
334  return *m_ret.get();
335  else
336  throw std::runtime_error("Tried to derefer nullptr pointer");
337  }
338 
339  private:
340  CONTAINER* m_source;
341  mutable SELECTED_CLASS_PTR m_ret;
342  void internal_update_ref() const
343  {
344  for (typename CONTAINER::const_iterator it = m_source->begin();
345  it != m_source->end(); ++it)
346  {
347  if (dynamic_cast<const_ptr_t>(it->get()))
348  {
349  m_ret = std::dynamic_pointer_cast<pointee_t>(it->get_ptr());
350  return;
351  }
352  }
353  m_ret = SELECTED_CLASS_PTR(); // Not found
354  }
355 
356  }; // end ProxySelectorContainerByClass
357 
358  /** STL-like proxy to access this kind of maps in \ref maps */
359  ProxyFilterContainerByClass<mrpt::maps::CSimplePointsMap::Ptr, TListMaps>
361  /** STL-like proxy to access this kind of maps in \ref maps */
364  /** STL-like proxy to access this kind of maps in \ref maps */
367  /** STL-like proxy to access this kind of maps in \ref maps */
370  /** STL-like proxy to access this kind of maps in \ref maps */
374  /** STL-like proxy to access this kind of maps in \ref maps */
378  /** STL-like proxy to access this kind of maps in \ref maps */
381  /** STL-like proxy to access this kind of maps in \ref maps */
385  /** STL-like proxy to access this kind of maps in \ref maps */
389  /** Proxy that looks like a smart pointer to the first matching object in
390  * \ref maps */
394  /** Proxy that looks like a smart pointer to the first matching object in
395  * \ref maps */
399  /** Proxy that looks like a smart pointer to the first matching object in
400  * \ref maps */
403  /** Proxy that looks like a smart pointer to the first matching object in
404  * \ref maps */
407 
408  /** @} */
409 
410  /** Constructor.
411  * \param initializers One internal map will be created for each entry in
412  * this "TSetOfMetricMapInitializers" struct.
413  * If initializers is nullptr, no internal map will be created.
414  */
416  const mrpt::maps::TSetOfMetricMapInitializers* initializers = nullptr);
417 
420 
423 
424  /** Sets the list of internal map according to the passed list of map
425  * initializers (Current maps' content will be deleted!) */
426  void setListOfMaps(
427  const mrpt::maps::TSetOfMetricMapInitializers* initializers);
428  /** \overload */
430  const mrpt::maps::TSetOfMetricMapInitializers& initializers)
431  {
432  this->setListOfMaps(&initializers);
433  }
434 
435  /** Returns true if all maps returns true to their isEmpty() method, which
436  * is map-dependent. Read the docs of each map class */
437  bool isEmpty() const override;
438 
439  // See docs in base class.
440  virtual void determineMatching2D(
441  const mrpt::maps::CMetricMap* otherMap,
442  const mrpt::poses::CPose2D& otherMapPose,
443  mrpt::tfest::TMatchingPairList& correspondences,
445  mrpt::maps::TMatchingExtraResults& extraResults) const override;
446 
447  /** See the definition in the base class: Calls in this class become a call
448  * to every single map in this set. */
450  const mrpt::maps::CMetricMap* otherMap,
451  const mrpt::poses::CPose3D& otherMapPose,
452  const TMatchingRatioParams& params) const override;
453 
454  /** The implementation in this class just calls all the corresponding method
455  * of the contained metric maps */
457  const std::string& filNamePrefix) const override;
458 
459  /** This method is called at the end of each "prediction-update-map
460  * insertion" cycle within
461  * "mrpt::slam::CMetricMapBuilderRBPF::processActionObservation".
462  * This method should normally do nothing, but in some cases can be used
463  * to free auxiliary cached variables.
464  */
465  void auxParticleFilterCleanUp() override;
466 
467  /** Returns a 3D object representing the map.
468  */
469  void getAs3DObject(mrpt::opengl::CSetOfObjects::Ptr& outObj) const override;
470 
471  /** If the map is a simple point map or it's a multi-metric map that
472  * contains EXACTLY one simple point map, return it.
473  * Otherwise, return NULL
474  */
476  const override;
478 
479  /** An auxiliary variable that can be used freely by the users (this will be
480  * copied to other maps using the copy constructor, copy operator,
481  * streaming,etc) The default value is 0.
482  */
483  unsigned int m_ID;
484 
485 }; // End of class def.
486 
487 }
488 #endif
489 
490 
void auxParticleFilterCleanUp() override
This method is called at the end of each "prediction-update-map insertion" cycle within "mrpt::slam::...
void getAs3DObject(mrpt::opengl::CSetOfObjects::Ptr &outObj) const override
Returns a 3D object representing the map.
Scalar * iterator
Definition: eigen_plugins.h:26
Parameters for CMetricMap::compute3DMatchingRatio()
void setListOfMaps(const mrpt::maps::TSetOfMetricMapInitializers &initializers)
void deleteAllMaps()
Deletes all maps and clears the internal lists of maps (with clear_unique(), so user copies remain al...
ProxyFilterContainerByClass< mrpt::maps::COctoMap::Ptr, TListMaps > m_octoMaps
STL-like proxy to access this kind of maps in maps.
T::Ptr getMapByClass(const size_t &ith=0) const
Returns the i&#39;th observation of a given class (or of a descendant class), or nullptr if there is no s...
ProxyFilterContainerByClass< mrpt::maps::CSimplePointsMap::Ptr, TListMaps > m_pointsMaps
STL-like proxy to access this kind of maps in maps.
std::shared_ptr< CGasConcentrationGridMap2D > Ptr
ProxySelectorContainerByClass< mrpt::maps::CColouredPointsMap::Ptr, TListMaps > m_colourPointsMap
Proxy that looks like a smart pointer to the first matching object in maps.
double internal_computeObservationLikelihood(const mrpt::obs::CObservation *obs, const mrpt::poses::CPose3D &takenFrom) override
Internal method called by computeObservationLikelihood()
A structure that holds runtime class type information.
Definition: CObject.h:30
A cloud of points in 2D or 3D, which can be built from a sequence of laser scans. ...
virtual void determineMatching2D(const mrpt::maps::CMetricMap *otherMap, const mrpt::poses::CPose2D &otherMapPose, mrpt::tfest::TMatchingPairList &correspondences, const mrpt::maps::TMatchingParams &params, mrpt::maps::TMatchingExtraResults &extraResults) const override
Computes the matching between this and another 2D point map, which includes finding: ...
Takes a const ref of a STL non-associative container of smart pointers at construction and exposes an...
ProxySelectorContainerByClass< mrpt::maps::CWeightedPointsMap::Ptr, TListMaps > m_weightedPointsMap
Proxy that looks like a smart pointer to the first matching object in maps.
A set of TMetricMapInitializer structures, passed to the constructor CMultiMetricMap::CMultiMetricMap...
ProxySelectorContainerByClass< mrpt::maps::CBeaconMap::Ptr, TListMaps > m_beaconMap
Proxy that looks like a smart pointer to the first matching object in maps.
bool isEmpty() const override
Returns true if all maps returns true to their isEmpty() method, which is map-dependent.
float compute3DMatchingRatio(const mrpt::maps::CMetricMap *otherMap, const mrpt::poses::CPose3D &otherMapPose, const TMatchingRatioParams &params) const override
See the definition in the base class: Calls in this class become a call to every single map in this s...
Additional results from the determination of matchings between point clouds, etc., apart from the pairings themselves.
void internal_clear() override
Clear all elements of the map.
GLuint index
Definition: glext.h:4054
CMultiMetricMap & operator=(const CMultiMetricMap &o)
bool internal_insertObservation(const mrpt::obs::CObservation *obs, const mrpt::poses::CPose3D *robotPose=nullptr) override
Internal method called by insertObservation()
A list of TMatchingPair.
Definition: TMatchingPair.h:81
CMultiMetricMap(const mrpt::maps::TSetOfMetricMapInitializers *initializers=nullptr)
Constructor.
ProxySelectorContainerByClass< mrpt::maps::CLandmarksMap::Ptr, TListMaps > m_landmarksMap
Proxy that looks like a smart pointer to the first matching object in maps.
ProxyFilterContainerByClass< mrpt::maps::COccupancyGridMap2D::Ptr, TListMaps > m_gridMaps
STL-like proxy to access this kind of maps in maps.
ProxyFilterContainerByClass< mrpt::maps::CColouredOctoMap::Ptr, TListMaps > m_colourOctoMaps
STL-like proxy to access this kind of maps in maps.
ProxySelectorContainerByClass< SELECTED_CLASS_PTR, CONTAINER > & operator=(ProxySelectorContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &&o)
ProxyFilterContainerByClass< mrpt::maps::CWirelessPowerGridMap2D::Ptr, TListMaps > m_wifiGridMaps
STL-like proxy to access this kind of maps in maps.
ProxySelectorContainerByClass(ProxySelectorContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &)
std::shared_ptr< CColouredPointsMap > Ptr
bool internal_canComputeObservationLikelihood(const mrpt::obs::CObservation *obs) const override
Returns true if any of the inner maps is able to compute a sensible likelihood function for this obse...
GLsizei const GLchar ** string
Definition: glext.h:4101
mrpt::maps::CMetricMap::Ptr getMapByIndex(size_t idx) const
Gets the i-th map.
ProxyFilterContainerByClass(ProxyFilterContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &&)
#define DEFINE_SERIALIZABLE(class_name)
This declaration must be inserted in all CSerializable classes definition, within the class declarati...
std::shared_ptr< CWirelessPowerGridMap2D > Ptr
ProxySelectorContainerByClass(ProxySelectorContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &&)
ProxyFilterContainerByClass< mrpt::maps::CGasConcentrationGridMap2D::Ptr, TListMaps > m_gasGridMaps
STL-like proxy to access this kind of maps in maps.
Declares a virtual base class for all metric maps storage classes.
Definition: CMetricMap.h:54
A class used to store a 2D pose, including the 2D coordinate point and a heading (phi) angle...
Definition: CPose2D.h:38
virtual const mrpt::maps::CSimplePointsMap * getAsSimplePointsMap() const override
If the map is a simple point map or it&#39;s a multi-metric map that contains EXACTLY one simple point ma...
A class used to store a 3D pose (a 3D translation + a rotation in 3D).
Definition: CPose3D.h:86
Declares a class that represents any robot&#39;s observation.
Definition: CObservation.h:43
void setListOfMaps(const mrpt::maps::TSetOfMetricMapInitializers *initializers)
Sets the list of internal map according to the passed list of map initializers (Current maps&#39; content...
ProxySelectorContainerByClass< SELECTED_CLASS_PTR, CONTAINER > & operator=(const ProxySelectorContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &o)
TListMaps::const_iterator const_iterator
ProxyFilterContainerByClass< SELECTED_CLASS_PTR, CONTAINER > & operator=(ProxyFilterContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &&o)
GLsizei GLsizei GLchar * source
Definition: glext.h:4082
ProxyFilterContainerByClass< SELECTED_CLASS_PTR, CONTAINER > & operator=(const ProxyFilterContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &o)
unsigned int m_ID
An auxiliary variable that can be used freely by the users (this will be copied to other maps using t...
std::shared_ptr< CWeightedPointsMap > Ptr
TListMaps maps
The list of MRPT metric maps in this object.
ProxyFilterContainerByClass< mrpt::maps::CHeightGridMap2D_MRF::Ptr, TListMaps > m_heightMRFMaps
STL-like proxy to access this kind of maps in maps.
A proxy like ProxyFilterContainerByClass, but it directly appears as if it was a single smart pointer...
ProxyFilterContainerByClass(ProxyFilterContainerByClass< SELECTED_CLASS_PTR, CONTAINER > &)
const_iterator end() const
std::deque< mrpt::containers::deepcopy_poly_ptr< mrpt::maps::CMetricMap::Ptr > > TListMaps
ProxyFilterContainerByClass< mrpt::maps::CHeightGridMap2D::Ptr, TListMaps > m_heightMaps
STL-like proxy to access this kind of maps in maps.
This class stores any customizable set of metric maps.
Parameters for the determination of matchings between point clouds, etc.
std::shared_ptr< CReflectivityGridMap2D > Ptr
GLenum const GLfloat * params
Definition: glext.h:3534
SELECTED_CLASS_PTR operator[](size_t index) const
const Scalar * const_iterator
Definition: eigen_plugins.h:27
std::shared_ptr< CHeightGridMap2D_MRF > Ptr
ProxyFilterContainerByClass< mrpt::maps::CReflectivityGridMap2D::Ptr, TListMaps > m_reflectivityMaps
STL-like proxy to access this kind of maps in maps.
void saveMetricMapRepresentationToFile(const std::string &filNamePrefix) const override
The implementation in this class just calls all the corresponding method of the contained metric maps...
const_iterator begin() const
TListMaps::iterator iterator



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: 7d5e6d718 Fri Aug 24 01:51:28 2018 +0200 at lun nov 2 08:35:50 CET 2020