#include "LinearGenerator.h" #include "QGCLoggingCategory.h" QGC_LOGGING_CATEGORY(LinearGeneratorLog, "LinearGeneratorLog") #define CLIPPER_SCALE 1000000 #include "clipper/clipper.hpp" #include "SnakeTile.h" #include "Wima/RoutingThread.h" namespace routing { bool linearTransects(const snake::FPolygon &polygon, const std::vector &tiles, snake::Length distance, snake::Angle angle, snake::Length minLength, snake::Transects &transects); const char *LinearGenerator::settingsGroup = "LinearGenerator"; const char *LinearGenerator::distanceName = "TransectDistance"; const char *LinearGenerator::alphaName = "Alpha"; const char *LinearGenerator::minLengthName = "MinLength"; LinearGenerator::LinearGenerator(QObject *parent) : LinearGenerator(nullptr, parent) {} LinearGenerator::LinearGenerator(GeneratorBase::Data d, QObject *parent) : GeneratorBase(d, parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/LinearGenerator.SettingsGroup.json"), this)), _distance(settingsGroup, _metaDataMap[distanceName]), _alpha(settingsGroup, _metaDataMap[alphaName]), _minLength(settingsGroup, _metaDataMap[minLengthName]) { establishConnections(); } QString LinearGenerator::editorQml() { return QStringLiteral("LinearGeneratorEditor.qml"); } QString LinearGenerator::name() { return QStringLiteral("Linear Generator"); } QString LinearGenerator::abbreviation() { return QStringLiteral("L. Gen."); } bool LinearGenerator::get(Generator &generator) { if (_d) { if (this->_d->isValid()) { // Prepare data. auto origin = this->_d->origin(); origin.setAltitude(0); if (!origin.isValid()) { qCWarning(LinearGeneratorLog) << "get(): origin invalid."; } auto geoPolygon = this->_d->measurementArea().coordinateList(); for (auto &v : geoPolygon) { if (v.isValid()) { v.setAltitude(0); } else { qCWarning(LinearGeneratorLog) << "get(): measurement area invalid."; for (const auto &w : geoPolygon) { qCWarning(LinearGeneratorLog) << w; } return false; } } auto pPolygon = std::make_shared(); snake::areaToEnu(origin, geoPolygon, *pPolygon); // Progress and tiles. const auto &progress = this->_d->measurementArea().progress(); const auto *tiles = this->_d->measurementArea().tiles(); auto pTiles = std::make_shared>(); if (progress.size() == tiles->count()) { for (int i = 0; i < tiles->count(); ++i) { if (progress[i] == 100) { const auto *tile = tiles->value(i); if (tile != nullptr) { snake::FPolygon tileENU; snake::areaToEnu(origin, tile->coordinateList(), tileENU); pTiles->push_back(std::move(tileENU)); } else { qCWarning(LinearGeneratorLog) << "get(): progress.size() != tiles->count()."; return false; } } } } else { qCWarning(LinearGeneratorLog) << "get(): progress.size() != tiles->count()."; return false; } auto geoDepot = this->_d->serviceArea().depot(); if (!geoDepot.isValid()) { qCWarning(LinearGeneratorLog) << "get(): depot invalid." << geoDepot; return false; } snake::FPoint depot; snake::toENU(origin, geoDepot, depot); // Fetch transect parameter. auto distance = snake::Length(this->_distance.rawValue().toDouble() * bu::si::meter); auto minLength = snake::Length(this->_minLength.rawValue().toDouble() * bu::si::meter); auto alpha = snake::Angle(this->_alpha.rawValue().toDouble() * bu::degree::degree); generator = [depot, pPolygon, pTiles, distance, alpha, minLength](snake::Transects &transects) -> bool { bool value = linearTransects(*pPolygon, *pTiles, distance, alpha, minLength, transects); transects.insert(transects.begin(), snake::FLineString{depot}); return value; }; return true; } else { qCWarning(LinearGeneratorLog) << "get(): data invalid."; return false; } } else { qCWarning(LinearGeneratorLog) << "get(): data member not set."; return false; } } Fact *LinearGenerator::distance() { return &_distance; } Fact *LinearGenerator::alpha() { return &_alpha; } Fact *LinearGenerator::minLength() { return &_minLength; } void LinearGenerator::establishConnections() { if (this->_d && !this->_connectionsEstablished) { connect(this->_d.get(), &WimaPlanData::measurementAreaChanged, this, &GeneratorBase::generatorChanged); connect(this->_d.get(), &WimaPlanData::originChanged, this, &GeneratorBase::generatorChanged); connect(&this->_d->measurementArea(), &WimaMeasurementAreaData::progressChanged, this, &GeneratorBase::generatorChanged); connect(&this->_d->measurementArea(), &WimaMeasurementAreaData::tileDataChanged, this, &GeneratorBase::generatorChanged); connect(&this->_d->serviceArea(), &WimaServiceAreaData::depotChanged, this, &GeneratorBase::generatorChanged); connect(this->distance(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); connect(this->alpha(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); connect(this->minLength(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); this->_connectionsEstablished = true; } } void LinearGenerator::deleteConnections() { if (this->_d && this->_connectionsEstablished) { disconnect(this->_d.get(), &WimaPlanData::measurementAreaChanged, this, &GeneratorBase::generatorChanged); disconnect(this->_d.get(), &WimaPlanData::originChanged, this, &GeneratorBase::generatorChanged); disconnect(&this->_d->measurementArea(), &WimaMeasurementAreaData::progressChanged, this, &GeneratorBase::generatorChanged); disconnect(&this->_d->measurementArea(), &WimaMeasurementAreaData::tileDataChanged, this, &GeneratorBase::generatorChanged); disconnect(&this->_d->serviceArea(), &WimaServiceAreaData::depotChanged, this, &GeneratorBase::generatorChanged); disconnect(this->distance(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); disconnect(this->alpha(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); disconnect(this->minLength(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); this->_connectionsEstablished = false; } } bool linearTransects(const snake::FPolygon &polygon, const std::vector &tiles, snake::Length distance, snake::Angle angle, snake::Length minLength, snake::Transects &transects) { namespace tr = bg::strategy::transform; auto s1 = std::chrono::high_resolution_clock::now(); // Check preconitions if (polygon.outer().size() >= 3) { // Convert to ENU system. std::string error; // Check validity. if (!bg::is_valid(polygon, error)) { std::stringstream ss; ss << bg::wkt(polygon); qCWarning(LinearGeneratorLog) << "linearTransects(): " "invalid polygon. " << error.c_str() << ss.str().c_str(); } else { tr::rotate_transformer rotate(angle.value() * 180 / M_PI); // Rotate polygon by angle and calculate bounding box. snake::FPolygon polygonENURotated; bg::transform(polygon.outer(), polygonENURotated.outer(), rotate); snake::FBox box; boost::geometry::envelope(polygonENURotated, box); double x0 = box.min_corner().get<0>(); double y0 = box.min_corner().get<1>(); double x1 = box.max_corner().get<0>(); double y1 = box.max_corner().get<1>(); // Generate transects and convert them to clipper path. size_t num_t = ceil((y1 - y0) / distance.value()); // number of transects vector transectsClipper; transectsClipper.reserve(num_t); for (size_t i = 0; i < num_t; ++i) { // calculate transect snake::FPoint v1{x0, y0 + i * distance.value()}; snake::FPoint v2{x1, y0 + i * distance.value()}; snake::FLineString transect; transect.push_back(v1); transect.push_back(v2); // transform back snake::FLineString temp_transect; tr::rotate_transformer rotate_back( -angle.value() * 180 / M_PI); bg::transform(transect, temp_transect, rotate_back); // to clipper ClipperLib::IntPoint c1{static_cast( temp_transect[0].get<0>() * CLIPPER_SCALE), static_cast( temp_transect[0].get<1>() * CLIPPER_SCALE)}; ClipperLib::IntPoint c2{static_cast( temp_transect[1].get<0>() * CLIPPER_SCALE), static_cast( temp_transect[1].get<1>() * CLIPPER_SCALE)}; ClipperLib::Path path{c1, c2}; transectsClipper.push_back(path); } if (transectsClipper.size() == 0) { std::stringstream ss; ss << "Not able to generate transects. Parameter: distance = " << distance << std::endl; qCWarning(LinearGeneratorLog) << "linearTransects(): " << ss.str().c_str(); return false; } // Convert measurement area to clipper path. snake::FPolygon shrinked; snake::offsetPolygon(polygon, shrinked, -0.2); auto &outer = shrinked.outer(); ClipperLib::Path polygonClipper; for (auto vertex : outer) { polygonClipper.push_back(ClipperLib::IntPoint{ static_cast(vertex.get<0>() * CLIPPER_SCALE), static_cast(vertex.get<1>() * CLIPPER_SCALE)}); } // Perform clipping. // Clip transects to measurement area. ClipperLib::Clipper clipper; clipper.AddPath(polygonClipper, ClipperLib::ptClip, true); clipper.AddPaths(transectsClipper, ClipperLib::ptSubject, false); ClipperLib::PolyTree clippedTransecs; clipper.Execute(ClipperLib::ctIntersection, clippedTransecs, ClipperLib::pftNonZero, ClipperLib::pftNonZero); // Subtract holes. if (tiles.size() > 0) { vector processedTiles; for (const auto &tile : tiles) { ClipperLib::Path path; for (const auto &v : tile.outer()) { path.push_back(ClipperLib::IntPoint{ static_cast(v.get<0>() * CLIPPER_SCALE), static_cast(v.get<1>() * CLIPPER_SCALE)}); } processedTiles.push_back(std::move(path)); } clipper.Clear(); for (const auto &child : clippedTransecs.Childs) { clipper.AddPath(child->Contour, ClipperLib::ptSubject, false); } clipper.AddPaths(processedTiles, ClipperLib::ptClip, true); clippedTransecs.Clear(); clipper.Execute(ClipperLib::ctDifference, clippedTransecs, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } // Extract transects from PolyTree and convert them to BoostLineString for (const auto &child : clippedTransecs.Childs) { const auto &clipperTransect = child->Contour; snake::FPoint v1{ static_cast(clipperTransect[0].X) / CLIPPER_SCALE, static_cast(clipperTransect[0].Y) / CLIPPER_SCALE}; snake::FPoint v2{ static_cast(clipperTransect[1].X) / CLIPPER_SCALE, static_cast(clipperTransect[1].Y) / CLIPPER_SCALE}; snake::FLineString transect{v1, v2}; if (bg::length(transect) >= minLength.value()) { transects.push_back(transect); } } if (transects.size() == 0) { std::stringstream ss; ss << "Not able to generatetransects. Parameter: minLength = " << minLength << std::endl; qCWarning(LinearGeneratorLog) << "linearTransects(): " << ss.str().c_str(); return false; } qCWarning(LinearGeneratorLog) << "linearTransects(): time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - s1) .count() << " ms"; return true; } } return false; } } // namespace routing