#include "MeasurementArea.h" #include "QtConcurrentRun" #include "nemo_interface/SnakeTile.h" #include "snake.h" #include #include "QGCLoggingCategory.h" #ifndef SNAKE_MAX_TILES #define SNAKE_MAX_TILES 1000 #endif QGC_LOGGING_CATEGORY(MeasurementAreaLog, "MeasurementAreaLog") 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(obj); if (tile != nullptr) { this->tiles.append(new SnakeTile(*tile, this)); } else { qCWarning(MeasurementAreaLog) << "TileData::operator=: nullptr"; } } 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; } } 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"; MeasurementArea::MeasurementArea(QObject *parent) : GeoArea(parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/MeasurementArea.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(); } MeasurementArea::MeasurementArea(const MeasurementArea &other, QObject *parent) : GeoArea(other, parent), _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(); } MeasurementArea &MeasurementArea::operator=(const MeasurementArea &other) { GeoArea::operator=(other); return *this; } MeasurementArea::~MeasurementArea() {} QString MeasurementArea::mapVisualQML() const { return QStringLiteral("MeasurementAreaMapVisual.qml"); } QString MeasurementArea::editorQML() const { return QStringLiteral("MeasurementAreaEditor.qml"); } MeasurementArea *MeasurementArea::clone(QObject *parent) const { return new MeasurementArea(*this, parent); } Fact *MeasurementArea::tileHeight() { return &_tileHeight; } Fact *MeasurementArea::tileWidth() { return &_tileWidth; } Fact *MeasurementArea::minTileArea() { return &_minTileAreaPercent; } Fact *MeasurementArea::showTiles() { return &_showTiles; } QmlObjectListModel *MeasurementArea::tiles() { return &this->_tileData.tiles; } const QVector &MeasurementArea::progress() const { return this->_progress; } QVector MeasurementArea::progressQml() const { return this->_progress; } const QmlObjectListModel *MeasurementArea::tiles() const { return &this->_tileData.tiles; } const QVariantList &MeasurementArea::tileCenterPoints() const { return this->_tileData.tileCenterPoints; } const TileData &MeasurementArea::tileData() const { return this->_tileData; } int MeasurementArea::maxTiles() const { return SNAKE_MAX_TILES; } bool MeasurementArea::ready() const { return this->_state == STATE::IDLE; } void MeasurementArea::saveToJson(QJsonObject &json) { if (ready()) { this->GeoArea::saveToJson(json); json[tileHeightName] = _tileHeight.rawValue().toDouble(); json[tileWidthName] = _tileWidth.rawValue().toDouble(); json[minTileAreaName] = _minTileAreaPercent.rawValue().toDouble(); json[showTilesName] = _showTiles.rawValue().toBool(); json[areaTypeName] = MeasurementAreaName; } else { qCDebug(MeasurementAreaLog) << "saveToJson(): not ready for saveing."; } } bool MeasurementArea::loadFromJson(const QJsonObject &json, QString &errorString) { if (this->GeoArea::loadFromJson(json, errorString)) { 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; } } bool MeasurementArea::setProgress(const QVector &p) { if (ready()) { if (p.size() == this->tiles()->count() && this->_progress != p) { this->_progress = p; emit progressChanged(); emit progressAccepted(); return true; } } return false; } //! //! \brief MeasurementArea::doUpdate //! \pre MeasurementArea::deferUpdate must be called first, don't call //! this function directly! void MeasurementArea::doUpdate() { 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 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); qCDebug(MeasurementAreaLog) << "doUpdate(): update time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) .count() << " ms"; return pData; }); // QtConcurrent::run() this->_watcher.setFuture(future); } } qCDebug(MeasurementAreaLog) << "doUpdate(): execution time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) .count() << " ms"; } void MeasurementArea::deferUpdate() { if (this->_state == STATE::IDLE || this->_state == STATE::DEFERED) { qCDebug(MeasurementAreaLog) << "defereUpdate(): defer update."; 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) { qCDebug(MeasurementAreaLog) << "defereUpdate(): restart."; setState(STATE::RESTARTING); } } void MeasurementArea::storeTiles() { auto start = std::chrono::high_resolution_clock::now(); if (this->_state == STATE::UPDATEING) { qCDebug(MeasurementAreaLog) << "storeTiles(): update."; this->_tileData = *this->_watcher.result(); // This is expensive. Drawing tiles is expensive too. this->_progress = QVector(this->_tileData.tiles.count(), 0); this->progressChanged(); emit this->tilesChanged(); setState(STATE::IDLE); } else if (this->_state == STATE::RESTARTING) { qCDebug(MeasurementAreaLog) << "storeTiles(): restart."; doUpdate(); } else if (this->_state == STATE::STOP) { qCDebug(MeasurementAreaLog) << "storeTiles(): stop."; } qCDebug(MeasurementAreaLog) << "storeTiles() execution time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) .count() << " ms"; } void MeasurementArea::disableUpdate() { setState(STATE::IDLE); this->_timer.stop(); } void MeasurementArea::enableUpdate() { if (this->_state == STATE::STOP) { setState(STATE::IDLE); } } void MeasurementArea::init() { this->setObjectName(MeasurementAreaName); connect(&this->_tileHeight, &Fact::rawValueChanged, this, &MeasurementArea::deferUpdate); connect(&this->_tileWidth, &Fact::rawValueChanged, this, &MeasurementArea::deferUpdate); connect(&this->_minTileAreaPercent, &Fact::rawValueChanged, this, &MeasurementArea::deferUpdate); connect(this, &GeoArea::pathChanged, this, &MeasurementArea::deferUpdate); this->_timer.setSingleShot(true); connect(&this->_timer, &QTimer::timeout, this, &MeasurementArea::doUpdate); connect(&this->_watcher, &QFutureWatcher>::finished, this, &MeasurementArea::storeTiles); } void MeasurementArea::setState(MeasurementArea::STATE s) { if (this->_state != s) { auto oldState = this->_state; this->_state = s; if (s == STATE::IDLE || oldState == STATE::IDLE) { emit readyChanged(); } } }