CircularSurveyComplexItem.cc 10.3 KB
Newer Older
1 2 3 4
#include "CircularSurveyComplexItem.h"


const char* CircularSurveyComplexItem::settingsGroup =              "Survey";
5 6
const char* CircularSurveyComplexItem::deltaRName =                 "DeltaR";
const char* CircularSurveyComplexItem::deltaAlphaName =             "DeltaAlpha";
7 8

CircularSurveyComplexItem::CircularSurveyComplexItem(Vehicle *vehicle, bool flyView, const QString &kmlOrShpFile, QObject *parent)
9 10 11 12 13
    :   TransectStyleComplexItem    (vehicle, flyView, settingsGroup, parent)
    ,   _referencePoint             (QGeoCoordinate(47.770859, 16.531076,0))
    ,   _metaDataMap                (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CircularSurvey.SettingsGroup.json"), this))
    ,   _deltaR                     (settingsGroup, _metaDataMap[deltaRName])
    ,   _deltaAlpha                 (settingsGroup, _metaDataMap[deltaAlphaName])
14
{
15 16 17 18 19
    _editorQml = "qrc:/qml/CircularSurveyItemEditor.qml";

    //connect(&_deltaR,       &Fact::valueChanged, this, &CircularSurveyComplexItem::_setDirty);
    //connect(&_deltaAlpha,   &Fact::valueChanged, this, &CircularSurveyComplexItem::_setDirty);

20

21 22
    connect(&_updateTimer, &QTimer::timeout, this, &CircularSurveyComplexItem::_updateItem);
    _updateTimer.start(100);
23 24 25
}

void CircularSurveyComplexItem::setRefPoint(const QGeoCoordinate &refPt)
26
{
27 28
    if (refPt != _referencePoint){
        _referencePoint = refPt;
29

30 31 32 33 34 35 36
        emit refPointChanged();
    }
}

QGeoCoordinate CircularSurveyComplexItem::refPoint() const
{
    return _referencePoint;
37 38
}

39 40 41 42 43 44 45 46 47 48
Fact *CircularSurveyComplexItem::deltaR()
{
    return &_deltaR;
}

Fact *CircularSurveyComplexItem::deltaAlpha()
{
    return &_deltaAlpha;
}

49 50
bool CircularSurveyComplexItem::load(const QJsonObject &complexObject, int sequenceNumber, QString &errorString)
{
51
    return false;
52 53 54 55
}

void CircularSurveyComplexItem::save(QJsonArray &planItems)
{
56

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
}

void CircularSurveyComplexItem::appendMissionItems(QList<MissionItem *> &items, QObject *missionItemParent)
{

}

void CircularSurveyComplexItem::applyNewAltitude(double newAltitude)
{

}

double CircularSurveyComplexItem::timeBetweenShots()
{
    return 1;
}

bool CircularSurveyComplexItem::readyForSave() const
{
    return false;
}

double CircularSurveyComplexItem::additionalTimeDelay() const
{
    return 0;
}

void CircularSurveyComplexItem::_rebuildTransectsPhase1()
{
86 87 88 89
    using namespace GeoUtilities;
    using namespace PolygonCalculus;
    using namespace PlanimetryCalculus;

90 91 92
    if ( _surveyAreaPolygon.count() < 3)
        return;

93
    _transects.clear();
94 95 96
    QPolygonF surveyPolygon = toQPolygonF(toCartesian2D(_surveyAreaPolygon.coordinateList(), _referencePoint));

    QVector<double> distances;
97 98
    for (const QPointF &p : surveyPolygon) distances.append(norm(p));

99 100 101 102 103 104 105 106 107 108 109
    // check if input is valid
    if (   _deltaAlpha.rawValue() > _deltaAlpha.rawMax()
           && _deltaAlpha.rawValue() < _deltaAlpha.rawMin())
        return;
    if (   _deltaR.rawValue() > _deltaR.rawMax()
           && _deltaR.rawValue() < _deltaR.rawMin())
        return;


    double dalpha = _deltaAlpha.rawValue().toDouble()/180*M_PI; // radiants
    double dr = _deltaR.rawValue().toDouble(); // meter
110 111 112 113 114
    double r_min = dr; // meter
    double r_max = (*std::max_element(distances.begin(), distances.end())); // meter

    QPointF origin(0, 0);
    IntersectType type;
115
    bool originInside = true;
116 117 118 119 120
    if (!contains(surveyPolygon, origin, type)) {
        QVector<double> angles;
        for (const QPointF &p : surveyPolygon) angles.append(truncateAngle(angle(p)));

        // determine r_min by successive approximation
121 122
        double r = r_min;
        while ( r < r_max) {
123 124
            Circle circle(r, origin);

125
            if (intersects(circle, surveyPolygon)) {
126 127 128 129
                r_min = r;
                break;
            }

130
            r += dr;
131
        }
132
        originInside = false;
133 134
    }

135 136 137
//    qWarning("r_min, r_max:");
//    qWarning() << r_min;
//    qWarning() << r_max;
138 139 140 141

    QList<QPolygonF> convexPolygons;
    decomposeToConvex(surveyPolygon, convexPolygons);

142
    QList<QList<QPointF>> fullPath;
143
    for (int i = 0; i < convexPolygons.size(); i++) {
144
        const QPolygonF &polygon = convexPolygons[i];
145
        double r = r_min;
146

147 148
        QList<QList<QPointF>> currPolyPath;
        while (r < r_max) {
149
            Circle circle(r, origin);
150 151
            QList<QPointFList> intersectPoints;
            QList<IntersectType> typeList;
152 153
            QList<QPair<int, int>> neighbourList;
            if (intersects(circle, polygon, intersectPoints, neighbourList, typeList)) {
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179

                // intersection Points between circle and polygon, entering polygon
                // when walking in counterclockwise direction along circle
                QPointFList entryPoints;
                // intersection Points between circle and polygon, leaving polygon
                // when walking in counterclockwise direction along circle
                QPointFList exitPoints;
                // determine entryPoints and exit Points
                for (int j = 0; j < intersectPoints.size(); j++) {
                    QList<QPointF> intersects = intersectPoints[j];

                    QPointF p1 = polygon[neighbourList[j].first];
                    QPointF p2 = polygon[neighbourList[j].second];
                    QLineF intersetLine(p1, p2);
                    double lineAngle = truncateAngle(angle(intersetLine));

                    for (QPointF ipt : intersects) {
                        double circleTangentAngle = truncateAngle(angle(ipt)+M_PI_2);
                        // compare line angle and circle tangent at intersection point
                        // to determine between exit and entry point
                        if (   !qFuzzyCompare(lineAngle, circleTangentAngle)
                            && !qFuzzyCompare(lineAngle, truncateAngle(circleTangentAngle + M_PI))) {
                            if (truncateAngle(lineAngle - circleTangentAngle)  < M_PI) {
                                entryPoints.append(ipt);
                            } else {
                                exitPoints.append(ipt);
180 181
                            }
                        }
182 183
                    }
                }
184

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
                // sort
                std::sort(entryPoints.begin(), entryPoints.end(), [](QPointF p1, QPointF p2) {
                   return angle(p1) < angle(p2);
                });
                std::sort(exitPoints.begin(), exitPoints.end(), [](QPointF p1, QPointF p2) {
                   return angle(p1) < angle(p2);
                });

                // match entry and exit points
                int offset = 0;
                double minAngle = std::numeric_limits<double>::infinity();
                for (int k = 0; k < exitPoints.size(); k++) {
                    QPointF pt = exitPoints[k];
                    double alpha = truncateAngle(angle(pt) - angle(entryPoints[0]));
                    if (minAngle > alpha) {
                        minAngle = alpha;
                        offset = k;
                    }
                }
204

205 206 207
                for (int k = 0; k < entryPoints.size(); k++) {
                    double alpha1 = angle(entryPoints[k]);
                    double alpha2 = angle(exitPoints[(k+offset) % entryPoints.size()]);
208

209
                    QList<QPointF> sectorPath = circle.approximateSektor(double(dalpha), alpha1, alpha2);
210 211
                    // use shortestPath() here if necessary, could be a problem if dr >>
                    if (sectorPath.size() > 0)
212 213
                        currPolyPath.append(sectorPath);
                }
214 215
            } else if (originInside) {
                // circle fully inside polygon
216
                QList<QPointF> sectorPath = circle.approximateSektor(double(dalpha), 0, 2*M_PI);
217 218
                // use shortestPath() here if necessary, could be a problem if dr >>
                currPolyPath.append(sectorPath);
219 220
            }
            r += dr;
221 222 223
         }
        if (currPolyPath.size() > 0) {
            fullPath.append(currPolyPath);
224
        }
225
    }
226

227
    // optimize path to lawn pattern
228 229
    if (fullPath.size() == 0)
        return;
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    QList<QPointF> currentSection = fullPath.takeFirst();
    QList<QList<QPointF>> optiPath; // optimized path
    while( !fullPath.empty() ) {
        optiPath.append(currentSection);
        QPointF endVertex = currentSection.last();
        double minDist = std::numeric_limits<double>::infinity();
        int index = 0;
        bool reversePath = false;

        // iterate over all paths in fullPath and assign the one with the shortest distance to endVertex to currentSection
        for (int i = 0; i < fullPath.size(); i++) {
            auto iteratorPath = fullPath[i];
            double dist = PlanimetryCalculus::distance(endVertex, iteratorPath.first());
            if ( dist < minDist ) {
                minDist = dist;
                index = i;
            }
            dist = PlanimetryCalculus::distance(endVertex, iteratorPath.last());
            if (dist < minDist) {
                minDist = dist;
                index = i;
                reversePath = true;
252
            }
253
        }
254 255 256 257
        currentSection = fullPath.takeAt(index);
        if (reversePath) {
            PolygonCalculus::reversePath(currentSection);
        }
258 259
    }

260 261 262

    // convert to CoordInfo_t
    for ( const QList<QPointF> &transect : optiPath) {
263 264 265 266 267 268 269 270
        QList<QGeoCoordinate> geoPath = toGeo(transect, _referencePoint);
        QList<CoordInfo_t> transectList;
        for ( const QGeoCoordinate &coordinate : geoPath) {
            CoordInfo_t coordinfo = {coordinate, CoordTypeInterior};
            transectList.append(coordinfo);
        }
        _transects.append(transectList);
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
271

272 273 274 275 276 277 278 279 280 281 282 283
}

void CircularSurveyComplexItem::_recalcComplexDistance()
{

}

void CircularSurveyComplexItem::_recalcCameraShots()
{

}

284 285 286 287 288 289 290 291 292
void CircularSurveyComplexItem::_updateItem()
{
    if (_dirty) {
        _rebuildTransects();
        setDirty(false);
    }

}

293 294 295 296 297 298 299 300 301 302 303
/*!
    \class CircularSurveyComplexItem
    \inmodule Wima

    \brief The \c CircularSurveyComplexItem class provides a survey mission item with circular transects around a point of interest.

    CircularSurveyComplexItem class provides a survey mission item with circular transects around a point of interest. Within the
    \c Wima module it's used to scan a defined area with constant angle (circular transects) to the base station (point of interest).

    \sa WimaArea
*/