WimaArea.cc 18.6 KB
Newer Older
1 2
#include "WimaArea.h"

3 4 5 6 7
const double WimaArea::numericalAccuracy    = 1e-3; // meters
const char* WimaArea::maxAltitudeName       = "maxAltitude";
const char* WimaArea::wimaAreaName          = "WimaArea";
const char* WimaArea::areaTypeName          = "AreaType";

8

9 10


11 12
WimaArea::WimaArea(QObject *parent)
    :  QGCMapPolygon (parent)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
13
{
14
    init();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
15 16
    _maxAltitude = 30;
}
17

Valentin Platzgummer's avatar
Valentin Platzgummer committed
18
WimaArea::WimaArea(const WimaArea &other, QObject *parent)
19
    : QGCMapPolygon (other, parent)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
20
{
21
    init();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
22 23 24 25 26 27
    this->setPath(other.path());
    this->setCenter(other.center());
    this->setCenterDrag(other.centerDrag());
    this->setInteractive(other.interactive());
    _maxAltitude = other.maxAltitude();
}
28 29 30

void WimaArea::setMaxAltitude(double alt)
{
31
    if ( alt > 0 && qFuzzyCompare(alt, _maxAltitude) ) {
32 33 34 35 36
        _maxAltitude = alt;
        emit maxAltitudeChanged();
    }
}

37
int WimaArea::getClosestVertexIndex(const QGeoCoordinate &coordinate) const
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
{
    if (this->count() == 0) {
        qWarning("Polygon count == 0!");
        return -1;
    }else if (this->count() == 1) {
        return 0;
    }else {
        int index = 0;
        double min_dist = coordinate.distanceTo(this->vertexCoordinate(index));
        for(int i = 1; i < this->count(); i++){
            double dist = coordinate.distanceTo(this->vertexCoordinate(i));
            if (dist < min_dist){
                min_dist = dist;
                index = i;
            }
        }

        return index;
    }
}

59
QGeoCoordinate WimaArea::getClosestVertex(const QGeoCoordinate& coordinate) const
60 61 62 63
{
    return this->vertexCoordinate(getClosestVertexIndex(coordinate));
}

64
QGCMapPolygon WimaArea::toQGCPolygon(const WimaArea &poly)
65
{
Valentin Platzgummer's avatar
Valentin Platzgummer committed
66 67 68 69 70
    QGCMapPolygon qgcPoly;
    qgcPoly.setPath(poly.path());
    qgcPoly.setCenter(poly.center());
    qgcPoly.setCenterDrag(poly.centerDrag());
    qgcPoly.setInteractive(poly.interactive());
71

Valentin Platzgummer's avatar
Valentin Platzgummer committed
72
    return QGCMapPolygon(qgcPoly);
73 74
}

75 76 77 78 79
QGCMapPolygon WimaArea::toQGCPolygon() const
{
    return toQGCPolygon(*this);
}

80
void WimaArea::join(QList<WimaArea *>* polyList,  WimaArea* joinedPoly)
81
{
82
    return;
83 84
}

85
bool WimaArea::join(WimaArea &poly1, WimaArea &poly2, WimaArea &joinedPoly)
86
{
87

88 89
        if (poly1.count() >= 3 && poly2.count() >= 3) {

90 91
            joinedPoly.clear();

Valentin Platzgummer's avatar
Valentin Platzgummer committed
92 93
            poly1.verifyClockwiseWinding();
            poly2.verifyClockwiseWinding();
94

Valentin Platzgummer's avatar
Valentin Platzgummer committed
95 96
            WimaArea* walkerPoly    = &poly1; // "walk" on this polygon towards higher indices
            WimaArea* crossPoly     = &poly2; // check for crossings with this polygon while "walking"
97 98
                                             // and swicht to this polygon on a intersection,
                                             // continue to walk towards higher indices
99 100 101 102 103 104 105 106 107 108 109 110 111 112

            // begin with the first index which is not inside crosspoly, if all Vertices are inside crosspoly return crosspoly
            int startIndex = 0;
            bool crossContainsWalker = true;
            for (int i = 0; i < walkerPoly->count(); i++) {
                if ( !crossPoly->containsCoordinate(walkerPoly->vertexCoordinate(i)) ) {
                    crossContainsWalker = false;
                    startIndex = i;
                    break;
                }
            }


            if ( crossContainsWalker == true) {
113
                joinedPoly.appendVertices(crossPoly->coordinateList());
114
                return true;
115 116 117 118 119
            }


            QGeoCoordinate currentVertex    = walkerPoly->vertexCoordinate(startIndex);
            QGeoCoordinate startVertex      = currentVertex;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
120
            // possible nextVertex (if no intersection between currentVertex and protoVertex with crossPoly)
121 122 123 124
            QGeoCoordinate protoNextVertex  = walkerPoly->vertexCoordinate(walkerPoly->nextVertexIndex(startIndex));

            int nextVertexIndex = walkerPoly->nextVertexIndex(startIndex);
            while (1) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
125
                //qDebug("nextVertexIndex: %i", nextVertexIndex);
126
                joinedPoly.appendVertex(currentVertex);
127 128 129

                QGCMapPolyline walkerPolySegment;
                walkerPolySegment.appendVertex(currentVertex);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
130
                walkerPolySegment.appendVertex(protoNextVertex);
131

132 133
                QList<QPair<int, int>> neighbourList;
                QList<QGeoCoordinate> intersectionList;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
134
                //qDebug("IntersectionList.size() on init: %i", intersectionList.size());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
135
                intersects(walkerPolySegment, *crossPoly, intersectionList, neighbourList);
136

Valentin Platzgummer's avatar
Valentin Platzgummer committed
137
                //qDebug("IntersectionList.size(): %i", intersectionList.size());
138

139 140
                if (intersectionList.size() >= 1) {
                    int minDistIndex = 0;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
141

142 143 144 145
                    if (intersectionList.size() > 1) {
                        double minDist = currentVertex.distanceTo(intersectionList.value(minDistIndex));
                        for (int i = 1; i < intersectionList.size(); i++) {
                            double currentDist = currentVertex.distanceTo(intersectionList.value(i));
Valentin Platzgummer's avatar
Valentin Platzgummer committed
146

147 148 149 150 151 152
                            if ( minDist > currentDist ) {
                                minDist         = currentDist;
                                minDistIndex    = i;
                            }
                        }
                    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
153

Valentin Platzgummer's avatar
Valentin Platzgummer committed
154
                    //qDebug("MinDistIndex: %i", minDistIndex);
155 156 157 158 159 160 161 162 163 164 165 166
                    QGeoCoordinate protoCurrentVertex = intersectionList.value(minDistIndex);
                    // take numerical erros into account
                    if (protoCurrentVertex.distanceTo(currentVertex) > WimaArea::numericalAccuracy) {
                        currentVertex                   = protoCurrentVertex;
                        QPair<int, int> neighbours      = neighbourList.value(minDistIndex);
                        protoNextVertex                 = crossPoly->vertexCoordinate(neighbours.second);
                        nextVertexIndex                 = neighbours.second;

                        // swap walker and cross poly
                        WimaArea* temp  = walkerPoly;
                        walkerPoly      = crossPoly;
                        crossPoly       = temp;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
167
                    } else {
168 169 170
                        currentVertex   = walkerPoly->vertexCoordinate(nextVertexIndex);
                        protoNextVertex = walkerPoly->vertexCoordinate(walkerPoly->nextVertexIndex(nextVertexIndex));
                        nextVertexIndex = walkerPoly->nextVertexIndex(nextVertexIndex);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
171
                    }
172

173
                } else {
174 175 176 177 178 179
                    currentVertex   = walkerPoly->vertexCoordinate(nextVertexIndex);
                    protoNextVertex = walkerPoly->vertexCoordinate(walkerPoly->nextVertexIndex(nextVertexIndex));
                    nextVertexIndex = walkerPoly->nextVertexIndex(nextVertexIndex);
                }

                if (currentVertex == startVertex) {
180 181 182 183 184
                    if (poly1.count() == joinedPoly.count()) { // is the case if poly1 and poly2 don't intersect
                        return false;
                    } else {
                        return true;
                    }
185
                }
186
            }
187 188

        } else {
189
            return false;
190
        }
191
}
192

193
bool WimaArea::join(WimaArea &poly)
194
{
Valentin Platzgummer's avatar
Valentin Platzgummer committed
195
    WimaArea joinedArea;
196 197 198 199 200 201
    if ( join(*this, poly, joinedArea) ) {
        this->setPath(joinedArea.path());
        return true;
    } else {
        return false;
    }
202
}
203

204
bool WimaArea::isDisjunct(QList<WimaArea *>* polyList)
205 206 207 208
{
    // needs improvement
    if (polyList != nullptr){
        for (int i = 0;i < polyList->size()-1; i++) {
209
            WimaArea* currPoly = polyList->value(i);
210
            for (int j = i+1; i < polyList->size(); j++) {
211
                if (isDisjunct(currPoly, polyList->value(j))) {
212 213 214 215 216 217 218 219 220 221
                    return false;
                }
            }
        }
        return true;
    } else {
        qWarning("WimaArea::isDisjunct(polyList): polyList == nullptr!");
        return false;
    }
}
222

223
bool WimaArea::isDisjunct(WimaArea *poly1, WimaArea *poly2)
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
{
    if (poly1 != nullptr && poly2 != nullptr) {
        QGCMapPolygon* poly1Copy = new QGCMapPolygon(this);
        poly1Copy->setPath(poly1->path());
        poly1Copy->offset(numericalAccuracy);// take numerical errors in account
        for(int i = 0; i < poly2->count(); i++){
            if (poly1Copy->containsCoordinate(poly2->vertexCoordinate(i))){
                return false;
            }
        }
        return true;
    } else {
        qWarning("WimaArea::isDisjunct(poly1, poly2):  poly1 == nullptr || poly2 == nullptr!");
        return false;
    }
239 240
}

241
int WimaArea::nextVertexIndex(int index) const
242 243 244 245 246 247 248 249 250 251 252
{
    if (index >= 0 && index < count()-1) {
        return index + 1;
    } else if (index == count()-1) {
        return 0;
    } else {
        qWarning("WimaArea::nextVertexIndex(): Index out of bounds! index:count = %i:%i", index, count());
        return -1;
    }
}

253
int WimaArea::previousVertexIndex(int index) const
254 255 256 257 258 259 260 261 262 263 264
{
    if (index > 0 && index < count()) {
        return index - 1;
    } else if (index == 0) {
        return count()-1;
    } else {
        qWarning("WimaArea::previousVertexIndex(): Index out of bounds! index:count = %i:%i", index, count());
        return -1;
    }
}

265
bool WimaArea::intersects(const QGCMapPolyline &line1, const QGCMapPolyline &line2, QGeoCoordinate &intersectionPt)
266
{
267 268

        if (line1.count() == 2 && line2.count() == 2 ) {
269 270 271
            QPointF pt11(0, 0);

            double x, y, z;
272 273
            QGeoCoordinate origin = line1.vertexCoordinate(0);
            convertGeoToNed(line1.vertexCoordinate(1), origin, &x, &y, &z);
274 275 276 277 278
            QPointF pt12(x, y);

            QLineF kartLine1(pt11, pt12);


279
            convertGeoToNed(line2.vertexCoordinate(0), origin, &x, &y, &z);
280 281
            QPointF pt21(x, y);

282
            convertGeoToNed(line2.vertexCoordinate(1), origin, &x, &y, &z);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
283
            QPointF pt22(x, y);;
284 285 286 287

            QLineF kartLine2(pt21, pt22);

            QPointF intersectionPoint;
288
            if (kartLine1.intersect(kartLine2, &intersectionPoint) == QLineF::BoundedIntersection) {
289
                convertNedToGeo(intersectionPoint.x(), intersectionPoint.y(), origin.altitude(), origin, &intersectionPt);
290
                return true;
291
            } else {
292
                return false;
293 294 295 296
            }


        } else {
297 298
            qWarning("WimaArea::intersect(line1, line2):  line1->count() != 2 || line2->count() != 2!");
            return false;
299 300 301
        }
}

302 303 304 305
bool WimaArea::intersects(const QGCMapPolyline      &line,
                          const WimaArea            &poly,
                          QList<QGeoCoordinate>     &intersectionList,
                          QList<QPair<int, int> >   &neighbourList)
306
{
307 308 309 310 311
       // ================ Brief Explanation ================
       // This function checks whether line intersects with the border line of poly
       // Returns true if at least one intersection occurs, false else.
       // Stores the intersection points inside intersectionList.
       // Stores the indices of the cloest two polygon vetices for each of coorespoinding intersection points in neighbourList
312
        intersectionList.clear();
313
        neighbourList.clear();
314 315


316
        if (line.count() == 2 && poly.count() >= 3) { // are line a proper line and poly a proper poly?
317

318 319
            // Asseble a line form each tow consecutive polygon vertices and check whether it intersects with line
            for (int i = 0; i < poly.count(); i++) {
320

321 322 323 324 325
                QGCMapPolyline interatorLine;
                QGeoCoordinate currentVertex    = poly.vertexCoordinate(i);
                QGeoCoordinate nextVertex       = poly.vertexCoordinate(poly.nextVertexIndex(i));
                interatorLine.appendVertex(currentVertex);
                interatorLine.appendVertex(nextVertex);
326

327 328
                QGeoCoordinate intersectionPoint;
                if ( intersects(line, interatorLine, intersectionPoint) ){
329
                    intersectionList.append(intersectionPoint);
330

331 332
                    QPair<int, int>     neighbours;
                    neighbours.first    = i;
333
                    neighbours.second   = poly.nextVertexIndex(i);
334
                    neighbourList.append(neighbours);
335 336
                }
            }
337

338
            if (intersectionList.count() > 0) {
339 340 341 342
                return true;
            } else {
                return false;
            }
343 344
        } else {
            qWarning("WimaArea::intersects(line, poly): line->count() != 2 || poly->count() < 3");
345
            return false;
346 347 348
        }
}

349
double WimaArea::distInsidePoly(const QGeoCoordinate &c1, const QGeoCoordinate &c2, WimaArea poly)
350
{
351 352
        poly.offset(0.1); // hack to compensate for numerical issues, migh be replaced in the future...
        if ( poly.containsCoordinate(c1) && poly.containsCoordinate(c2)) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
353 354 355 356
            QList<QGeoCoordinate>   intersectionList;
            QList<QPair<int, int>>  neighbourlist;
            QGCMapPolyline line;

357 358
            line.appendVertex(c1);
            line.appendVertex(c2);
359
            intersects(line, poly, intersectionList, neighbourlist);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
360 361

            if ( intersectionList.size() == 0 ){
362
                return c1.distanceTo(c2);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
363 364
            } else {
                return std::numeric_limits<qreal>::infinity();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
365 366
            }

367 368
        } else {
            return std::numeric_limits<qreal>::infinity();
369 370 371
        }
}

372 373 374 375
bool WimaArea::dijkstraPath(const QGeoCoordinate    &start,
                            const QGeoCoordinate    &end,
                            const WimaArea          &poly,
                            QList<QGeoCoordinate>   &dijkstraPath)
376
{
377 378 379 380 381
    // ================ Brief Explanation ================
    // This function calculates the shortes Path between two GeoCoordinates (start, end) using the Dijkstra Algorithm.
    // The path will be inside the polygon poly if possible.
    // Stores the result inside dijkstraPath
    // Returns true if a valid path was found.
382 383 384 385
    if ( isSelfIntersecting(poly) ) {
        return false;
    }

386 387
    // Each QGeoCoordinate gets stuff into a Node
    /// @param distance is the distance between the Node and it's predecessor
388 389 390 391 392 393
    struct Node{
        QGeoCoordinate coordinate;
        double distance = std::numeric_limits<qreal>::infinity();
        Node* predecessorNode = nullptr;
    };

394
    // The list with all Nodes (start, end + poly.path())
395
    QList<Node> nodeList;
396 397
    // This list will be initalized with (pointer to) all elements of nodeList.
    // Elements will be successively remove during the execution of the Dijkstra Algorithm.
398 399
    QList<Node*> workingSet;

400 401
    // initialize nodeList
    // start cooridnate
402 403 404 405 406
    Node startNode;
    startNode.coordinate    = start;
    startNode.distance      = 0;
    nodeList.append(startNode);

407
    //poly cooridnates
408 409 410 411 412
    for (int i = 0; i < poly.count(); i++) {
        Node node;
        node.coordinate = poly.vertexCoordinate(i);
        nodeList.append(node);
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
413

414
    //end coordinate
415 416 417
    Node endNode;
    endNode.coordinate  = end;
    nodeList.append(endNode);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
418

419
    // initialize working set
420 421 422 423
    for (int i = 0; i < nodeList.size(); i++) {
        Node* nodePtr = &nodeList[i];
        workingSet.append(nodePtr);
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
424 425


426 427 428 429 430 431 432 433 434 435 436 437
    // Dijkstra Algorithm
    // https://de.wikipedia.org/wiki/Dijkstra-Algorithmus
    while (workingSet.size() > 0) {
        // serach Node with minimal distance
        double minDist = std::numeric_limits<qreal>::infinity();
        int minDistIndex = 0;
        for (int i = 0; i < workingSet.size(); i++) {
            Node* node = workingSet.value(i);
            double dist = node->distance;
            if (dist < minDist) {
                minDist = dist;
                minDistIndex = i;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
438
            }
439 440
        }
        Node* u = workingSet.takeAt(minDistIndex);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
441 442


443 444 445
        //update distance
        for (int i = 0; i < workingSet.size(); i++) {
            Node* v = workingSet[i];
Valentin Platzgummer's avatar
Valentin Platzgummer committed
446

447
            // is neighbour? dist == infinity if no neihbour
448
            double dist = distInsidePoly(u->coordinate, v->coordinate, poly);
449
            // is ther a alternative path which is shorter?
450 451 452 453
            double alternative = u->distance + dist;
            if (alternative < v->distance)  {
                v->distance         = alternative;
                v->predecessorNode  = u;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
454 455 456
            }
        }

457
    }
458 459
    // end Djikstra Algorithm

Valentin Platzgummer's avatar
Valentin Platzgummer committed
460

461
    // check it the Algorithm was sucessful
462 463 464
    Node* Node = &nodeList.last();
    if (Node->predecessorNode == nullptr) {
        qWarning("WimaArea::dijkstraPath(): Error, no path found!");
465
        return false;
466
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
467

468
    // assemble path
469 470 471 472 473 474 475
    while (1) {
        dijkstraPath.prepend(Node->coordinate);

        //Update Node
        Node = Node->predecessorNode;
        if (Node == nullptr) {
            break;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
476
        }
477
    }
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511

    return true;
}

bool WimaArea::isSelfIntersecting(const WimaArea &poly)
{
    int i = 0;
    if (poly.count() > 3) {
        while(i < poly.count()-1) {
            QGCMapPolyline refLine;
            refLine.appendVertex(poly.vertexCoordinate(i));
            refLine.appendVertex(poly.vertexCoordinate(poly.nextVertexIndex(i)));
            int j = poly.nextVertexIndex(i);
            while(j < poly.count()) {
                QGeoCoordinate dummy;
                QGCMapPolyline iteratorLine;
                iteratorLine.appendVertex(poly.vertexCoordinate(j));
                iteratorLine.appendVertex(poly.vertexCoordinate(poly.nextVertexIndex(j)));

                if ( intersects(refLine, iteratorLine, dummy) )
                    return true;

                j++;
            }
            i++;
        }
    }

    return false;
}

bool WimaArea::isSelfIntersecting()
{
    return isSelfIntersecting(*this);
512
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed
513

514 515 516 517 518 519 520
void WimaArea::saveToJson(QJsonObject &json)
{
    this->QGCMapPolygon::saveToJson(json);
    json[maxAltitudeName]   = _maxAltitude;
    json[areaTypeName]      = wimaAreaName;
    // add WimaVehicle if necessary..
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed
521

522 523 524 525 526 527 528
bool WimaArea::loadFromJson(const QJsonObject &json, QString& errorString)
{
    if ( this->QGCMapPolygon::loadFromJson(json, false /*no poly required*/, errorString) ) {
        if ( json.contains(maxAltitudeName) && json[maxAltitudeName].isDouble()) {
            _maxAltitude = json[maxAltitudeName].toDouble();
            return true;
        } else {
529
            errorString.append(tr("Could not load Maximum Altitude value!\n"));
530 531
            return false;
        }
532
    } else {
533 534
        qWarning() << errorString;
        return false;
535 536
    }
}
537

538 539 540 541 542 543
void WimaArea::update(const WimaArea &area)
{
    this->QGCMapPolygon::update(area);
    this->setMaxAltitude(area.maxAltitude());
}

544 545 546 547 548 549 550 551 552 553 554 555 556 557
void WimaArea::init()
{
    this->setObjectName(wimaAreaName);
}

void print(const WimaArea &area)
{
    QString message;
    print(area, message);
    qWarning() << message;
}

void print(const WimaArea &area, QString &outputString)
{
558
    outputString.append(QString("Type: %1").arg(area.objectName()));
559
    print(static_cast<const QGCMapPolygon&>(area), outputString);
560
    outputString.append(QString("Maximum Altitude: %1").arg(area._maxAltitude));
561 562
}