#include "CircularGenerator.h" #include "JsonHelper.h" #include "QGCLoggingCategory.h" #include "SettingsFact.h" #define CLIPPER_SCALE 1000000 #include "RoutingThread.h" #include "geometry/GenericCircle.h" #include "geometry/MeasurementArea.h" #include "geometry/SafeArea.h" #include "geometry/clipper/clipper.hpp" #include "nemo_interface/MeasurementTile.h" QGC_LOGGING_CATEGORY(CircularGeneratorLog, "CircularGeneratorLog") using namespace ClipperLib; template <> inline auto get<0>(const IntPoint &p) { return p.X; } template <> inline auto get<1>(const IntPoint &p) { return p.Y; } namespace routing { namespace { GeneratorBase *creator(QObject *parent) { return new CircularGenerator(parent); } const char *distanceKey = "TransectDistance"; const char *deltaAlphaKey = "DeltaAlpha"; const char *minLengthKey = "MinLength"; const char *referenceKey = "ReferencePoint"; } // namespace bool circularTransects(const geometry::FPoint &reference, const geometry::FPolygon &polygon, const std::vector &tiles, geometry::Length deltaR, geometry::Angle deltaAlpha, geometry::Length minLength, geometry::LineStringArray &transects); const char *CircularGenerator::settingsGroup = "CircularGenerator"; const char *CircularGenerator::typeString = "CircularGenerator"; REGISTER_GENERATOR(CircularGenerator::typeString, creator) CircularGenerator::CircularGenerator(QObject *parent) : GeneratorBase(parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/CircularGenerator.SettingsGroup.json"), this)), _distance(settingsGroup, _metaDataMap[distanceKey]), _deltaAlpha(settingsGroup, _metaDataMap[deltaAlphaKey]), _minLength(settingsGroup, _metaDataMap[minLengthKey]), _measurementArea(nullptr) { init(); } CircularGenerator::CircularGenerator(GeneratorBase::Data d, QObject *parent) : GeneratorBase(d, parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/CircularGenerator.SettingsGroup.json"), this)), _distance(settingsGroup, _metaDataMap[distanceKey]), _deltaAlpha(settingsGroup, _metaDataMap[deltaAlphaKey]), _minLength(settingsGroup, _metaDataMap[minLengthKey]), _measurementArea(nullptr) { init(); } QString CircularGenerator::editorQml() const { return QStringLiteral("CircularGeneratorEditor.qml"); } QString CircularGenerator::mapVisualQml() const { return QStringLiteral("CircularGeneratorMapVisual.qml"); } QString CircularGenerator::abbreviation() const { return tr("C. Gen."); } QString CircularGenerator::type() const { return typeString; } bool CircularGenerator::get(Work &work) { if (this->_d) { if (this->_d->isCorrect()) { // Prepare data. auto origin = this->_d->origin(); origin.setAltitude(0); if (!origin.isValid()) { qCDebug(CircularGeneratorLog) << "get(): origin invalid." << origin; return false; } auto ref = this->_reference; ref.setAltitude(0); if (!ref.isValid()) { qCDebug(CircularGeneratorLog) << "get(): reference invalid." << ref; return false; } geometry::FPoint reference; geometry::toENU(origin, ref, reference); auto measurementArea = getGeoArea(*this->_d->areaList()); if (measurementArea == nullptr) { qCDebug(CircularGeneratorLog) << "get(): measurement area == nullptr"; return false; } auto geoPolygon = measurementArea->coordinateList(); for (auto &v : geoPolygon) { if (v.isValid()) { v.setAltitude(0); } else { qCDebug(CircularGeneratorLog) << "get(): measurement area invalid."; for (const auto &w : geoPolygon) { qCDebug(CircularGeneratorLog) << w; } return false; } } auto pPolygon = std::make_shared(); geometry::areaToEnu(origin, geoPolygon, *pPolygon); // Collect tiles with progress == 100 %. const auto *tiles = measurementArea->tiles(); auto pTiles = std::make_shared>(); for (int i = 0; i < tiles->count(); ++i) { const auto tile = qobject_cast(tiles->operator[](i)); if (tile != nullptr) { if (qFuzzyCompare(tile->progress(), 100)) { geometry::FPolygon tileENU; geometry::areaToEnu(origin, tile->coordinateList(), tileENU); pTiles->push_back(std::move(tileENU)); } } else { qCDebug(CircularGeneratorLog) << "get(): tile == nullptr."; return false; } } auto serviceArea = getGeoArea(*this->_d->areaList()); if (measurementArea == nullptr) { qCDebug(CircularGeneratorLog) << "get(): measurement area == nullptr"; return false; } auto geoDepot = serviceArea->depot(); if (!geoDepot.isValid()) { qCDebug(CircularGeneratorLog) << "get(): depot invalid." << geoDepot; return false; } geometry::FPoint depot; geometry::toENU(origin, geoDepot, depot); // Fetch transect parameter. auto distance = geometry::Length(this->_distance.rawValue().toDouble() * bu::si::meter); auto minLength = geometry::Length(this->_minLength.rawValue().toDouble() * bu::si::meter); auto alpha = geometry::Angle(this->_deltaAlpha.rawValue().toDouble() * bu::degree::degree); work = [reference, depot, pPolygon, pTiles, distance, alpha, minLength](geometry::LineStringArray &transects) -> bool { bool value = circularTransects(reference, *pPolygon, *pTiles, distance, alpha, minLength, transects); transects.insert(transects.begin(), geometry::FLineString{depot}); return value; }; return true; } else { qCDebug(CircularGeneratorLog) << "get(): data invalid."; return false; } } else { qCDebug(CircularGeneratorLog) << "get(): data member not set."; return false; } } QGeoCoordinate CircularGenerator::reference() const { return _reference; } void CircularGenerator::setReference(const QGeoCoordinate &reference) { if (_reference != reference) { _reference = reference; emit referenceChanged(); } } bool CircularGenerator::save(QJsonObject &obj) const { QJsonObject temp; GeneratorBase::save(temp); bool ok = false; auto variant = _distance.rawValue(); auto val = variant.toDouble(&ok); if (!ok) { qCDebug(CircularGeneratorLog) << "save(): not able to save distance. Not a double: " << variant.typeName(); return false; } else { temp[distanceKey] = val; } variant = _deltaAlpha.rawValue(); val = variant.toDouble(&ok); if (!ok) { qCDebug(CircularGeneratorLog) << "save(): not able to save deltaAlpha. Not a double: " << variant.typeName(); return false; } else { temp[deltaAlphaKey] = val; } variant = _minLength.rawValue(); val = variant.toDouble(&ok); if (!ok) { qCDebug(CircularGeneratorLog) << "save(): not able to save minLength. Not a double: " << variant.typeName(); return false; } else { temp[minLengthKey] = val; } QJsonValue jsonReference; JsonHelper::saveGeoCoordinate(_reference, false, jsonReference); temp[referenceKey] = jsonReference; obj = std::move(temp); return true; } bool CircularGenerator::load(const QJsonObject &obj, QString &errorString) { bool returnValue = true; { QString e; if (!GeneratorBase::load(obj, e)) { returnValue = false; errorString.append(e); } } // load distance { QString e; QList keyInfo = { {distanceKey, QJsonValue::Double, true}, }; if (JsonHelper::validateKeys(obj, keyInfo, e)) { _distance.setRawValue(obj[distanceKey]); } else { returnValue = false; errorString.append(e); errorString.append("\n"); } } // load deltaAlpha { QString e; QList keyInfo = { {deltaAlphaKey, QJsonValue::Double, true}, }; if (JsonHelper::validateKeys(obj, keyInfo, e)) { _deltaAlpha.setRawValue(obj[deltaAlphaKey]); } else { returnValue = false; errorString.append(e); errorString.append("\n"); } } // load distance { QString e; QList keyInfo = { {minLengthKey, QJsonValue::Double, true}, }; if (JsonHelper::validateKeys(obj, keyInfo, e)) { _minLength.setRawValue(obj[minLengthKey]); } else { returnValue = false; errorString.append(e); errorString.append("\n"); } } // load reference { QString e; QList keyInfo = { {referenceKey, QJsonValue::Array, true}, }; if (JsonHelper::validateKeys(obj, keyInfo, e)) { QGeoCoordinate ref; if (JsonHelper::loadGeoCoordinate(obj[referenceKey], false, ref, e)) { setReference(ref); } else { returnValue = false; errorString.append(e); errorString.append("\n"); } } else { returnValue = false; errorString.append(e); errorString.append("\n"); } } return returnValue; } void CircularGenerator::resetReferenceIfInvalid() { if (!this->_reference.isValid()) { resetReference(); } } void CircularGenerator::resetReference() { if (this->_d != nullptr) { auto measurementArea = getGeoArea(*this->_d->areaList()); if (measurementArea != nullptr) { if (measurementArea->center().isValid()) { setReference(measurementArea->center()); } else { qCWarning(CircularGeneratorLog) << "measurement area center" << measurementArea->center(); } } else { qCDebug(CircularGeneratorLog) << "resetReference(): measurement area == nullptr"; } } } Fact *CircularGenerator::distance() { return &_distance; } Fact *CircularGenerator::deltaAlpha() { return &_deltaAlpha; } Fact *CircularGenerator::minLength() { return &_minLength; } void CircularGenerator::onAreaListChanged() { if (this->_d != nullptr) { auto *measurementArea = getGeoArea(*this->_d->areaList()); setMeasurementArea(measurementArea); } } void CircularGenerator::onDataChanged() { if (this->_d != nullptr) { connect(this->_d, &AreaData::areaListChanged, this, &CircularGenerator::onAreaListChanged); onAreaListChanged(); } } void CircularGenerator::init() { connect(this->distance(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); connect(this->deltaAlpha(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); connect(this->minLength(), &Fact::rawValueChanged, this, &GeneratorBase::generatorChanged); connect(this, &CircularGenerator::referenceChanged, this, &GeneratorBase::generatorChanged); connect(this, &CircularGenerator::dataChanged, this, &CircularGenerator::onDataChanged); onDataChanged(); setName(tr("Circular Generator")); } void CircularGenerator::setMeasurementArea(MeasurementArea *area) { if (_measurementArea != area) { if (_measurementArea != nullptr) { disconnect(_measurementArea, &MeasurementArea::progressChanged, this, &GeneratorBase::generatorChanged); disconnect(_measurementArea, &MeasurementArea::tilesChanged, this, &GeneratorBase::generatorChanged); disconnect(_measurementArea, &MeasurementArea::centerChanged, this, &CircularGenerator::resetReferenceIfInvalid); disconnect(_measurementArea, &MeasurementArea::pathChanged, this, &GeneratorBase::generatorChanged); } _measurementArea = area; if (_measurementArea != nullptr) { connect(_measurementArea, &MeasurementArea::progressChanged, this, &GeneratorBase::generatorChanged); connect(_measurementArea, &MeasurementArea::tilesChanged, this, &GeneratorBase::generatorChanged); connect(_measurementArea, &MeasurementArea::centerChanged, this, &CircularGenerator::resetReferenceIfInvalid); connect(_measurementArea, &MeasurementArea::pathChanged, this, &GeneratorBase::generatorChanged); resetReferenceIfInvalid(); } emit generatorChanged(); } } bool circularTransects(const geometry::FPoint &reference, const geometry::FPolygon &polygon, const std::vector &tiles, geometry::Length deltaR, geometry::Angle deltaAlpha, geometry::Length minLength, geometry::LineStringArray &transects) { auto s1 = std::chrono::high_resolution_clock::now(); // Check preconitions if (polygon.outer().size() >= 3) { using namespace boost::units; // Convert geo polygon to ENU polygon. std::string error; // Check validity. if (!bg::is_valid(polygon, error)) { qCDebug(CircularGeneratorLog) << "circularTransects(): " "invalid polygon."; qCDebug(CircularGeneratorLog) << error.c_str(); std::stringstream ss; ss << bg::wkt(polygon); qCDebug(CircularGeneratorLog) << ss.str().c_str(); } else { // Calculate polygon distances and angles. std::vector distances; distances.reserve(polygon.outer().size()); std::vector angles; angles.reserve(polygon.outer().size()); // qCDebug(CircularGeneratorLog) << "circularTransects():"; for (const auto &p : polygon.outer()) { geometry::Length distance = bg::distance(reference, p) * si::meter; distances.push_back(distance); geometry::Angle alpha = (std::atan2(p.get<1>(), p.get<0>())) * si::radian; alpha = alpha < 0 * si::radian ? alpha + 2 * M_PI * si::radian : alpha; angles.push_back(alpha); // qCDebug(CircularGeneratorLog) << "distances, angles, // coordinates:"; qCDebug(CircularGeneratorLog) << // to_string(distance).c_str(); qCDebug(CircularGeneratorLog) // << to_string(snake::Degree(alpha)).c_str(); // qCDebug(CircularGeneratorLog) << "x = " << p.get<0>() << "y // = " // << p.get<1>(); } auto rMin = deltaR; // minimal circle radius geometry::Angle alpha1(0 * degree::degree); geometry::Angle alpha2(360 * degree::degree); // Determine r_min by successive approximation if (!bg::within(reference, polygon.outer())) { rMin = bg::distance(reference, polygon) * si::meter; } auto rMax = (*std::max_element(distances.begin(), distances.end())); // maximal circle radius // Scale parameters and coordinates. const auto rMinScaled = ClipperLib::cInt(std::round(rMin.value() * CLIPPER_SCALE)); const auto deltaRScaled = ClipperLib::cInt(std::round(deltaR.value() * CLIPPER_SCALE)); auto referenceScaled = ClipperLib::IntPoint{ ClipperLib::cInt(std::round(reference.get<0>() * CLIPPER_SCALE)), ClipperLib::cInt(std::round(reference.get<1>() * CLIPPER_SCALE))}; // Generate circle sectors. auto rScaled = rMinScaled; const auto nTran = long(std::ceil(((rMax - rMin) / deltaR).value())); std::vector sectors(nTran, ClipperLib::Path()); const auto nSectors = long(std::round(((alpha2 - alpha1) / deltaAlpha).value())); // qCDebug(CircularGeneratorLog) << "circularTransects(): sector // parameres:"; qCDebug(CircularGeneratorLog) << "alpha1: " << // to_string(snake::Degree(alpha1)).c_str(); // qCDebug(CircularGeneratorLog) << "alpha2: // " // << to_string(snake::Degree(alpha2)).c_str(); // qCDebug(CircularGeneratorLog) << "n: " // << to_string((alpha2 - alpha1) / deltaAlpha).c_str(); // qCDebug(CircularGeneratorLog) // << "nSectors: " << nSectors; qCDebug(CircularGeneratorLog) << // "rMin: " << to_string(rMin).c_str(); // qCDebug(CircularGeneratorLog) // << "rMax: " << to_string(rMax).c_str(); // qCDebug(CircularGeneratorLog) << "nTran: " << nTran; using ClipperCircle = GenericCircle; for (auto §or : sectors) { ClipperCircle circle(rScaled, referenceScaled); approximate(circle, nSectors, sector); rScaled += deltaRScaled; } // Clip sectors to polygonENU. ClipperLib::Path polygonClipper; geometry::FPolygon shrinked; geometry::offsetPolygon(polygon, shrinked, -0.3); auto &outer = shrinked.outer(); polygonClipper.reserve(outer.size()); for (auto it = outer.begin(); it < outer.end() - 1; ++it) { auto x = ClipperLib::cInt(std::round(it->get<0>() * CLIPPER_SCALE)); auto y = ClipperLib::cInt(std::round(it->get<1>() * CLIPPER_SCALE)); polygonClipper.push_back(ClipperLib::IntPoint{x, y}); } ClipperLib::Clipper clipper; clipper.AddPath(polygonClipper, ClipperLib::ptClip, true); clipper.AddPaths(sectors, ClipperLib::ptSubject, false); ClipperLib::PolyTree transectsClipper; clipper.Execute(ClipperLib::ctIntersection, transectsClipper, ClipperLib::pftNonZero, ClipperLib::pftNonZero); // Subtract holes. if (tiles.size() > 0) { std::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 : transectsClipper.Childs) { clipper.AddPath(child->Contour, ClipperLib::ptSubject, false); } clipper.AddPaths(processedTiles, ClipperLib::ptClip, true); transectsClipper.Clear(); clipper.Execute(ClipperLib::ctDifference, transectsClipper, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } // Extract transects from PolyTree and convert them to // BoostLineString for (const auto &child : transectsClipper.Childs) { geometry::FLineString transect; transect.reserve(child->Contour.size()); for (const auto &vertex : child->Contour) { auto x = static_cast(vertex.X) / CLIPPER_SCALE; auto y = static_cast(vertex.Y) / CLIPPER_SCALE; transect.push_back(geometry::FPoint(x, y)); } transects.push_back(transect); } // Join sectors which where slit due to clipping. const double th = 0.01; for (auto ito = transects.begin(); ito < transects.end(); ++ito) { for (auto iti = ito + 1; iti < transects.end(); ++iti) { auto dist1 = bg::distance(ito->front(), iti->front()); if (dist1 < th) { geometry::FLineString temp; for (auto it = iti->end() - 1; it >= iti->begin(); --it) { temp.push_back(*it); } temp.insert(temp.end(), ito->begin(), ito->end()); *ito = temp; transects.erase(iti); break; } auto dist2 = bg::distance(ito->front(), iti->back()); if (dist2 < th) { geometry::FLineString temp; temp.insert(temp.end(), iti->begin(), iti->end()); temp.insert(temp.end(), ito->begin(), ito->end()); *ito = temp; transects.erase(iti); break; } auto dist3 = bg::distance(ito->back(), iti->front()); if (dist3 < th) { geometry::FLineString temp; temp.insert(temp.end(), ito->begin(), ito->end()); temp.insert(temp.end(), iti->begin(), iti->end()); *ito = temp; transects.erase(iti); break; } auto dist4 = bg::distance(ito->back(), iti->back()); if (dist4 < th) { geometry::FLineString temp; temp.insert(temp.end(), ito->begin(), ito->end()); for (auto it = iti->end() - 1; it >= iti->begin(); --it) { temp.push_back(*it); } *ito = temp; transects.erase(iti); break; } } } // Remove short transects for (auto it = transects.begin(); it < transects.end();) { if (bg::length(*it) < minLength.value()) { it = transects.erase(it); } else { ++it; } } qCDebug(CircularGeneratorLog) << "circularTransects(): transect gen. time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - s1) .count() << " ms"; return true; } } return false; } } // namespace routing