MeasurementArea.cc 22.5 KB
Newer Older
1
#include "MeasurementArea.h"
2
#include "QtConcurrentRun"
3
#include "geometry.h"
4
#include "nemo_interface/SnakeTile.h"
5
#include <ctime>
6 7 8

#include <boost/units/systems/si.hpp>

9
#include "JsonHelper.h"
10 11
#include "QGCLoggingCategory.h"

12 13
#include <QJsonArray>

14 15 16 17
#ifndef SNAKE_MAX_TILES
#define SNAKE_MAX_TILES 1000
#endif

18 19 20 21 22 23 24 25
using namespace geometry;
namespace trans = bg::strategy::transform;

// Aux function
bool getTiles(const FPolygon &area, Length tileHeight, Length tileWidth,
              Area minTileArea, std::vector<FPolygon> &tiles,
              BoundingBox &bbox);

26
QGC_LOGGING_CATEGORY(MeasurementAreaLog, "MeasurementAreaLog")
27

28 29 30 31 32
namespace {
const char *tileCenterPointsKey = "TileCenterPoints";
const char *tileArrayKey = "TileArray";
} // namespace

33 34 35 36 37 38 39 40 41 42
TileData::TileData() : tiles(this) {}

TileData::~TileData() { tiles.clearAndDeleteContents(); }

TileData &TileData::operator=(const TileData &other) {
  this->tiles.clearAndDeleteContents();
  for (std::size_t i = 0; i < std::size_t(other.tiles.count()); ++i) {
    const auto *obj = other.tiles[i];
    const auto *tile = qobject_cast<const SnakeTile *>(obj);
    if (tile != nullptr) {
43
      this->tiles.append(tile->clone(this));
44
    } else {
45
      qCWarning(MeasurementAreaLog) << "TileData::operator=: nullptr";
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    }
  }
  this->tileCenterPoints = other.tileCenterPoints;
  return *this;
}

bool TileData::operator==(const TileData &other) const {
  if (this->tileCenterPoints == other.tileCenterPoints &&
      this->tiles.count() == other.tiles.count()) {
    for (int i = 0; i < other.tiles.count(); ++i) {
      if (this->tiles[i] != other.tiles[i]) {
        return false;
      }
    }
    return true;
  } else {
    return false;
  }
}

bool TileData::operator!=(const TileData &other) const {
  return !this->operator==(other);
}

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
void TileData::saveToJson(QJsonObject &json) {
  // save center points
  QJsonValue jsonCenterPoints;
  JsonHelper::saveGeoCoordinateArray(tileCenterPoints, false, jsonCenterPoints);
  json[tileCenterPointsKey] = std::move(jsonCenterPoints);

  // save tiles
  QJsonArray jsonTiles;
  for (int i = 0; i < tiles.count(); ++i) {
    auto tile = tiles.value<SnakeTile *>(i);
    if (tile != nullptr) {
      QJsonObject jsonTile;
      tile->saveToJson(jsonTile);
      jsonTiles.append(jsonTile);
    } else {
      qCritical() << "TileData::saveToJson(): Object other than SnakeTile "
                     "inside tiles (QmlObjectListModel))";
      Q_ASSERT(tile != nullptr);
    }
  }
  json[tileArrayKey] = std::move(jsonTiles);
}

bool TileData::loadFromJson(const QJsonObject &json, QString &errorString) {
  clear();

  // load tiles
  if (json.contains(tileArrayKey) && json[tileArrayKey].isArray()) {
    QString e;
    for (const auto &jsonTile : json[tileArrayKey].toArray()) {
      auto tile = new SnakeTile(this);
      if (tile->loadFromJson(jsonTile.toObject(), e)) {
        tiles.append(tile);
      } else {
        tile->deleteLater();
        errorString.append(e);
        return false;
      }
    }
  } else {
    errorString.append(tr("Not able to load tiles.\n"));
    qCWarning(MeasurementAreaLog)
        << "Not able to load tiles. tileArrayKey missing or wrong type.";
    if (json.contains(tileArrayKey)) {
      qCWarning(MeasurementAreaLog)
          << "tile array type: " << json[tileArrayKey].type();
    }
    return false;
  }

  // load center points
  if (json.contains(tileCenterPointsKey) &&
      json[tileCenterPointsKey].isArray()) {
    QString e;
    if (!JsonHelper::loadGeoCoordinateArray(json[tileCenterPointsKey], false,
                                            tileCenterPoints, e)) {
      errorString.append(e);
      errorString.append("\n");
      return false;
    }

  } else {
    errorString.append(tr("Not able to load center points.\n"));
    qCWarning(MeasurementAreaLog)
        << "Not able to load center points. tileCenterPointsKey missing or "
           "wrong type.";
    if (json.contains(tileCenterPointsKey)) {
      qCWarning(MeasurementAreaLog)
          << "center points type: " << json[tileCenterPointsKey].type();
    }
    return false;
  }

  return true;
}

146 147 148 149 150 151 152 153 154 155 156 157 158
void TileData::clear() {
  this->tiles.clearAndDeleteContents();
  this->tileCenterPoints.clear();
}

size_t TileData::size() const {
  if (tiles.count() == tileCenterPoints.size()) {
    return tiles.count();
  } else {
    return 0;
  }
}

159
const char *MeasurementArea::settingsGroup = "MeasurementArea";
160 161 162 163
const char *tileHeightKey = "TileHeight";
const char *tileWidthName = "TileWidth";
const char *minTileAreaKey = "MinTileAreaPercent";
const char *showTilesKey = "ShowTiles";
164 165 166
const char *progressKey = "Progress";
const char *tileKey = "Tiles";
const char *MeasurementArea::nameString = "Measurement Area";
167

168 169
MeasurementArea::MeasurementArea(QObject *parent)
    : GeoArea(parent),
170
      _metaDataMap(FactMetaData::createMapFromJsonFile(
171
          QStringLiteral(":/json/MeasurementArea.SettingsGroup.json"),
172
          this /* QObject parent */)),
173
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightKey],
174 175 176 177
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
      _minTileAreaPercent(SettingsFact(settingsGroup,
178
                                       _metaDataMap[minTileAreaKey],
179
                                       this /* QObject parent */)),
180
      _showTiles(SettingsFact(settingsGroup, _metaDataMap[showTilesKey],
181
                              this /* QObject parent */)),
182
      _holdProgress(false), _state(STATE::IDLE) {
183 184 185
  init();
}

186 187
MeasurementArea::MeasurementArea(const MeasurementArea &other, QObject *parent)
    : GeoArea(other, parent),
188
      _metaDataMap(FactMetaData::createMapFromJsonFile(
189
          QStringLiteral(":/json/MeasurementArea.SettingsGroup.json"),
190
          this /* QObject parent */)),
191
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightKey],
192 193 194 195
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
      _minTileAreaPercent(SettingsFact(settingsGroup,
196
                                       _metaDataMap[minTileAreaKey],
197
                                       this /* QObject parent */)),
198
      _showTiles(SettingsFact(settingsGroup, _metaDataMap[showTilesKey],
199
                              this /* QObject parent */)),
200
      _holdProgress(false), _state(STATE::IDLE) {
201 202 203
  init();
  disableUpdate();

204 205 206 207 208
  _tileHeight = other._tileHeight;
  _tileWidth = other._tileWidth;
  _minTileAreaPercent = other._minTileAreaPercent;
  _showTiles = other._showTiles;

209 210 211 212 213 214 215 216
  if (other.ready()) {
    _progress = other._progress;
    _tileData = other._tileData;
    enableUpdate();
  } else {
    enableUpdate();
    doUpdate();
  }
217 218
}

219 220
MeasurementArea &MeasurementArea::operator=(const MeasurementArea &other) {
  GeoArea::operator=(other);
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235

  disableUpdate();
  _tileHeight = other._tileHeight;
  _tileWidth = other._tileWidth;
  _minTileAreaPercent = other._minTileAreaPercent;
  _showTiles = other._showTiles;

  if (other.ready()) {
    _progress = other._progress;
    _tileData = other._tileData;
    enableUpdate();
  } else {
    enableUpdate();
    doUpdate();
  }
236 237 238
  return *this;
}

239
MeasurementArea::~MeasurementArea() {}
240

241 242
QString MeasurementArea::mapVisualQML() const {
  return QStringLiteral("MeasurementAreaMapVisual.qml");
243
  // return QStringLiteral("");
244 245
}

246 247
QString MeasurementArea::editorQML() const {
  return QStringLiteral("MeasurementAreaEditor.qml");
248 249
}

250 251 252
MeasurementArea *MeasurementArea::clone(QObject *parent) const {
  return new MeasurementArea(*this, parent);
}
253

254
Fact *MeasurementArea::tileHeight() { return &_tileHeight; }
255

256
Fact *MeasurementArea::tileWidth() { return &_tileWidth; }
257

258
Fact *MeasurementArea::minTileArea() { return &_minTileAreaPercent; }
259

260
Fact *MeasurementArea::showTiles() { return &_showTiles; }
261

262
QmlObjectListModel *MeasurementArea::tiles() { return &this->_tileData.tiles; }
263

264
const QVector<int> &MeasurementArea::progress() const {
265 266 267
  return this->_progress;
}

268 269 270
QVector<int> MeasurementArea::progressQml() const { return this->_progress; }

const QmlObjectListModel *MeasurementArea::tiles() const {
271 272 273
  return &this->_tileData.tiles;
}

274
const QVariantList &MeasurementArea::tileCenterPoints() const {
275 276 277
  return this->_tileData.tileCenterPoints;
}

278
const TileData &MeasurementArea::tileData() const { return this->_tileData; }
279

280
int MeasurementArea::maxTiles() const { return SNAKE_MAX_TILES; }
281

282
bool MeasurementArea::ready() const { return this->_state == STATE::IDLE; }
283

284 285 286 287 288 289 290 291 292 293 294 295 296
bool MeasurementArea::measurementCompleted() const {
  if (ready()) {
    for (const auto &p : _progress) {
      if (p != 100) {
        return false;
      }
    }
    return true;
  } else {
    return false;
  }
}

297
bool MeasurementArea::saveToJson(QJsonObject &json) {
298
  if (ready()) {
299 300 301 302 303
    if (this->GeoArea::saveToJson(json)) {
      json[tileHeightKey] = _tileHeight.rawValue().toDouble();
      json[tileWidthName] = _tileWidth.rawValue().toDouble();
      json[minTileAreaKey] = _minTileAreaPercent.rawValue().toDouble();
      json[showTilesKey] = _showTiles.rawValue().toBool();
304 305 306 307 308 309 310 311 312 313 314 315 316 317
      json[areaTypeKey] = nameString;

      // save progess
      QJsonArray jsonProgess;
      for (const auto &p : _progress) {
        jsonProgess.append(p);
      }
      json[progressKey] = std::move(jsonProgess);

      // save tiles
      QJsonObject jsonTiles;
      _tileData.saveToJson(jsonTiles);
      json[tileKey] = std::move(jsonTiles);

318 319 320 321 322
      return true;
    } else {
      qCDebug(MeasurementAreaLog)
          << "saveToJson(): error inside GeoArea::saveToJson().";
    }
323
  } else {
324
    qCDebug(MeasurementAreaLog) << "saveToJson(): not ready().";
325
  }
326
  return false;
327 328
}

329 330 331
bool MeasurementArea::loadFromJson(const QJsonObject &json,
                                   QString &errorString) {
  if (this->GeoArea::loadFromJson(json, errorString)) {
332 333 334
    disableUpdate();
    bool retVal = true;

335
    // load parameters necessary for tile calculation.
336
    if (!json.contains(tileHeightKey) || !json[tileHeightKey].isDouble()) {
337 338 339
      errorString.append(tr("Could not load tile height!\n"));
      retVal = false;
    } else {
340
      _tileHeight.setRawValue(json[tileHeightKey].toDouble());
341 342 343 344 345 346 347 348 349
    }

    if (!json.contains(tileWidthName) || !json[tileWidthName].isDouble()) {
      errorString.append(tr("Could not load tile width!\n"));
      retVal = false;
    } else {
      _tileWidth.setRawValue(json[tileWidthName].toDouble());
    }

350
    if (!json.contains(minTileAreaKey) || !json[minTileAreaKey].isDouble()) {
351 352 353
      errorString.append(tr("Could not load minimal tile area!\n"));
      retVal = false;
    } else {
354
      _minTileAreaPercent.setRawValue(json[minTileAreaKey].toDouble());
355 356
    }

357 358
    // load less important parameters
    if (json.contains(showTilesKey) || !json[showTilesKey].isBool()) {
359
      _showTiles.setRawValue(json[showTilesKey].toBool());
360 361
    }

362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
    // load tiles and progress
    bool tileError = false;
    if (json.contains(tileKey) && json[tileKey].isObject()) {
      QString e;
      if (!_tileData.loadFromJson(json[tileKey].toObject(), e)) {
        qCWarning(MeasurementAreaLog) << "TileData::loadFromJson(): " << e;
        tileError = true;
      } else {
        progressChanged();
      }
    } else {
      tileError = true;
    }

    bool progressError = false;
    QVector<int> prog;
    if (json.contains(progressKey) && json[progressKey].isArray()) {
      for (const auto &p : json[progressKey].toArray()) {
        if (p.isDouble()) {
          prog.append(p.toDouble());
        } else {
          progressError = true;
          break;
        }
      }

      // check if entries are in range
      if (!progressError) {
        for (const auto &p : prog) {
          if (p < 0 || p > 100) {
            progressError = true;
            break;
          }
        }
      }
    } else {
      progressError = true;
    }
    if (!progressError) {
      _progress.swap(prog);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
402
      emit progressChanged();
403 404 405
    }

    // do update if error occurred.
406
    enableUpdate();
407 408 409
    if (progressError || tileError) {
      doUpdate();
    }
410 411 412 413 414 415 416

    return retVal;
  } else {
    return false;
  }
}

417 418 419 420 421 422 423 424 425 426 427 428
bool MeasurementArea::isCorrect() {
  if (GeoArea::isCorrect()) {
    if (ready()) {
      return true;
    } else {
      setErrorString(
          tr("Measurement Area tile calculation in progess. Please wait."));
    }
  }
  return false;
}

429
bool MeasurementArea::setProgress(const QVector<int> &p) {
430
  if (ready()) {
431 432
    if (!_holdProgress && p.size() == this->tiles()->count() &&
        this->_progress != p) {
433 434 435 436 437 438
      this->_progress = p;
      emit progressChanged();
      emit progressAccepted();
      return true;
    }
  }
439
  emit progressNotAccepted();
440 441
  return false;
}
442 443 444 445 446

void MeasurementArea::randomProgress() {
  if (ready()) {
    std::srand(std::time(nullptr));
    for (auto &p : _progress) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
447
      p += std::rand() % 125;
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
      if (p > 100) {
        p = 100;
      }
    }

    emit progressChanged();
  }
}

void MeasurementArea::resetProgress() {
  if (ready()) {
    for (auto &p : _progress) {
      p = 0;
    }

    emit progressChanged();
  }
}
466
//!
467 468
//! \brief MeasurementArea::doUpdate
//! \pre MeasurementArea::deferUpdate must be called first, don't call
469
//! this function directly!
470
void MeasurementArea::doUpdate() {
471
  using namespace geometry;
472 473 474 475 476 477 478 479 480 481 482 483
  using namespace boost::units;

  auto start = std::chrono::high_resolution_clock::now();

  if (this->_state != STATE::UPDATEING && this->_state != STATE::STOP) {
    const auto height = this->_tileHeight.rawValue().toDouble() * si::meter;
    const auto width = this->_tileWidth.rawValue().toDouble() * si::meter;
    const auto tileArea = width * height;
    const auto totalArea = this->area() * si::meter * si::meter;
    const auto estNumTiles = totalArea / tileArea;
    // Check some conditions.
    if (long(std::ceil(estNumTiles.value())) <= SNAKE_MAX_TILES &&
484
        this->GeoArea::isCorrect()) {
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
      setState(STATE::UPDATEING);

      auto polygon = this->coordinateList();
      for (auto &v : polygon) {
        v.setAltitude(0);
      }
      const auto minArea =
          this->_minTileAreaPercent.rawValue().toDouble() / 100 * tileArea;
      auto *th = this->thread();
      auto future = QtConcurrent::run([polygon, th, height, width, minArea] {
        auto start = std::chrono::high_resolution_clock::now();

        DataPtr pData(new TileData());
        // Convert to ENU system.
        QGeoCoordinate origin = polygon.first();
        FPolygon polygonENU;
        areaToEnu(origin, polygon, polygonENU);
        std::vector<FPolygon> tilesENU;
        BoundingBox bbox;
        // Generate tiles.
505
        if (getTiles(polygonENU, height, width, minArea, tilesENU, bbox)) {
506 507 508 509 510 511 512 513 514 515
          // Convert to geo system.
          for (const auto &t : tilesENU) {
            auto geoTile = new SnakeTile(pData.get());
            for (const auto &v : t.outer()) {
              QGeoCoordinate geoVertex;
              fromENU(origin, v, geoVertex);
              geoTile->push_back(geoVertex);
            }
            pData->tiles.append(geoTile);
            // Calculate center.
516 517
            geometry::FPoint center;
            geometry::polygonCenter(t, center);
518 519 520 521 522 523 524
            QGeoCoordinate geoCenter;
            fromENU(origin, center, geoCenter);
            pData->tileCenterPoints.append(QVariant::fromValue(geoCenter));
          }
        }
        pData->moveToThread(th);

525
        qCDebug(MeasurementAreaLog)
526 527 528 529 530 531 532 533 534 535 536 537
            << "doUpdate(): update time: "
            << std::chrono::duration_cast<std::chrono::milliseconds>(
                   std::chrono::high_resolution_clock::now() - start)
                   .count()
            << " ms";

        return pData;
      }); // QtConcurrent::run()

      this->_watcher.setFuture(future);
    }
  }
538
  qCDebug(MeasurementAreaLog)
539 540 541 542 543 544 545
      << "doUpdate(): execution time: "
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
}

546
void MeasurementArea::deferUpdate() {
547
  if (this->_state == STATE::IDLE || this->_state == STATE::DEFERED) {
548
    qCDebug(MeasurementAreaLog) << "defereUpdate(): defer update.";
549 550 551 552 553 554 555 556 557
    if (this->_state == STATE::IDLE) {
      this->_progress.clear();
      this->_tileData.clear();
      emit this->progressChanged();
      emit this->tilesChanged();
    }
    this->setState(STATE::DEFERED);
    this->_timer.start(100);
  } else if (this->_state == STATE::UPDATEING) {
558
    qCDebug(MeasurementAreaLog) << "defereUpdate(): restart.";
559 560 561 562
    setState(STATE::RESTARTING);
  }
}

563
void MeasurementArea::storeTiles() {
564 565 566
  auto start = std::chrono::high_resolution_clock::now();

  if (this->_state == STATE::UPDATEING) {
567
    qCDebug(MeasurementAreaLog) << "storeTiles(): update.";
568 569 570 571 572 573 574 575

    this->_tileData = *this->_watcher.result();
    // This is expensive. Drawing tiles is expensive too.
    this->_progress = QVector<int>(this->_tileData.tiles.count(), 0);
    this->progressChanged();
    emit this->tilesChanged();
    setState(STATE::IDLE);
  } else if (this->_state == STATE::RESTARTING) {
576
    qCDebug(MeasurementAreaLog) << "storeTiles(): restart.";
577 578
    doUpdate();
  } else if (this->_state == STATE::STOP) {
579
    qCDebug(MeasurementAreaLog) << "storeTiles(): stop.";
580
  }
581
  qCDebug(MeasurementAreaLog)
582 583 584 585 586 587 588
      << "storeTiles() execution time: "
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
}

589
void MeasurementArea::disableUpdate() {
590 591 592 593
  setState(STATE::IDLE);
  this->_timer.stop();
}

594
void MeasurementArea::enableUpdate() {
595 596 597 598 599
  if (this->_state == STATE::STOP) {
    setState(STATE::IDLE);
  }
}

600
void MeasurementArea::init() {
601
  this->setObjectName(nameString);
602
  connect(&this->_tileHeight, &Fact::rawValueChanged, this,
603
          &MeasurementArea::deferUpdate);
604
  connect(&this->_tileWidth, &Fact::rawValueChanged, this,
605
          &MeasurementArea::deferUpdate);
606
  connect(&this->_minTileAreaPercent, &Fact::rawValueChanged, this,
607 608
          &MeasurementArea::deferUpdate);
  connect(this, &GeoArea::pathChanged, this, &MeasurementArea::deferUpdate);
609
  this->_timer.setSingleShot(true);
610
  connect(&this->_timer, &QTimer::timeout, this, &MeasurementArea::doUpdate);
611 612
  connect(&this->_watcher,
          &QFutureWatcher<std::unique_ptr<QmlObjectListModel>>::finished, this,
613
          &MeasurementArea::storeTiles);
614 615
}

616
void MeasurementArea::setState(MeasurementArea::STATE s) {
617 618 619 620 621 622 623 624
  if (this->_state != s) {
    auto oldState = this->_state;
    this->_state = s;
    if (s == STATE::IDLE || oldState == STATE::IDLE) {
      emit readyChanged();
    }
  }
}
625 626 627 628 629 630 631 632 633

bool MeasurementArea::holdProgress() const { return _holdProgress; }

void MeasurementArea::setHoldProgress(bool holdProgress) {
  if (_holdProgress != holdProgress) {
    _holdProgress = holdProgress;
    emit holdProgressChanged();
  }
}
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

bool getTiles(const FPolygon &area, Length tileHeight, Length tileWidth,
              Area minTileArea, std::vector<FPolygon> &tiles,
              BoundingBox &bbox) {
  if (area.outer().empty() || area.outer().size() < 4) {
    qCDebug(MeasurementAreaLog) << "Area has to few vertices.";
    return false;
  }

  if (tileWidth <= 0 * bu::si::meter || tileHeight <= 0 * bu::si::meter ||
      minTileArea < 0 * bu::si::meter * bu::si::meter) {
    std::stringstream ss;
    ss << "Parameters tileWidth (" << tileWidth << "), tileHeight ("
       << tileHeight << "), minTileArea (" << minTileArea
       << ") must be positive.";
    qCDebug(MeasurementAreaLog) << ss.str().c_str();
    return false;
  }

  if (bbox.corners.outer().size() != 5) {
    bbox.corners.clear();
    minimalBoundingBox(area, bbox);
  }

  if (bbox.corners.outer().size() < 5)
    return false;
  double bboxWidth = bbox.width;
  double bboxHeight = bbox.height;
  FPoint origin = bbox.corners.outer()[0];

  // cout << "Origin: " << origin[0] << " " << origin[1] << endl;
  // Transform _mArea polygon to bounding box coordinate system.
  trans::rotate_transformer<boost::geometry::degree, double, 2, 2> rotate(
      bbox.angle * 180 / M_PI);
  trans::translate_transformer<double, 2, 2> translate(-origin.get<0>(),
                                                       -origin.get<1>());
  FPolygon translated_polygon;
  FPolygon rotated_polygon;
  boost::geometry::transform(area, translated_polygon, translate);
  boost::geometry::transform(translated_polygon, rotated_polygon, rotate);
  bg::correct(rotated_polygon);
  // cout << bg::wkt<BoostPolygon2D>(rotated_polygon) << endl;

  size_t iMax = ceil(bboxWidth / tileWidth.value());
  size_t jMax = ceil(bboxHeight / tileHeight.value());

  if (iMax < 1 || jMax < 1) {
    std::stringstream ss;
    ss << "Tile width (" << tileWidth << ") or tile height (" << tileHeight
       << ") to large for measurement area.";
    qCDebug(MeasurementAreaLog) << ss.str().c_str();
    return false;
  }

  trans::rotate_transformer<boost::geometry::degree, double, 2, 2> rotate_back(
      -bbox.angle * 180 / M_PI);
  trans::translate_transformer<double, 2, 2> translate_back(origin.get<0>(),
                                                            origin.get<1>());
  for (size_t i = 0; i < iMax; ++i) {
    double x_min = tileWidth.value() * i;
    double x_max = x_min + tileWidth.value();
    for (size_t j = 0; j < jMax; ++j) {
      double y_min = tileHeight.value() * j;
      double y_max = y_min + tileHeight.value();

      FPolygon tile_unclipped;
      tile_unclipped.outer().push_back(FPoint{x_min, y_min});
      tile_unclipped.outer().push_back(FPoint{x_min, y_max});
      tile_unclipped.outer().push_back(FPoint{x_max, y_max});
      tile_unclipped.outer().push_back(FPoint{x_max, y_min});
      tile_unclipped.outer().push_back(FPoint{x_min, y_min});

      std::deque<FPolygon> boost_tiles;
      if (!boost::geometry::intersection(tile_unclipped, rotated_polygon,
                                         boost_tiles))
        continue;

      for (FPolygon t : boost_tiles) {
        if (bg::area(t) > minTileArea.value()) {
          // Transform boost_tile to world coordinate system.
          FPolygon rotated_tile;
          FPolygon translated_tile;
          boost::geometry::transform(t, rotated_tile, rotate_back);
          boost::geometry::transform(rotated_tile, translated_tile,
                                     translate_back);

          // Store tile and calculate center point.
          tiles.push_back(translated_tile);
        }
      }
    }
  }

  if (tiles.size() < 1) {
    std::stringstream ss;
    ss << "No tiles calculated. Is the minTileArea (" << minTileArea
       << ") parameter large enough?";
    qCDebug(MeasurementAreaLog) << ss.str().c_str();
    return false;
  }

  return true;
}