#include "WimaMeasurementArea.h" #include "QtConcurrentRun" #include "SnakeTile.h" #include "snake.h" #include #ifndef SNAKE_MAX_TILES #define SNAKE_MAX_TILES 1000 #endif 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(obj); if (tile != nullptr) { this->tiles.append(new SnakeTile(*tile, this)); } else { qWarning("TileData::operator=: nullptr"); } } this->tileCenterPoints = other.tileCenterPoints; return *this; } 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); } 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 *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 */)), _calculating(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 */)), _calculating(false) { init(); } /*! * \overload operator=() * * Calls the inherited operator WimaArea::operator=(). */ WimaMeasurementArea &WimaMeasurementArea:: operator=(const WimaMeasurementArea &other) { WimaArea::operator=(other); return *this; } WimaMeasurementArea::~WimaMeasurementArea() {} 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->_tileData.tiles; } const QmlObjectListModel *WimaMeasurementArea::tiles() const { return &this->_tileData.tiles; } const QVariantList &WimaMeasurementArea::tileCenterPoints() const { return this->_tileData.tileCenterPoints; } const TileData &WimaMeasurementArea::tileData() const { return this->_tileData; } int WimaMeasurementArea::maxTiles() const { return SNAKE_MAX_TILES; } bool WimaMeasurementArea::ready() const { 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()) { _tileWidth.setRawValue(json[tileWidthName].toDouble()); } else { errorString.append(tr("Could not load tile width!\n")); retVal = false; } if (json.contains(minTileAreaName) && json[minTileAreaName].isDouble()) { _minTileArea.setRawValue(json[minTileAreaName].toDouble()); } else { errorString.append(tr("Could not load minimal tile area!\n")); retVal = false; } if (json.contains(transectDistanceName) && json[transectDistanceName].isDouble()) { _transectDistance.setRawValue(json[transectDistanceName].toDouble()); } else { errorString.append(tr("Could not load transect distance!\n")); retVal = false; } if (json.contains(minTransectLengthName) && json[minTransectLengthName].isDouble()) { _minTransectLength.setRawValue(json[minTransectLengthName].toDouble()); } else { errorString.append(tr("Could not load minimal transect length!\n")); retVal = false; } if (json.contains(showTilesName) && json[showTilesName].isBool()) { _showTiles.setRawValue(json[showTilesName].toBool()); } 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; auto polygon = this->coordinateList(); for (auto &v : polygon) { v.setAltitude(0); } 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 DataPtr pData(new TileData()); // Convert to ENU system. QGeoCoordinate origin = polygon.first(); BoostPolygon 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::BoostPoint center; snake::polygonCenter(t, center); QGeoCoordinate geoCenter; fromENU(origin, center, geoCenter); pData->tileCenterPoints.append(QVariant::fromValue(geoCenter)); } } pData->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 pData; }); // 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->_tileData.size() > 0) { this->_tileData.clear(); emit this->tilesChanged(); } this->_timer.start(100); } void WimaMeasurementArea::storeTiles() { #ifdef SNAKE_SHOW_TIME auto start = std::chrono::high_resolution_clock::now(); #endif this->_tileData = *this->_watcher.result(); this->_calculating = false; // This is expensive. Drawing tiles is expensive too. emit this->tilesChanged(); #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); 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 */