WimaMeasurementArea.cc 13.8 KB
Newer Older
1
#include "WimaMeasurementArea.h"
2
#include "QtConcurrentRun"
Valentin Platzgummer's avatar
Valentin Platzgummer committed
3 4 5 6
#include "SnakeTile.h"
#include "snake.h"

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

8 9
#include "QGCLoggingCategory.h"

10 11 12 13
#ifndef SNAKE_MAX_TILES
#define SNAKE_MAX_TILES 1000
#endif

14 15
QGC_LOGGING_CATEGORY(WimaMeasurementAreaLog, "WimaMeasurementAreaLog")

16 17 18 19 20 21 22 23 24 25 26 27
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.get(i);
    const auto *tile = qobject_cast<const SnakeTile *>(obj);
    if (tile != nullptr) {
      this->tiles.append(new SnakeTile(*tile, this));
    } else {
28
      qCWarning(WimaMeasurementAreaLog) << "TileData::operator=: nullptr";
29 30 31 32 33 34
    }
  }
  this->tileCenterPoints = other.tileCenterPoints;
  return *this;
}

35 36 37 38 39 40 41 42 43
bool TileData::operator==(const TileData &other) const {
  return this->tiles == other.tiles &&
         this->tileCenterPoints == other.tileCenterPoints;
}

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

44 45 46 47 48 49 50 51 52 53 54 55 56
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;
  }
}

57
const char *WimaMeasurementArea::settingsGroup = "MeasurementArea";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
58 59 60 61 62 63
const char *WimaMeasurementArea::tileHeightName = "TileHeight";
const char *WimaMeasurementArea::tileWidthName = "TileWidth";
const char *WimaMeasurementArea::minTileAreaName = "MinTileArea";
const char *WimaMeasurementArea::transectDistanceName = "TransectDistance";
const char *WimaMeasurementArea::minTransectLengthName = "MinTransectLength";
const char *WimaMeasurementArea::showTilesName = "ShowTiles";
64
const char *WimaMeasurementArea::WimaMeasurementAreaName = "Measurement Area";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
65

66 67
void tileDeleter(QmlObjectListModel *tiles) { tiles->clearAndDeleteContents(); }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
68
WimaMeasurementArea::WimaMeasurementArea(QObject *parent)
69 70 71 72
    : WimaArea(parent),
      _metaDataMap(FactMetaData::createMapFromJsonFile(
          QStringLiteral(":/json/WimaMeasurementArea.SettingsGroup.json"),
          this /* QObject parent */)),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightName],
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
      _minTileArea(SettingsFact(settingsGroup, _metaDataMap[minTileAreaName],
                                this /* QObject parent */)),
      _transectDistance(SettingsFact(settingsGroup,
                                     _metaDataMap[transectDistanceName],
                                     this /* QObject parent */)),
      _minTransectLength(SettingsFact(settingsGroup,
                                      _metaDataMap[minTransectLengthName],
                                      this /* QObject parent */)),
      _showTiles(SettingsFact(settingsGroup, _metaDataMap[showTilesName],
                              this /* QObject parent */)),
87
      _state(STATE::IDLE) {
88
  init();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
89 90
}

91 92 93 94 95 96
WimaMeasurementArea::WimaMeasurementArea(const WimaMeasurementArea &other,
                                         QObject *parent)
    : WimaArea(other, parent),
      _metaDataMap(FactMetaData::createMapFromJsonFile(
          QStringLiteral(":/json/WimaMeasurementArea.SettingsGroup.json"),
          this /* QObject parent */)),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightName],
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
      _minTileArea(SettingsFact(settingsGroup, _metaDataMap[minTileAreaName],
                                this /* QObject parent */)),
      _transectDistance(SettingsFact(settingsGroup,
                                     _metaDataMap[transectDistanceName],
                                     this /* QObject parent */)),
      _minTransectLength(SettingsFact(settingsGroup,
                                      _metaDataMap[minTransectLengthName],
                                      this /* QObject parent */)),
      _showTiles(SettingsFact(settingsGroup, _metaDataMap[showTilesName],
                              this /* QObject parent */)),
111
      _state(STATE::IDLE) {
112
  init();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
113 114
}

115 116 117 118 119
/*!
 * \overload operator=()
 *
 * Calls the inherited operator WimaArea::operator=().
 */
120 121 122 123 124 125 126
WimaMeasurementArea &WimaMeasurementArea::
operator=(const WimaMeasurementArea &other) {
  WimaArea::operator=(other);

  return *this;
}

127
WimaMeasurementArea::~WimaMeasurementArea() {}
Valentin Platzgummer's avatar
Valentin Platzgummer committed
128

129
QString WimaMeasurementArea::mapVisualQML() const {
130
  return QStringLiteral("WimaMeasurementAreaMapVisual.qml");
131
}
132

133
QString WimaMeasurementArea::editorQML() const {
134
  return QStringLiteral("WimaMeasurementAreaEditor.qml");
135 136
}

137 138
Fact *WimaMeasurementArea::tileHeight() { return &_tileHeight; }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
139 140 141 142 143 144 145 146 147 148
Fact *WimaMeasurementArea::tileWidth() { return &_tileWidth; }

Fact *WimaMeasurementArea::minTileArea() { return &_minTileArea; }

Fact *WimaMeasurementArea::transectDistance() { return &_transectDistance; }

Fact *WimaMeasurementArea::minTransectLength() { return &_minTransectLength; }

Fact *WimaMeasurementArea::showTiles() { return &_showTiles; }

149 150 151
QmlObjectListModel *WimaMeasurementArea::tiles() {
  return &this->_tileData.tiles;
}
152

153 154 155 156 157 158 159
const QVector<int> &WimaMeasurementArea::progress() const {
  return this->_progress;
}

QVector<int> WimaMeasurementArea::progressQml() const {
  return this->_progress;
}
160

161
const QmlObjectListModel *WimaMeasurementArea::tiles() const {
162 163 164 165 166 167 168 169 170
  return &this->_tileData.tiles;
}

const QVariantList &WimaMeasurementArea::tileCenterPoints() const {
  return this->_tileData.tileCenterPoints;
}

const TileData &WimaMeasurementArea::tileData() const {
  return this->_tileData;
171 172 173
}

int WimaMeasurementArea::maxTiles() const { return SNAKE_MAX_TILES; }
174

175
bool WimaMeasurementArea::ready() const { return this->_state == STATE::IDLE; }
176

177 178
void WimaMeasurementArea::saveToJson(QJsonObject &json) {
  this->WimaArea::saveToJson(json);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
179 180 181 182 183 184
  json[tileHeightName] = _tileHeight.rawValue().toDouble();
  json[tileWidthName] = _tileWidth.rawValue().toDouble();
  json[minTileAreaName] = _minTileArea.rawValue().toDouble();
  json[transectDistanceName] = _transectDistance.rawValue().toDouble();
  json[minTransectLengthName] = _minTransectLength.rawValue().toDouble();
  json[showTilesName] = _showTiles.rawValue().toBool();
185
  json[areaTypeName] = WimaMeasurementAreaName;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
186 187
}

188 189 190 191 192
bool WimaMeasurementArea::loadFromJson(const QJsonObject &json,
                                       QString &errorString) {
  if (this->WimaArea::loadFromJson(json, errorString)) {
    bool retVal = true;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
193 194
    if (json.contains(tileHeightName) && json[tileHeightName].isDouble()) {
      _tileHeight.setRawValue(json[tileHeightName].toDouble());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
195
    } else {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
196
      errorString.append(tr("Could not load tile height!\n"));
197
      retVal = false;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
198 199
    }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
200
    if (json.contains(tileWidthName) && json[tileWidthName].isDouble()) {
201
      _tileWidth.setRawValue(json[tileWidthName].toDouble());
202
    } else {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
203
      errorString.append(tr("Could not load tile width!\n"));
204 205
      retVal = false;
    }
206

Valentin Platzgummer's avatar
Valentin Platzgummer committed
207
    if (json.contains(minTileAreaName) && json[minTileAreaName].isDouble()) {
208
      _minTileArea.setRawValue(json[minTileAreaName].toDouble());
209
    } else {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
210
      errorString.append(tr("Could not load minimal tile area!\n"));
211
      retVal = false;
212
    }
213

Valentin Platzgummer's avatar
Valentin Platzgummer committed
214 215
    if (json.contains(transectDistanceName) &&
        json[transectDistanceName].isDouble()) {
216
      _transectDistance.setRawValue(json[transectDistanceName].toDouble());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
217 218 219 220 221 222 223
    } else {
      errorString.append(tr("Could not load transect distance!\n"));
      retVal = false;
    }

    if (json.contains(minTransectLengthName) &&
        json[minTransectLengthName].isDouble()) {
224
      _minTransectLength.setRawValue(json[minTransectLengthName].toDouble());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
225 226 227 228 229 230
    } else {
      errorString.append(tr("Could not load minimal transect length!\n"));
      retVal = false;
    }

    if (json.contains(showTilesName) && json[showTilesName].isBool()) {
231
      _showTiles.setRawValue(json[showTilesName].toBool());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
232 233 234 235
    } else {
      errorString.append(tr("Could not load show tiles !\n"));
      retVal = false;
    }
236 237 238 239
    return retVal;
  } else {
    return false;
  }
240
}
241 242

bool WimaMeasurementArea::setProgress(const QVector<int> &p) {
243
  if (!ready()) {
244 245 246 247 248 249 250 251
    if (p.size() == this->tiles()->count() && this->_progress != p) {
      this->_progress = p;
      emit progressChanged();
      return true;
    }
  }
  return false;
}
252 253
//!
//! \brief WimaMeasurementArea::doUpdate
254 255
//! \pre WimaMeasurementArea::deferUpdate must be called first, don't call
//! this function directly!
Valentin Platzgummer's avatar
Valentin Platzgummer committed
256 257 258
void WimaMeasurementArea::doUpdate() {
  using namespace snake;
  using namespace boost::units;
259

Valentin Platzgummer's avatar
Valentin Platzgummer committed
260
  auto start = std::chrono::high_resolution_clock::now();
261

262 263 264 265 266
  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;
267
  if (this->_state != STATE::UPDATE &&
268 269
      long(std::ceil(estNumTiles.value())) <= SNAKE_MAX_TILES &&
      this->count() >= 3 && this->isSimplePolygon()) {
270
    setState(STATE::UPDATE);
271 272 273
    auto polygon = this->coordinateList();
    for (auto &v : polygon) {
      v.setAltitude(0);
274 275
    }
    const auto minArea =
Valentin Platzgummer's avatar
Valentin Platzgummer committed
276
        this->_minTileArea.rawValue().toDouble() * si::meter * si::meter;
277 278 279
    auto *th = this->thread();
    auto future = QtConcurrent::run([polygon, th, height, width, minArea] {
      auto start = std::chrono::high_resolution_clock::now();
280

281 282
      DataPtr pData(new TileData());
      // Convert to ENU system.
283
      QGeoCoordinate origin = polygon.first();
284
      FPolygon polygonENU;
285
      areaToEnu(origin, polygon, polygonENU);
286
      std::vector<FPolygon> tilesENU;
287 288
      BoundingBox bbox;
      std::string errorString;
289
      // Generate tiles.
290 291
      if (snake::tiles(polygonENU, height, width, minArea, tilesENU, bbox,
                       errorString)) {
292
        // Convert to geo system.
293
        for (const auto &t : tilesENU) {
294
          auto geoTile = new SnakeTile(pData.get());
295 296 297 298 299
          for (const auto &v : t.outer()) {
            QGeoCoordinate geoVertex;
            fromENU(origin, v, geoVertex);
            geoTile->push_back(geoVertex);
          }
300 301
          pData->tiles.append(geoTile);
          // Calculate center.
302
          snake::FPoint center;
303 304 305 306
          snake::polygonCenter(t, center);
          QGeoCoordinate geoCenter;
          fromENU(origin, center, geoCenter);
          pData->tileCenterPoints.append(QVariant::fromValue(geoCenter));
Valentin Platzgummer's avatar
Valentin Platzgummer committed
307 308
        }
      }
309
      pData->moveToThread(th);
310 311 312 313 314 315 316 317

      qCDebug(WimaMeasurementAreaLog)
          << "doUpdate(): update time: "
          << std::chrono::duration_cast<std::chrono::milliseconds>(
                 std::chrono::high_resolution_clock::now() - start)
                 .count()
          << " ms";

318
      return pData;
319 320 321
    }); // QtConcurrent::run()

    this->_watcher.setFuture(future);
322
  }
323 324 325 326 327 328 329

  qCDebug(WimaMeasurementAreaLog)
      << "doUpdate(): execution time: "
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
330 331
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
332
void WimaMeasurementArea::deferUpdate() {
333 334 335 336 337 338 339 340 341 342 343
  if (this->_state == STATE::IDLE || this->_state == STATE::DEFERED) {
    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::UPDATE) {
    setState(STATE::RESTART);
344
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
345 346
}

347 348
void WimaMeasurementArea::storeTiles() {
  auto start = std::chrono::high_resolution_clock::now();
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

  if (this->_state == STATE::UPDATE) {

    qCDebug(WimaMeasurementAreaLog) << "storeTiles(): update.";

    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::RESTART) {
    qCDebug(WimaMeasurementAreaLog) << "storeTiles(): restart.";

    doUpdate();
  }
  qCDebug(WimaMeasurementAreaLog)
      << "storeTiles() execution time: "
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
371 372
}

373 374
void WimaMeasurementArea::init() {
  this->setObjectName(WimaMeasurementAreaName);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
375 376 377 378 379 380 381 382 383 384 385
  connect(&this->_tileHeight, &Fact::rawValueChanged, this,
          &WimaMeasurementArea::deferUpdate);
  connect(&this->_tileWidth, &Fact::rawValueChanged, this,
          &WimaMeasurementArea::deferUpdate);
  connect(&this->_minTileArea, &Fact::rawValueChanged, this,
          &WimaMeasurementArea::deferUpdate);
  connect(this, &WimaArea::pathChanged, this,
          &WimaMeasurementArea::deferUpdate);
  this->_timer.setSingleShot(true);
  connect(&this->_timer, &QTimer::timeout, this,
          &WimaMeasurementArea::doUpdate);
386 387 388
  connect(&this->_watcher,
          &QFutureWatcher<std::unique_ptr<QmlObjectListModel>>::finished, this,
          &WimaMeasurementArea::storeTiles);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
389 390
}

391 392 393 394 395 396 397 398 399 400
void WimaMeasurementArea::setState(WimaMeasurementArea::STATE s) {
  if (this->_state != s) {
    auto oldState = this->_state;
    this->_state = s;
    if (s == STATE::IDLE || oldState == STATE::IDLE) {
      emit readyChanged();
    }
  }
}

401 402
/*!
 * \class WimaMeasurementArea
403 404
 * \brief Class defining the area inside which the actual drone measurements
 * are performed.
405 406 407
 *
 * \sa WimaArea, WimaController, WimaPlaner
 */