MeasurementArea.cc 12.7 KB
Newer Older
1
#include "MeasurementArea.h"
2
#include "QtConcurrentRun"
3
#include "nemo_interface/SnakeTile.h"
4 5 6 7 8 9 10 11 12 13
#include "snake.h"

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

#include "QGCLoggingCategory.h"

#ifndef SNAKE_MAX_TILES
#define SNAKE_MAX_TILES 1000
#endif

14
QGC_LOGGING_CATEGORY(MeasurementAreaLog, "MeasurementAreaLog")
15 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[i];
    const auto *tile = qobject_cast<const SnakeTile *>(obj);
    if (tile != nullptr) {
      this->tiles.append(new SnakeTile(*tile, this));
    } else {
28
      qCWarning(MeasurementAreaLog) << "TileData::operator=: nullptr";
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
    }
  }
  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);
}

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 67 68 69 70 71
const char *MeasurementArea::settingsGroup = "MeasurementArea";
const char *MeasurementArea::tileHeightName = "TileHeight";
const char *MeasurementArea::tileWidthName = "TileWidth";
const char *MeasurementArea::minTileAreaName = "MinTileAreaPercent";
const char *MeasurementArea::showTilesName = "ShowTiles";
const char *MeasurementArea::MeasurementAreaName = "Measurement Area";
72

73 74
MeasurementArea::MeasurementArea(QObject *parent)
    : GeoArea(parent),
75
      _metaDataMap(FactMetaData::createMapFromJsonFile(
76
          QStringLiteral(":/json/MeasurementArea.SettingsGroup.json"),
77 78 79 80 81 82 83 84 85 86 87 88 89 90
          this /* QObject parent */)),
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightName],
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
      _minTileAreaPercent(SettingsFact(settingsGroup,
                                       _metaDataMap[minTileAreaName],
                                       this /* QObject parent */)),
      _showTiles(SettingsFact(settingsGroup, _metaDataMap[showTilesName],
                              this /* QObject parent */)),
      _state(STATE::IDLE) {
  init();
}

91 92
MeasurementArea::MeasurementArea(const MeasurementArea &other, QObject *parent)
    : GeoArea(other, parent),
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
      _metaDataMap(FactMetaData::createMapFromJsonFile(
          QStringLiteral(":/json/WimaMeasurementArea.SettingsGroup.json"),
          this /* QObject parent */)),
      _tileHeight(SettingsFact(settingsGroup, _metaDataMap[tileHeightName],
                               this /* QObject parent */)),
      _tileWidth(SettingsFact(settingsGroup, _metaDataMap[tileWidthName],
                              this /* QObject parent */)),
      _minTileAreaPercent(SettingsFact(settingsGroup,
                                       _metaDataMap[minTileAreaName],
                                       this /* QObject parent */)),
      _showTiles(SettingsFact(settingsGroup, _metaDataMap[showTilesName],
                              this /* QObject parent */)),
      _state(STATE::IDLE) {
  init();
}

109 110
MeasurementArea &MeasurementArea::operator=(const MeasurementArea &other) {
  GeoArea::operator=(other);
111 112 113
  return *this;
}

114
MeasurementArea::~MeasurementArea() {}
115

116 117
QString MeasurementArea::mapVisualQML() const {
  return QStringLiteral("MeasurementAreaMapVisual.qml");
118 119
}

120 121
QString MeasurementArea::editorQML() const {
  return QStringLiteral("MeasurementAreaEditor.qml");
122 123
}

124 125 126
MeasurementArea *MeasurementArea::clone(QObject *parent) const {
  return new MeasurementArea(*this, parent);
}
127

128
Fact *MeasurementArea::tileHeight() { return &_tileHeight; }
129

130
Fact *MeasurementArea::tileWidth() { return &_tileWidth; }
131

132
Fact *MeasurementArea::minTileArea() { return &_minTileAreaPercent; }
133

134
Fact *MeasurementArea::showTiles() { return &_showTiles; }
135

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

138
const QVector<int> &MeasurementArea::progress() const {
139 140 141
  return this->_progress;
}

142 143 144
QVector<int> MeasurementArea::progressQml() const { return this->_progress; }

const QmlObjectListModel *MeasurementArea::tiles() const {
145 146 147
  return &this->_tileData.tiles;
}

148
const QVariantList &MeasurementArea::tileCenterPoints() const {
149 150 151
  return this->_tileData.tileCenterPoints;
}

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

154
int MeasurementArea::maxTiles() const { return SNAKE_MAX_TILES; }
155

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

158
void MeasurementArea::saveToJson(QJsonObject &json) {
159
  if (ready()) {
160
    this->GeoArea::saveToJson(json);
161 162 163 164
    json[tileHeightName] = _tileHeight.rawValue().toDouble();
    json[tileWidthName] = _tileWidth.rawValue().toDouble();
    json[minTileAreaName] = _minTileAreaPercent.rawValue().toDouble();
    json[showTilesName] = _showTiles.rawValue().toBool();
165
    json[areaTypeName] = MeasurementAreaName;
166
  } else {
167
    qCDebug(MeasurementAreaLog) << "saveToJson(): not ready for saveing.";
168 169 170
  }
}

171 172 173
bool MeasurementArea::loadFromJson(const QJsonObject &json,
                                   QString &errorString) {
  if (this->GeoArea::loadFromJson(json, errorString)) {
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
    disableUpdate();
    bool retVal = true;

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

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

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

    if (!json.contains(showTilesName) || !json[showTilesName].isBool()) {
      errorString.append(tr("Could not load show tiles !\n"));
      retVal = false;
    } else {
      _showTiles.setRawValue(json[showTilesName].toBool());
    }

    enableUpdate();
    doUpdate();

    return retVal;
  } else {
    return false;
  }
}

214
bool MeasurementArea::setProgress(const QVector<int> &p) {
215 216 217 218 219 220 221 222 223 224 225
  if (ready()) {
    if (p.size() == this->tiles()->count() && this->_progress != p) {
      this->_progress = p;
      emit progressChanged();
      emit progressAccepted();
      return true;
    }
  }
  return false;
}
//!
226 227
//! \brief MeasurementArea::doUpdate
//! \pre MeasurementArea::deferUpdate must be called first, don't call
228
//! this function directly!
229
void MeasurementArea::doUpdate() {
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 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
  using namespace snake;
  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 &&
        this->count() >= 3 && this->isSimplePolygon()) {
      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;
        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));
          }
        }
        pData->moveToThread(th);

286
        qCDebug(MeasurementAreaLog)
287 288 289 290 291 292 293 294 295 296 297 298
            << "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);
    }
  }
299
  qCDebug(MeasurementAreaLog)
300 301 302 303 304 305 306
      << "doUpdate(): execution time: "
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
}

307
void MeasurementArea::deferUpdate() {
308
  if (this->_state == STATE::IDLE || this->_state == STATE::DEFERED) {
309
    qCDebug(MeasurementAreaLog) << "defereUpdate(): defer update.";
310 311 312 313 314 315 316 317 318
    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) {
319
    qCDebug(MeasurementAreaLog) << "defereUpdate(): restart.";
320 321 322 323
    setState(STATE::RESTARTING);
  }
}

324
void MeasurementArea::storeTiles() {
325 326 327
  auto start = std::chrono::high_resolution_clock::now();

  if (this->_state == STATE::UPDATEING) {
328
    qCDebug(MeasurementAreaLog) << "storeTiles(): update.";
329 330 331 332 333 334 335 336

    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) {
337
    qCDebug(MeasurementAreaLog) << "storeTiles(): restart.";
338 339
    doUpdate();
  } else if (this->_state == STATE::STOP) {
340
    qCDebug(MeasurementAreaLog) << "storeTiles(): stop.";
341
  }
342
  qCDebug(MeasurementAreaLog)
343 344 345 346 347 348 349
      << "storeTiles() execution time: "
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
}

350
void MeasurementArea::disableUpdate() {
351 352 353 354
  setState(STATE::IDLE);
  this->_timer.stop();
}

355
void MeasurementArea::enableUpdate() {
356 357 358 359 360
  if (this->_state == STATE::STOP) {
    setState(STATE::IDLE);
  }
}

361 362
void MeasurementArea::init() {
  this->setObjectName(MeasurementAreaName);
363
  connect(&this->_tileHeight, &Fact::rawValueChanged, this,
364
          &MeasurementArea::deferUpdate);
365
  connect(&this->_tileWidth, &Fact::rawValueChanged, this,
366
          &MeasurementArea::deferUpdate);
367
  connect(&this->_minTileAreaPercent, &Fact::rawValueChanged, this,
368 369
          &MeasurementArea::deferUpdate);
  connect(this, &GeoArea::pathChanged, this, &MeasurementArea::deferUpdate);
370
  this->_timer.setSingleShot(true);
371
  connect(&this->_timer, &QTimer::timeout, this, &MeasurementArea::doUpdate);
372 373
  connect(&this->_watcher,
          &QFutureWatcher<std::unique_ptr<QmlObjectListModel>>::finished, this,
374
          &MeasurementArea::storeTiles);
375 376
}

377
void MeasurementArea::setState(MeasurementArea::STATE s) {
378 379 380 381 382 383 384 385
  if (this->_state != s) {
    auto oldState = this->_state;
    this->_state = s;
    if (s == STATE::IDLE || oldState == STATE::IDLE) {
      emit readyChanged();
    }
  }
}