#include "WimaMeasurementArea.h" #include "QtConcurrentRun" #include "SnakeTile.h" #include "snake.h" #include #ifndef SNAKE_MAX_TILES #define SNAKE_MAX_TILES 1000 #endif const char *WimaMeasurementArea::settingsGroup = "MeasurementArea"; 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"; const char *WimaMeasurementArea::WimaMeasurementAreaName = "Measurement Area"; void tileDeleter(QmlObjectListModel *tiles) { tiles->clearAndDeleteContents(); } WimaMeasurementArea::WimaMeasurementArea(QObject *parent) : WimaArea(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 */)), _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 */)), _pTiles(new QmlObjectListModel(), &tileDeleter), _calculating(false), _polygonValid(false) { init(); } WimaMeasurementArea::WimaMeasurementArea(const WimaMeasurementArea &other, QObject *parent) : WimaArea(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 */)), _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 */)), _pTiles(new QmlObjectListModel(), &tileDeleter), _calculating(false), _polygonValid(false) { init(); } /*! * \overload operator=() * * Calls the inherited operator WimaArea::operator=(). */ WimaMeasurementArea &WimaMeasurementArea:: operator=(const WimaMeasurementArea &other) { WimaArea::operator=(other); return *this; } WimaMeasurementArea::~WimaMeasurementArea() { this->_pTiles->clearAndDeleteContents(); } QString WimaMeasurementArea::mapVisualQML() const { return "WimaMeasurementAreaMapVisual.qml"; } QString WimaMeasurementArea::editorQML() const { return "WimaMeasurementAreaEditor.qml"; } Fact *WimaMeasurementArea::tileHeight() { return &_tileHeight; } 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; } QmlObjectListModel *WimaMeasurementArea::tiles() { return this->_pTiles.get(); } int WimaMeasurementArea::maxTiles() { return SNAKE_MAX_TILES; } bool WimaMeasurementArea::ready() { return !_calculating; } void WimaMeasurementArea::saveToJson(QJsonObject &json) { this->WimaArea::saveToJson(json); 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(); json[areaTypeName] = WimaMeasurementAreaName; } bool WimaMeasurementArea::loadFromJson(const QJsonObject &json, QString &errorString) { if (this->WimaArea::loadFromJson(json, errorString)) { bool retVal = true; if (json.contains(tileHeightName) && json[tileHeightName].isDouble()) { _tileHeight.setRawValue(json[tileHeightName].toDouble()); } else { errorString.append(tr("Could not load tile height!\n")); retVal = false; } if (json.contains(tileWidthName) && json[tileWidthName].isDouble()) { _tileHeight.setRawValue(json[tileWidthName].toDouble()); } else { errorString.append(tr("Could not load tile width!\n")); retVal = false; } if (json.contains(minTileAreaName) && json[minTileAreaName].isDouble()) { _tileHeight.setRawValue(json[minTileAreaName].toDouble()); } else { errorString.append(tr("Could not load minimal tile area!\n")); retVal = false; } if (json.contains(transectDistanceName) && json[transectDistanceName].isDouble()) { _tileHeight.setRawValue(json[transectDistanceName].toDouble()); } else { errorString.append(tr("Could not load transect distance!\n")); retVal = false; } if (json.contains(minTransectLengthName) && json[minTransectLengthName].isDouble()) { _tileHeight.setRawValue(json[minTransectLengthName].toDouble()); } else { errorString.append(tr("Could not load minimal transect length!\n")); retVal = false; } if (json.contains(showTilesName) && json[showTilesName].isBool()) { _tileHeight.setRawValue(json[showTilesName].toDouble()); } else { errorString.append(tr("Could not load show tiles !\n")); retVal = false; } return retVal; } else { return false; } } //! //! \brief WimaMeasurementArea::doUpdate //! \pre WimaMeasurementArea::deferUpdate must be called first, don't call this //! function directly! void WimaMeasurementArea::doUpdate() { using namespace snake; using namespace boost::units; #ifdef SNAKE_SHOW_TIME auto start = std::chrono::high_resolution_clock::now(); #endif 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; if (!this->_calculating && long(std::ceil(estNumTiles.value())) <= SNAKE_MAX_TILES && this->count() >= 3 && this->isSimplePolygon()) { this->_calculating = true; if (!this->_polygonValid) { this->_polygon = this->coordinateList(); for (auto &v : this->_polygon) { v.setAltitude(0); } this->_polygonValid = true; } const auto &polygon = this->_polygon; const auto minArea = this->_minTileArea.rawValue().toDouble() * si::meter * si::meter; auto *th = this->thread(); auto future = QtConcurrent::run([polygon, th, height, width, minArea] { #ifdef SNAKE_SHOW_TIME auto start = std::chrono::high_resolution_clock::now(); #endif TilesPtr pTiles(new QmlObjectListModel(), &tileDeleter); QGeoCoordinate origin = polygon.first(); BoostPolygon polygonENU; areaToEnu(origin, polygon, polygonENU); std::vector tilesENU; BoundingBox bbox; std::string errorString; if (snake::tiles(polygonENU, height, width, minArea, tilesENU, bbox, errorString)) { for (const auto &t : tilesENU) { auto geoTile = new SnakeTile(pTiles.get()); for (const auto &v : t.outer()) { QGeoCoordinate geoVertex; fromENU(origin, v, geoVertex); geoTile->push_back(geoVertex); } pTiles->append(geoTile); } } pTiles->moveToThread(th); #ifdef SNAKE_SHOW_TIME qDebug() << "WimaMeasurementArea::doUpdate concurrent update execution time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) .count() << " ms"; #endif return pTiles; }); // QtConcurrent::run() this->_watcher.setFuture(future); } #ifdef SNAKE_SHOW_TIME qDebug() << "WimaMeasurementArea::doUpdate execution time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) .count() << " ms"; #endif } void WimaMeasurementArea::deferUpdate() { if (this->_timer.isActive()) { this->_timer.stop(); } if (this->_pTiles->count() > 0) { this->_pTiles->clearAndDeleteContents(); emit this->tilesChanged(); } this->_timer.start(100); } void WimaMeasurementArea::storeTiles() { #ifdef SNAKE_SHOW_TIME auto start = std::chrono::high_resolution_clock::now(); #endif this->_pTiles = this->_watcher.result(); this->_calculating = false; emit this ->tilesChanged(); // This is expensive. Drawing tiles is expensive too. #ifdef SNAKE_SHOW_TIME qDebug() << "WimaMeasurementArea::storeTiles() execution time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) .count() << " ms"; #endif } void WimaMeasurementArea::init() { this->setObjectName(WimaMeasurementAreaName); 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); connect(this, &WimaArea::pathChanged, [this] { this->_polygonValid = false; }); this->_timer.setSingleShot(true); connect(&this->_timer, &QTimer::timeout, this, &WimaMeasurementArea::doUpdate); connect(&this->_watcher, &QFutureWatcher>::finished, this, &WimaMeasurementArea::storeTiles); } /*! * \class WimaMeasurementArea * \brief Class defining the area inside which the actual drone measurements * are performed. * * \sa WimaArea, WimaController, WimaPlaner */