MIRA
GridMap.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) by
3  * MetraLabs GmbH (MLAB), GERMANY
4  * and
5  * Neuroinformatics and Cognitive Robotics Labs (NICR) at TU Ilmenau, GERMANY
6  * All rights reserved.
7  *
8  * Redistribution and modification of this code is strictly prohibited.
9  *
10  * IN NO EVENT SHALL "MLAB" OR "NICR" BE LIABLE TO ANY PARTY FOR DIRECT,
11  * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
12  * THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF "MLAB" OR
13  * "NICR" HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14  *
15  * "MLAB" AND "NICR" SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING,
16  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
17  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
18  * ON AN "AS IS" BASIS, AND "MLAB" AND "NICR" HAVE NO OBLIGATION TO
19  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR MODIFICATIONS.
20  */
21 
30 #ifndef _MIRA_GRIDMAP_H_
31 #define _MIRA_GRIDMAP_H_
32 
33 #include <cmath>
34 #include <type_traits>
35 
36 #include <image/Img.h>
37 #include <geometry/Size.h>
38 #include <geometry/Rect.h>
41 
42 namespace mira { namespace maps {
43 
45 
66 template<typename T, int Channels = 1>
67 class GridMap : public Img<T,Channels>
68 {
69  // the is_pod check is too hard here, but the type needs to be trivial, i.e.
70  // there must exist a trivial default constructor, copy constructor and
71  // trivial destructor
72  // TODO: we can remove this check if arithmetic types are not sufficient,
73  // but then have to make sure that the types are "trivial" (see above).
74  static_assert(std::is_void<T>::value || std::is_arithmetic<T>::value, "GridMap can be used with arithmetic types only");
75 
76  typedef Img<T,Channels> Base;
77 public:
78 
79  typedef typename Base::Pixel CellType;
80 
81 public:
82 
88  explicit GridMap(float cellSize=0.1f) : mCellSize(cellSize), mOffset(0, 0) {}
89 
90  GridMap(const Size2i& size, float cellSize, const Point2i& offset = Point2i(0, 0)) :
91  Base(size), mCellSize(cellSize), mOffset(offset) {}
92 
101  GridMap(const Rect2f& region, float cellSize) :
102  Base(getOffsetAndSize(region,cellSize).second),
103  mCellSize(cellSize),
104  mOffset(getOffsetAndSize(region,cellSize).first)
105  {}
106 
107 
108  GridMap(const Rect2i& region, float cellSize) :
109  Base(region.size()),
110  mCellSize(cellSize),
111  mOffset(-region.minCorner)
112  {}
113 
118  GridMap(const Base& data, float cellSize, const Point2i& offset = Point2i(0, 0)) :
119  Base(data), mCellSize(cellSize), mOffset(offset) {}
120 
121  // use default copy constructor and assignment operator
122 
123 public:
124 
126  float getCellSize() const { return mCellSize; }
127 
128 
130  Point2i getOffset() const { return mOffset; }
131 
133  Point2i getMapOffset() const { return mOffset; }
134 
137  return Point2f(mOffset.x()*mCellSize, mOffset.y()*mCellSize);
138  }
139 
140 
141 public:
142 
144  Base::operator=(c);
145  return *this;
146  }
147 
148  GridMap clone() const {
149  return GridMap(Base::clone(), mCellSize, mOffset);
150  }
151 
152 public:
153 
157  Rect2f getRegion() const {
158  return Rect2f(-mOffset.x()*mCellSize, -mOffset.y()*mCellSize,
159  this->width()*mCellSize, this->height()*mCellSize);
160  }
161 
162 
167  return Rect2i(-mOffset.x(), -mOffset.y(),
168  this->width(), this->height());
169  }
170 
171 public:
172 
183  void grow(const Rect2f& region, const CellType& valueForNewCells);
184 
185  void grow(const Rect2i& region, const CellType& valueForNewCells);
186 
194  void grow(const Point2i& growLowerLeftDelta, const Point2i& growUpperRightDelta,
195  const CellType& valueForNewCells);
196 
204  void clip(const Rect2i& region, const CellType& valueForNewCells);
205 
206  void clip(const Rect2f& region, const CellType& valueForNewCells);
207 
208 public:
209 
217  Point2i world2map(const Point2f& p, bool roundDown = true) const {
218  if (roundDown)
219  return Point2i((int)std::floor(p.x()/mCellSize + mOffset.x()),
220  (int)std::floor(p.y()/mCellSize + mOffset.y()));
221  else
222  return Point2i((int)std::ceil(p.x()/mCellSize + mOffset.x()),
223  (int)std::ceil(p.y()/mCellSize + mOffset.y()));
224  }
225 
232  Point2f world2mapf(const Point2f& p) const {
233  return Point2f(p.x()/mCellSize + mOffset.x(),
234  p.y()/mCellSize + mOffset.y());
235  }
236 
243  Point2f map2world(const Point2i& p) const {
244  return Point2f((p.x() - mOffset.x()) * mCellSize,
245  (p.y() - mOffset.y()) * mCellSize);
246  }
247 
254  Point2f map2world(const Point2f& p) const {
255  return Point2f((p.x() - mOffset.x()) * mCellSize,
256  (p.y() - mOffset.y()) * mCellSize);
257  }
258 
267  Rect2i world2map(const Rect2f& r, bool includeBorder = true) const {
268  if(includeBorder) {
269  Point2i lowerLeft (std::floor(r.x0() / mCellSize + mOffset.x()),
270  std::floor(r.y0() / mCellSize + mOffset.y()));
271  Point2i upperRight(std::ceil(r.x1() / mCellSize + mOffset.x()),
272  std::ceil(r.y1() / mCellSize + mOffset.y()));
273  return Rect2i(lowerLeft, upperRight);
274  } else {
275  Point2i lowerLeft (std::ceil(r.x0() / mCellSize + mOffset.x()),
276  std::ceil(r.y0() / mCellSize + mOffset.y()));
277  Point2i upperRight(std::floor(r.x1() / mCellSize + mOffset.x()),
278  std::floor(r.y1() / mCellSize + mOffset.y()));
279  return Rect2i(lowerLeft, upperRight);
280  }
281  }
282 
283 
285 
286 
287  template<typename Derived>
290  r.member("CellSize", mCellSize, "The cellsize in meter");
291  r.member("Offset" , mOffset, "The cell index that corresponds to the origin of the map");
292  }
293 
295  template<typename Derived>
298  r.member("CellSize", mCellSize, "The cellsize in meter");
299  r.member("Offset" , mOffset, "The cell index that corresponds to the origin of the map");
300  }
301 
305  r.member("CellSize", mCellSize, "The cellsize in meter");
306  r.member("Offset" , mOffset, "The cell index that corresponds to the origin of the map");
307  }
308 
309 private:
310 
311  static std::pair<Point2i, Size2i> getOffsetAndSize(const Rect2f& region, float cellSize);
312 
313 protected:
314  float mCellSize;
316 };
317 
319 
320 template<typename T, int Channels>
321 inline void GridMap<T,Channels>::grow(const Rect2f& region, const CellType& valueForNewCells)
322 {
323  // if our map already contains the specified region, do nothing
324  if(getRegion().x0()<=region.x0() &&
325  getRegion().y0()<=region.y0() &&
326  getRegion().x1()>=region.x1() &&
327  getRegion().y1()>=region.y1())
328  return;
329 
330  // get the overall region
331  Rect2f o = region | getRegion();
332  Size2i oldSize = this->size();
333 
334  auto offsetSize = getOffsetAndSize(o,mCellSize);
335 
336  Point2i growLowerLeft = offsetSize.first - mOffset;
337  Point2i growUpperRight = offsetSize.second - oldSize - growLowerLeft;
338 
339  assert(growLowerLeft.x()>=0 && growLowerLeft.y()>=0);
340  assert(growUpperRight.x()>=0 && growUpperRight.y()>=0);
341  grow(growLowerLeft,growUpperRight, valueForNewCells);
342 }
343 
344 template<typename T, int Channels>
345 inline void GridMap<T,Channels>::grow(const Rect2i& region, const CellType& valueForNewCells)
346 {
347  // if our map already contains the specified region, do nothing
348  if(getMapRegion().contains(region))
349  return;
350 
351  // get the overall region
352  const auto o = region | getMapRegion();
353 
354  const auto newOffset = -o.minCorner;
355  const auto newSize = o.size();
356 
357  const Size2i oldSize = this->size();
358  const Point2i growLowerLeft = newOffset - mOffset;
359  const Point2i growUpperRight = newSize - oldSize - growLowerLeft;
360 
361  assert(growLowerLeft.x()>=0 && growLowerLeft.y()>=0);
362  assert(growUpperRight.x()>=0 && growUpperRight.y()>=0);
363  grow(growLowerLeft,growUpperRight, valueForNewCells);
364 }
365 
366 template<typename T, int Channels>
367 inline void GridMap<T,Channels>::grow(const Point2i& growLowerLeft, const Point2i& growUpperRight,
368  const CellType& valueForNewCells)
369 {
370  assert(growLowerLeft.x()>=0 && growLowerLeft.y()>=0);
371  assert(growUpperRight.x()>=0 && growUpperRight.y()>=0);
372 
373  Size2i oldSize = this->size();
374  Size2i newSize = oldSize + growLowerLeft + growUpperRight;
375 
376  Base& oldBuffer = *this;
377  Base newBuffer(newSize);
378 
379  // copy the existing data into the new buffer (scanline by scanline)
380  for(int y=0; y<oldBuffer.height(); ++y)
381  {
382  const CellType* src = oldBuffer[y];
383  CellType* dest = newBuffer[y+growLowerLeft.y()];
384  memcpy(dest+growLowerLeft.x(), src, oldBuffer.width()*sizeof(CellType));
385  }
386 
387  // now fill the new areas with the default data:
388 
389  // newBuffer:
390  // ------------------------------
391  // | above |
392  // |----------------------------|
393  // | | | |
394  // | | oldBuffer | |
395  // |left| | right |
396  // | | | |
397  // | | | |
398  // |----------------------------|
399  // | below |
400  // |----------------------------|
401 
402  // above
403  for(int y=0; y<growLowerLeft.y(); ++y)
404  {
405  CellType* dest = newBuffer[y];
406  for(int x=0; x<newBuffer.width(); ++x)
407  dest[x] = valueForNewCells;
408  }
409 
410  // left and right
411  for(int y=growLowerLeft.y(); y<growLowerLeft.y()+oldBuffer.height(); ++y)
412  {
413  CellType* dest = newBuffer[y];
414  // left
415  for(int x=0; x<growLowerLeft.x(); ++x)
416  dest[x] = valueForNewCells;
417  // right
418  for(int x=growLowerLeft.x()+oldBuffer.width(); x<newBuffer.width(); ++x)
419  dest[x] = valueForNewCells;
420  }
421 
422  // below
423  for(int y=growLowerLeft.y()+oldBuffer.height(); y<newBuffer.height(); ++y)
424  {
425  CellType* dest = newBuffer[y];
426  for(int x=0; x<newBuffer.width(); ++x)
427  dest[x] = valueForNewCells;
428  }
429 
430 
431  oldBuffer = newBuffer;
432  mOffset = mOffset + growLowerLeft;
433 }
434 
435 template<typename T, int Channels>
436 inline void GridMap<T,Channels>::clip(const Rect2f& region, const CellType& valueForNewCells)
437 {
438  Point2i lowerLeft (std::floor(region.x0() / mCellSize),
439  std::floor(region.y0() / mCellSize));
440  Point2i upperRight(std::ceil(region.x1() / mCellSize),
441  std::ceil(region.y1() / mCellSize));
442  Rect2i r(lowerLeft, upperRight);
443 
444  clip(r,valueForNewCells);
445 }
446 
447 template<typename T, int Channels>
448 inline void GridMap<T,Channels>::clip(const Rect2i& region, const CellType& valueForNewCells)
449 {
450  Rect2i oldRegion = getMapRegion();
451  if(region==oldRegion)
452  return; // nothing to do
453 
454  // newBuffer:
455  // ------------
456  // | new :
457  // | ------:-----------------|
458  // | | copy: |
459  // |....|.....: oldBuffer |
460  // | |
461  // | |
462  // -------------------------
463 
464 
465  Point2i growLowerLeft = oldRegion.minCorner - region.minCorner;
466  Point2i growUpperRight = region.maxCorner - oldRegion.maxCorner;
467 
468  Point2i copyRegionInNewLL(0,0);
469  Point2i copyRegionInOldLL(0,0);
470  if(oldRegion.minCorner.x() > region.minCorner.x())
471  copyRegionInNewLL.x() = oldRegion.minCorner.x() - region.minCorner.x();
472  else
473  copyRegionInOldLL.x() = region.minCorner.x() - oldRegion.minCorner.x();
474 
475  if(oldRegion.minCorner.y() > region.minCorner.y())
476  copyRegionInNewLL.y() = oldRegion.minCorner.y() - region.minCorner.y();
477  else
478  copyRegionInOldLL.y() = region.minCorner.y() - oldRegion.minCorner.y();
479 
480 
481  Rect2i intersection = oldRegion & region;
482 
483  Base& oldBuffer = *this;
484  Base newBuffer(region.size());
485 
486  if(intersection.isValid()) {
487 
488  Point2i copyRegionInNewUR = copyRegionInNewLL + intersection.size();
489  Point2i copyRegionInOldUR = copyRegionInOldLL + intersection.size();
490 
491  // detailed view for copying:
492  // ------------------------------
493  // | above |
494  // |----------------------------|
495  // | | | |
496  // | | oldBuffer | |
497  // |left| | right |
498  // | | | |
499  // | | | |
500  // |----------------------------|
501  // | below |
502  // |----------------------------|
503 
504 
505  // fill in new parts
506  // above
507  assert(copyRegionInNewLL.y()<=newBuffer.height());
508  for(int y=0; y<copyRegionInNewLL.y(); ++y)
509  {
510  CellType* dest = newBuffer[y];
511  for(int x=0; x<newBuffer.width(); ++x)
512  dest[x] = valueForNewCells;
513  }
514 
515  // left and right
516  assert(copyRegionInNewLL.y()>=0 && copyRegionInNewUR.y()<=newBuffer.height());
517  assert(copyRegionInNewUR.x()>=0 && copyRegionInNewLL.x()<=newBuffer.width());
518  for(int y=copyRegionInNewLL.y(); y<copyRegionInNewUR.y(); ++y)
519  {
520  CellType* dest = newBuffer[y];
521  // left
522  for(int x=0; x<copyRegionInNewLL.x(); ++x)
523  dest[x] = valueForNewCells;
524  // right
525  for(int x=copyRegionInNewUR.x(); x<newBuffer.width(); ++x)
526  dest[x] = valueForNewCells;
527  }
528 
529  // below
530  assert(copyRegionInNewUR.y()>=0);
531  for(int y=copyRegionInNewUR.y(); y<newBuffer.height(); ++y)
532  {
533  CellType* dest = newBuffer[y];
534  for(int x=0; x<newBuffer.width(); ++x)
535  dest[x] = valueForNewCells;
536  }
537 
538  // copy the overlapping content (scanline by scanline)
539  for(int y=0; y<intersection.height(); ++y)
540  {
541  const CellType* src = oldBuffer[y+copyRegionInOldLL.y()]+copyRegionInOldLL.x();
542  CellType* dest = newBuffer[y+copyRegionInNewLL.y()]+copyRegionInNewLL.x();
543  memcpy(dest, src, intersection.width()*sizeof(CellType));
544  }
545  } else {
546  // no intersection between old and new buffer, so simply fill the
547  // new buffer
548  newBuffer = valueForNewCells;
549  }
550 
551  mOffset = -region.minCorner;
552  oldBuffer = newBuffer;
553 
554 }
555 
556 template<typename T, int Channels>
557 inline std::pair<Point2i, Size2i> GridMap<T,Channels>::getOffsetAndSize(const Rect2f& region, float cellSize)
558 {
559  Point2i lowerLeft (std::floor(region.x0() / cellSize),
560  std::floor(region.y0() / cellSize));
561  Point2i upperRight(std::ceil(region.x1() / cellSize),
562  std::ceil(region.y1() / cellSize));
563 
564  // compute required size and offset of the map
565  Size2i size = upperRight - lowerLeft;// + Size2i(1,1);
566  Point2i offset = -lowerLeft;
567  return std::make_pair(offset,size);
568 }
569 
571 
572 } // namespace maps
573 
575 template <typename TPixel, int TChannels>
576 class IsNotMetaSerializable<maps::GridMap<TPixel, TChannels>> : public std::true_type {};
577 
579 
580 } // namespace mira
581 
582 #endif /* _MIRA_GRIDMAP_H_ */
Point2f map2world(const Point2i &p) const
Convert a given point to world coordinates.
Definition: GridMap.h:243
Point2f map2world(const Point2f &p) const
Convert a given point to world coordinates.
Definition: GridMap.h:254
ImgPixel< T, Channels > Pixel
Rect2i world2map(const Rect2f &r, bool includeBorder=true) const
Returns the Rect in cell coordinates that is covered by the given rect that is specified in world coo...
Definition: GridMap.h:267
GridMap clone() const
Definition: GridMap.h:148
void grow(const Rect2f &region, const CellType &valueForNewCells)
Grows the map so that the specified region is covered by the map.
Definition: GridMap.h:321
Rect< int, 2 > Rect2i
GridMap(float cellSize=0.1f)
Constructs an empty map with a default cell size of 0.1 m.
Definition: GridMap.h:88
Definition: GridMap.h:67
void member(const char *name, T &member, const char *comment, ReflectCtrlFlags flags=REFLECT_CTRLFLAG_NONE)
Self & operator=(const Pixel &p)
Point< int, 2 > Point2i
#define MIRA_REFLECT_BASE(reflector, BaseClass)
Point2i getMapOffset() const
Provided for backward compatibility. Use getOffset() instead.
Definition: GridMap.h:133
void clip(const Rect2i &region, const CellType &valueForNewCells)
Grows and optionally crops the map to cover the specified region while minimizing copying of memory...
Definition: GridMap.h:448
Point2i mOffset
cell index that corresponds to the origin of the map
Definition: GridMap.h:315
bool isValid() const
float mCellSize
width of one cell in m
Definition: GridMap.h:314
Rect< float, 2 > Rect2f
PointType minCorner
void reflect(BinaryDeserializer< Derived > &r)
Reflect method for deserialization.
Definition: GridMap.h:296
Rect2i getMapRegion() const
Returns the region that is covered by the GridMap in grid cells.
Definition: GridMap.h:166
Base::Pixel CellType
Definition: GridMap.h:79
void reflect(BinarySerializer< Derived > &r)
Reflect method for serialization.
Definition: GridMap.h:288
GridMap(const Rect2f &region, float cellSize)
Creates a new grid map that covers the specified region.
Definition: GridMap.h:101
GridMap(const Base &data, float cellSize, const Point2i &offset=Point2i(0, 0))
Construct a grid map from existing image.
Definition: GridMap.h:118
PointType maxCorner
GridMap(const Size2i &size, float cellSize, const Point2i &offset=Point2i(0, 0))
Definition: GridMap.h:90
float getCellSize() const
Returns the size of each cell in meter.
Definition: GridMap.h:126
Point2i world2map(const Point2f &p, bool roundDown=true) const
Convert a given point to map coordinates.
Definition: GridMap.h:217
Img< T, Channels > clone() const
Point< float, 2 > Point2f
#define MIRA_NO_GENERIC_REFLECT_MEMBER(Type)
void reflect(JSONSerializer &r)
Reflect method for text visualization.
Definition: GridMap.h:303
Point2i getOffset() const
Returns the offset of the map, e.g. the index of the cell that is located in the origin.
Definition: GridMap.h:130
Point2f world2mapf(const Point2f &p) const
Convert a given point to map coordinates.
Definition: GridMap.h:232
GridMap & operator=(const CellType &c)
Definition: GridMap.h:143
Point2f getWorldOffset() const
Returns the offset of the map in metric coordinates.
Definition: GridMap.h:136
SizeType size() const
Rect2f getRegion() const
Returns the region that is covered by the GridMap.
Definition: GridMap.h:157
GridMap(const Rect2i &region, float cellSize)
Definition: GridMap.h:108