WimaMeasurementArea.cc 13.1 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
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) {
23
    const auto *obj = other.tiles[i];
24 25 26 27
    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
bool TileData::operator==(const TileData &other) const {
36 37 38 39 40 41 42 43 44 45 46
  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;
  }
47 48 49 50 51 52
}

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

53 54 55 56 57 58 59 60 61 62 63 64 65
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;
  }
}

66
const char *WimaMeasurementArea::settingsGroup = "MeasurementArea";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
67 68
const char *WimaMeasurementArea::tileHeightName = "TileHeight";
const char *WimaMeasurementArea::tileWidthName = "TileWidth";
69
const char *WimaMeasurementArea::minTileAreaName = "MinTileAreaPercent";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
70
const char *WimaMeasurementArea::showTilesName = "ShowTiles";
71
const char *WimaMeasurementArea::WimaMeasurementAreaName = "Measurement Area";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
72 73

WimaMeasurementArea::WimaMeasurementArea(QObject *parent)
74 75 76 77
    : WimaArea(parent),
      _metaDataMap(FactMetaData::createMapFromJsonFile(
          QStringLiteral(":/json/WimaMeasurementArea.SettingsGroup.json"),
          this /* QObject parent */)),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
78 79 80 81
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightName],
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
82 83
      _minTileAreaPercent(SettingsFact(settingsGroup,
                                       _metaDataMap[minTileAreaName],
84
                                       this /* QObject parent */)),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
85 86
      _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
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightName],
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
101 102
      _minTileAreaPercent(SettingsFact(settingsGroup,
                                       _metaDataMap[minTileAreaName],
103
                                       this /* QObject parent */)),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
104 105
      _showTiles(SettingsFact(settingsGroup, _metaDataMap[showTilesName],
                              this /* QObject parent */)),
106
      _state(STATE::IDLE) {
107
  init();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
108 109
}

110 111 112 113 114
/*!
 * \overload operator=()
 *
 * Calls the inherited operator WimaArea::operator=().
 */
115 116 117 118 119 120
WimaMeasurementArea &WimaMeasurementArea::
operator=(const WimaMeasurementArea &other) {
  WimaArea::operator=(other);
  return *this;
}

121
WimaMeasurementArea::~WimaMeasurementArea() {}
Valentin Platzgummer's avatar
Valentin Platzgummer committed
122

123
QString WimaMeasurementArea::mapVisualQML() const {
124
  return QStringLiteral("WimaMeasurementAreaMapVisual.qml");
125
}
126

127
QString WimaMeasurementArea::editorQML() const {
128
  return QStringLiteral("WimaMeasurementAreaEditor.qml");
129 130
}

131 132
Fact *WimaMeasurementArea::tileHeight() { return &_tileHeight; }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
133 134
Fact *WimaMeasurementArea::tileWidth() { return &_tileWidth; }

135
Fact *WimaMeasurementArea::minTileArea() { return &_minTileAreaPercent; }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
136 137 138

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

139 140 141
QmlObjectListModel *WimaMeasurementArea::tiles() {
  return &this->_tileData.tiles;
}
142

143 144 145 146 147 148 149
const QVector<int> &WimaMeasurementArea::progress() const {
  return this->_progress;
}

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

151
const QmlObjectListModel *WimaMeasurementArea::tiles() const {
152 153 154 155 156 157 158 159 160
  return &this->_tileData.tiles;
}

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

const TileData &WimaMeasurementArea::tileData() const {
  return this->_tileData;
161 162 163
}

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

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

167
void WimaMeasurementArea::saveToJson(QJsonObject &json) {
168
  if (ready()) {
169 170 171
    this->WimaArea::saveToJson(json);
    json[tileHeightName] = _tileHeight.rawValue().toDouble();
    json[tileWidthName] = _tileWidth.rawValue().toDouble();
172
    json[minTileAreaName] = _minTileAreaPercent.rawValue().toDouble();
173 174
    json[showTilesName] = _showTiles.rawValue().toBool();
    json[areaTypeName] = WimaMeasurementAreaName;
175 176 177
  } else {
    qCDebug(WimaMeasurementAreaLog) << "saveToJson(): not ready for saveing.";
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
178 179
}

180 181 182
bool WimaMeasurementArea::loadFromJson(const QJsonObject &json,
                                       QString &errorString) {
  if (this->WimaArea::loadFromJson(json, errorString)) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
183
    disableUpdate();
184 185
    bool retVal = true;

186
    if (!json.contains(tileHeightName) || !json[tileHeightName].isDouble()) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
187
      errorString.append(tr("Could not load tile height!\n"));
188
      retVal = false;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
189 190
    } else {
      _tileHeight.setRawValue(json[tileHeightName].toDouble());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
191 192
    }

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

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

207
    if (!json.contains(showTilesName) || !json[showTilesName].isBool()) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
208 209
      errorString.append(tr("Could not load show tiles !\n"));
      retVal = false;
210
    } else {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
211
      _showTiles.setRawValue(json[showTilesName].toBool());
212 213
    }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
214 215 216
    enableUpdate();
    doUpdate();

217 218 219 220
    return retVal;
  } else {
    return false;
  }
221
}
222 223

bool WimaMeasurementArea::setProgress(const QVector<int> &p) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
224
  if (ready()) {
225 226 227
    if (p.size() == this->tiles()->count() && this->_progress != p) {
      this->_progress = p;
      emit progressChanged();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
228
      emit progressAccepted();
229 230 231 232 233
      return true;
    }
  }
  return false;
}
234 235
//!
//! \brief WimaMeasurementArea::doUpdate
236 237
//! \pre WimaMeasurementArea::deferUpdate must be called first, don't call
//! this function directly!
Valentin Platzgummer's avatar
Valentin Platzgummer committed
238 239 240
void WimaMeasurementArea::doUpdate() {
  using namespace snake;
  using namespace boost::units;
241

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

Valentin Platzgummer's avatar
Valentin Platzgummer committed
244
  if (this->_state != STATE::UPDATEING && this->_state != STATE::STOP) {
245 246 247 248 249
    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;
250 251
    // Check some conditions.
    if (long(std::ceil(estNumTiles.value())) <= SNAKE_MAX_TILES &&
252
        this->count() >= 3 && this->isSimplePolygon()) {
253 254
      setState(STATE::UPDATEING);

255 256 257 258 259
      auto polygon = this->coordinateList();
      for (auto &v : polygon) {
        v.setAltitude(0);
      }
      const auto minArea =
260
          this->_minTileAreaPercent.rawValue().toDouble() / 100 * tileArea;
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
      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;
        std::string errorString;
        // Generate tiles.
        if (snake::tiles(polygonENU, height, width, minArea, tilesENU, bbox,
                         errorString)) {
          // 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.
            snake::FPoint center;
            snake::polygonCenter(t, center);
            QGeoCoordinate geoCenter;
            fromENU(origin, center, geoCenter);
            pData->tileCenterPoints.append(QVariant::fromValue(geoCenter));
291
          }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
292
        }
293
        pData->moveToThread(th);
294

295 296 297 298 299 300
        qCDebug(WimaMeasurementAreaLog)
            << "doUpdate(): update time: "
            << std::chrono::duration_cast<std::chrono::milliseconds>(
                   std::chrono::high_resolution_clock::now() - start)
                   .count()
            << " ms";
301

302 303
        return pData;
      }); // QtConcurrent::run()
304

305 306
      this->_watcher.setFuture(future);
    }
307
  }
308 309 310 311 312 313
  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
314 315
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
316
void WimaMeasurementArea::deferUpdate() {
317
  if (this->_state == STATE::IDLE || this->_state == STATE::DEFERED) {
318
    qCDebug(WimaMeasurementAreaLog) << "defereUpdate(): defer update.";
319 320 321 322 323 324 325 326
    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);
327 328 329
  } else if (this->_state == STATE::UPDATEING) {
    qCDebug(WimaMeasurementAreaLog) << "defereUpdate(): restart.";
    setState(STATE::RESTARTING);
330
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
331 332
}

333 334
void WimaMeasurementArea::storeTiles() {
  auto start = std::chrono::high_resolution_clock::now();
335

336
  if (this->_state == STATE::UPDATEING) {
337 338 339 340 341 342 343 344
    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);
345
  } else if (this->_state == STATE::RESTARTING) {
346 347
    qCDebug(WimaMeasurementAreaLog) << "storeTiles(): restart.";
    doUpdate();
348 349
  } else if (this->_state == STATE::STOP) {
    qCDebug(WimaMeasurementAreaLog) << "storeTiles(): stop.";
350 351 352 353 354 355 356
  }
  qCDebug(WimaMeasurementAreaLog)
      << "storeTiles() execution time: "
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
357 358
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
359
void WimaMeasurementArea::disableUpdate() {
360 361 362 363
  setState(STATE::IDLE);
  this->_timer.stop();
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
364
void WimaMeasurementArea::enableUpdate() {
365 366 367 368 369
  if (this->_state == STATE::STOP) {
    setState(STATE::IDLE);
  }
}

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

388 389 390 391 392 393 394 395 396 397
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();
    }
  }
}

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