QGCMapPolygon.cc 18.2 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9 10
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#include "QGCMapPolygon.h"
11
#include "JsonHelper.h"
DonLakeFlyer's avatar
DonLakeFlyer committed
12
#include "QGCApplication.h"
13
#include "QGCGeo.h"
14
#include "QGCLoggingCategory.h"
15 16
#include "QGCQGeoCoordinate.h"
#include "ShapeFileHelper.h"
17 18

#include <QDebug>
19 20 21
#include <QDomDocument>
#include <QFile>
#include <QGeoRectangle>
22
#include <QJsonArray>
23
#include <QLineF>
24

25
const char *QGCMapPolygon::jsonPolygonKey = "polygon";
26

27 28 29 30
QGCMapPolygon::QGCMapPolygon(QObject *parent)
    : QObject(parent), _dirty(false), _centerDrag(false),
      _ignoreCenterUpdates(false), _interactive(false), _resetActive(false) {
  _init();
31 32
}

33 34 35 36 37
QGCMapPolygon::QGCMapPolygon(const QGCMapPolygon &other, QObject *parent)
    : QObject(parent), _dirty(false), _centerDrag(false),
      _ignoreCenterUpdates(false), _interactive(false), _resetActive(false) {
  _init();
  *this = other;
38 39
}

40 41 42 43 44
void QGCMapPolygon::_init(void) {
  connect(&_polygonModel, &QmlObjectListModel::dirtyChanged, this,
          &QGCMapPolygon::_polygonModelDirtyChanged);
  connect(&_polygonModel, &QmlObjectListModel::countChanged, this,
          &QGCMapPolygon::_polygonModelCountChanged);
DonLakeFlyer's avatar
DonLakeFlyer committed
45

46 47 48 49 50 51
  connect(this, &QGCMapPolygon::pathChanged, this,
          &QGCMapPolygon::_updateCenter);
  connect(this, &QGCMapPolygon::countChanged, this,
          &QGCMapPolygon::isValidChanged);
  connect(this, &QGCMapPolygon::countChanged, this,
          &QGCMapPolygon::isEmptyChanged);
52 53
}

54 55
const QGCMapPolygon &QGCMapPolygon::operator=(const QGCMapPolygon &other) {
  clear();
56

57 58 59 60 61 62
  QVariantList vertices = other.path();
  QList<QGeoCoordinate> rgCoord;
  for (const QVariant &vertexVar : vertices) {
    rgCoord.append(vertexVar.value<QGeoCoordinate>());
  }
  appendVertices(rgCoord);
63

64
  setDirty(true);
65

66
  return *this;
67 68
}

69 70 71 72 73 74
void QGCMapPolygon::clear(void) {
  // Bug workaround, see below
  while (_polygonPath.count() > 1) {
    _polygonPath.takeLast();
  }
  emit pathChanged();
75

76 77 78 79 80
  // Although this code should remove the polygon from the map it doesn't. There
  // appears to be a bug in QGCMapPolygon which causes it to not be redrawn if
  // the list is empty. So we work around it by using the code above to remove
  // all but the last point which in turn will cause the polygon to go away.
  _polygonPath.clear();
81

82
  _polygonModel.clearAndDeleteContents();
83

84
  emit cleared();
85

86
  setDirty(true);
87 88
}

89 90 91 92 93 94 95 96 97 98 99
void QGCMapPolygon::adjustVertex(int vertexIndex,
                                 const QGeoCoordinate coordinate) {
  _polygonPath[vertexIndex] = QVariant::fromValue(coordinate);
  _polygonModel.value<QGCQGeoCoordinate *>(vertexIndex)
      ->setCoordinate(coordinate);
  if (!_centerDrag) {
    // When dragging center we don't signal path changed until all vertices are
    // updated
    emit pathChanged();
  }
  setDirty(true);
100 101
}

102 103 104 105 106
void QGCMapPolygon::setDirty(bool dirty) {
  if (_dirty != dirty) {
    _dirty = dirty;
    if (!dirty) {
      _polygonModel.setDirty(false);
107
    }
108 109
    emit dirtyChanged(dirty);
  }
110 111
}

112 113
QGeoCoordinate QGCMapPolygon::_coordFromPointF(const QPointF &point) const {
  QGeoCoordinate coord;
114

115 116 117 118
  if (_polygonPath.count() > 0) {
    QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
    convertNedToGeo(-point.y(), point.x(), 0, tangentOrigin, &coord);
  }
119

120
  return coord;
121
}
122

123 124 125 126 127
QPointF
QGCMapPolygon::_pointFFromCoord(const QGeoCoordinate &coordinate) const {
  if (_polygonPath.count() > 0) {
    double y, x, down;
    QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
128

129 130 131
    convertGeoToNed(coordinate, tangentOrigin, &y, &x, &down);
    return QPointF(x, -y);
  }
132

133
  return QPointF();
134
}
135

136 137
QPolygonF QGCMapPolygon::_toPolygonF(void) const {
  QPolygonF polygon;
138

139 140 141
  if (_polygonPath.count() > 2) {
    for (int i = 0; i < _polygonPath.count(); i++) {
      polygon.append(_pointFFromCoord(_polygonPath[i].value<QGeoCoordinate>()));
142
    }
143
  }
144

145
  return polygon;
146 147
}

148 149 150 151 152 153 154
bool QGCMapPolygon::containsCoordinate(const QGeoCoordinate &coordinate) const {
  if (_polygonPath.count() > 2) {
    return _toPolygonF().containsPoint(_pointFFromCoord(coordinate),
                                       Qt::OddEvenFill);
  } else {
    return false;
  }
155 156
}

157 158 159 160 161 162 163
void QGCMapPolygon::setPath(const QList<QGeoCoordinate> &path) {
  _polygonPath.clear();
  _polygonModel.clearAndDeleteContents();
  for (const QGeoCoordinate &coord : path) {
    _polygonPath.append(QVariant::fromValue(coord));
    _polygonModel.append(new QGCQGeoCoordinate(coord, this));
  }
164

165 166
  setDirty(true);
  emit pathChanged();
167 168
}

169 170
void QGCMapPolygon::setPath(const QVariantList &path) {
  _polygonPath = path;
171

172 173 174 175 176
  _polygonModel.clearAndDeleteContents();
  for (int i = 0; i < _polygonPath.count(); i++) {
    _polygonModel.append(
        new QGCQGeoCoordinate(_polygonPath[i].value<QGeoCoordinate>(), this));
  }
177

178 179
  setDirty(true);
  emit pathChanged();
180
}
181

182 183
void QGCMapPolygon::saveToJson(QJsonObject &json) {
  QJsonValue jsonValue;
184

185 186 187 188
  JsonHelper::saveGeoCoordinateArray(_polygonPath, false /* writeAltitude*/,
                                     jsonValue);
  json.insert(jsonPolygonKey, jsonValue);
  setDirty(false);
189 190
}

191 192 193 194
bool QGCMapPolygon::loadFromJson(const QJsonObject &json, bool required,
                                 QString &errorString) {
  errorString.clear();
  clear();
195

196 197 198 199
  if (required) {
    if (!JsonHelper::validateRequiredKeys(json, QStringList(jsonPolygonKey),
                                          errorString)) {
      return false;
200
    }
201 202 203
  } else if (!json.contains(jsonPolygonKey)) {
    return true;
  }
204

205 206 207 208 209
  if (!JsonHelper::loadGeoCoordinateArray(json[jsonPolygonKey],
                                          false /* altitudeRequired */,
                                          _polygonPath, errorString)) {
    return false;
  }
210

211 212 213 214
  for (int i = 0; i < _polygonPath.count(); i++) {
    _polygonModel.append(
        new QGCQGeoCoordinate(_polygonPath[i].value<QGeoCoordinate>(), this));
  }
215

216 217
  setDirty(false);
  emit pathChanged();
218

219
  return true;
220 221
}

222 223
QList<QGeoCoordinate> QGCMapPolygon::coordinateList(void) const {
  QList<QGeoCoordinate> coords;
224

225 226 227
  for (int i = 0; i < _polygonPath.count(); i++) {
    coords.append(_polygonPath[i].value<QGeoCoordinate>());
  }
228

229
  return coords;
230
}
231

232 233 234 235 236
void QGCMapPolygon::splitPolygonSegment(int vertexIndex) {
  int nextIndex = vertexIndex + 1;
  if (nextIndex > _polygonPath.length() - 1) {
    nextIndex = 0;
  }
237

238 239 240
  QGeoCoordinate firstVertex =
      _polygonPath[vertexIndex].value<QGeoCoordinate>();
  QGeoCoordinate nextVertex = _polygonPath[nextIndex].value<QGeoCoordinate>();
241

242 243 244 245
  double distance = firstVertex.distanceTo(nextVertex);
  double azimuth = firstVertex.azimuthTo(nextVertex);
  QGeoCoordinate newVertex =
      firstVertex.atDistanceAndAzimuth(distance / 2, azimuth);
246

247 248 249 250 251 252 253
  if (nextIndex == 0) {
    appendVertex(newVertex);
  } else {
    _polygonModel.insert(nextIndex, new QGCQGeoCoordinate(newVertex, this));
    _polygonPath.insert(nextIndex, QVariant::fromValue(newVertex));
    emit pathChanged();
  }
254 255
}

256 257 258 259
void QGCMapPolygon::appendVertex(const QGeoCoordinate &coordinate) {
  _polygonPath.append(QVariant::fromValue(coordinate));
  _polygonModel.append(new QGCQGeoCoordinate(coordinate, this));
  emit pathChanged();
260 261
}

262 263
void QGCMapPolygon::appendVertices(const QList<QGeoCoordinate> &coordinates) {
  QList<QObject *> objects;
264

265 266 267 268 269 270 271
  _beginResetIfNotActive();
  for (const QGeoCoordinate &coordinate : coordinates) {
    objects.append(new QGCQGeoCoordinate(coordinate, this));
    _polygonPath.append(QVariant::fromValue(coordinate));
  }
  _polygonModel.append(objects);
  _endResetIfNotActive();
Don Gagne's avatar
Don Gagne committed
272

273
  emit pathChanged();
274 275
}

276 277 278 279 280 281
void QGCMapPolygon::appendVertices(const QVariantList &varCoords) {
  QList<QGeoCoordinate> rgCoords;
  for (const QVariant &varCoord : varCoords) {
    rgCoords.append(varCoord.value<QGeoCoordinate>());
  }
  appendVertices(rgCoords);
282 283
}

284 285 286 287
void QGCMapPolygon::_polygonModelDirtyChanged(bool dirty) {
  if (dirty) {
    setDirty(true);
  }
288 289
}

290 291 292 293 294 295
void QGCMapPolygon::removeVertex(int vertexIndex) {
  if (vertexIndex < 0 && vertexIndex > _polygonPath.length() - 1) {
    qWarning() << "Call to removePolygonCoordinate with bad vertexIndex:count"
               << vertexIndex << _polygonPath.length();
    return;
  }
296

297 298 299 300
  if (_polygonPath.length() <= 3) {
    // Don't allow the user to trash the polygon
    return;
  }
301

302 303 304 305 306 307 308
  QObject *coordObj = _polygonModel.removeAt(vertexIndex);
  coordObj->deleteLater();
  if (vertexIndex == _selectedVertexIndex) {
    selectVertex(-1);
  } else if (vertexIndex < _selectedVertexIndex) {
    selectVertex(_selectedVertexIndex - 1);
  } // else do nothing - keep current selected vertex
309

310 311
  _polygonPath.removeAt(vertexIndex);
  emit pathChanged();
312 313
}

314 315
void QGCMapPolygon::_polygonModelCountChanged(int count) {
  emit countChanged(count);
316 317
}

318 319 320
void QGCMapPolygon::_updateCenter(void) {
  if (!_ignoreCenterUpdates) {
    QGeoCoordinate center;
321

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
    if (_polygonPath.count() > 2) {
      QPointF centroid(0, 0);
      QPolygonF polygonF = _toPolygonF();
      for (int i = 0; i < polygonF.count(); i++) {
        centroid += polygonF[i];
      }
      center = _coordFromPointF(QPointF(centroid.x() / polygonF.count(),
                                        centroid.y() / polygonF.count()));
    }
    if (_center != center) {
      _center = center;
      emit centerChanged(center);
    }
  }
}
337

338 339 340
void QGCMapPolygon::setCenter(QGeoCoordinate newCenter) {
  if (newCenter != _center) {
    _ignoreCenterUpdates = true;
341

342 343 344
    // Adjust polygon vertices to new center
    double distance = _center.distanceTo(newCenter);
    double azimuth = _center.azimuthTo(newCenter);
DonLakeFlyer's avatar
DonLakeFlyer committed
345

346 347 348 349 350 351
    for (int i = 0; i < count(); i++) {
      QGeoCoordinate oldVertex = _polygonPath[i].value<QGeoCoordinate>();
      QGeoCoordinate newVertex =
          oldVertex.atDistanceAndAzimuth(distance, azimuth);
      adjustVertex(i, newVertex);
    }
DonLakeFlyer's avatar
DonLakeFlyer committed
352

353 354 355 356
    if (_centerDrag) {
      // When center dragging, signals from adjustVertext are not sent. So we
      // need to signal here when all adjusting is complete.
      emit pathChanged();
DonLakeFlyer's avatar
DonLakeFlyer committed
357
    }
358 359 360 361 362 363

    _ignoreCenterUpdates = false;

    _center = newCenter;
    emit centerChanged(newCenter);
  }
DonLakeFlyer's avatar
DonLakeFlyer committed
364 365
}

366 367 368 369 370
void QGCMapPolygon::setCenterDrag(bool centerDrag) {
  if (centerDrag != _centerDrag) {
    _centerDrag = centerDrag;
    emit centerDragChanged(centerDrag);
  }
371
}
372

373 374 375 376 377
void QGCMapPolygon::setInteractive(bool interactive) {
  if (_interactive != interactive) {
    _interactive = interactive;
    emit interactiveChanged(interactive);
  }
378
}
379

380 381 382 383 384 385 386 387
QGeoCoordinate QGCMapPolygon::vertexCoordinate(int vertex) const {
  if (vertex >= 0 && vertex < _polygonPath.count()) {
    return _polygonPath[vertex].value<QGeoCoordinate>();
  } else {
    qWarning() << "QGCMapPolygon::vertexCoordinate bad vertex requested:count"
               << vertex << _polygonPath.count();
    return QGeoCoordinate();
  }
388
}
389

390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
QList<QPointF> QGCMapPolygon::nedPolygon(void) const {
  QList<QPointF> nedPolygon;

  if (count() > 0) {
    QGeoCoordinate tangentOrigin = vertexCoordinate(0);

    for (int i = 0; i < _polygonModel.count(); i++) {
      double y, x, down;
      QGeoCoordinate vertex = vertexCoordinate(i);
      if (i == 0) {
        // This avoids a nan calculation that comes out of convertGeoToNed
        x = y = 0;
      } else {
        convertGeoToNed(vertex, tangentOrigin, &y, &x, &down);
      }
      nedPolygon += QPointF(x, y);
406
    }
407
  }
408

409
  return nedPolygon;
410 411
}

412 413
void QGCMapPolygon::offset(double distance) {
  QList<QGeoCoordinate> rgNewPolygon;
414

415 416
  // I'm sure there is some beautiful famous algorithm to do this, but here is a
  // brute force method
417

418 419 420
  if (count() > 2) {
    // Convert the polygon to NED
    QList<QPointF> rgNedVertices = nedPolygon();
421

422 423 424 425 426 427
    // Walk the edges, offsetting by the specified distance
    QList<QLineF> rgOffsetEdges;
    for (int i = 0; i < rgNedVertices.count(); i++) {
      int lastIndex = i == rgNedVertices.count() - 1 ? 0 : i + 1;
      QLineF offsetEdge;
      QLineF originalEdge(rgNedVertices[i], rgNedVertices[lastIndex]);
428

429 430 431 432
      QLineF workerLine = originalEdge;
      workerLine.setLength(distance);
      workerLine.setAngle(workerLine.angle() - 90.0);
      offsetEdge.setP1(workerLine.p2());
433

434 435 436 437
      workerLine.setPoints(originalEdge.p2(), originalEdge.p1());
      workerLine.setLength(distance);
      workerLine.setAngle(workerLine.angle() + 90.0);
      offsetEdge.setP2(workerLine.p2());
438

439 440
      rgOffsetEdges.append(offsetEdge);
    }
441

442 443 444 445 446
    // Intersect the offset edges to generate new vertices
    QPointF newVertex;
    QGeoCoordinate tangentOrigin = vertexCoordinate(0);
    for (int i = 0; i < rgOffsetEdges.count(); i++) {
      int prevIndex = i == 0 ? rgOffsetEdges.count() - 1 : i - 1;
447
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
448 449
      auto intersect =
          rgOffsetEdges[prevIndex].intersect(rgOffsetEdges[i], &newVertex);
450
#else
451 452
      auto intersect =
          rgOffsetEdges[prevIndex].intersects(rgOffsetEdges[i], &newVertex);
453
#endif
454 455 456 457 458 459 460 461
      if (intersect == QLineF::NoIntersection) {
        // FIXME: Better error handling?
        qWarning("Intersection failed");
        return;
      }
      QGeoCoordinate coord;
      convertNedToGeo(newVertex.y(), newVertex.x(), 0, tangentOrigin, &coord);
      rgNewPolygon.append(coord);
462
    }
463
  }
464

465 466 467 468 469
  // Update internals
  _beginResetIfNotActive();
  clear();
  appendVertices(rgNewPolygon);
  _endResetIfNotActive();
470
}
DonLakeFlyer's avatar
DonLakeFlyer committed
471

472 473 474 475 476 477 478
bool QGCMapPolygon::loadKMLOrSHPFile(const QString &file) {
  QString errorString;
  QList<QGeoCoordinate> rgCoords;
  if (!ShapeFileHelper::loadPolygonFromFile(file, rgCoords, errorString)) {
    qgcApp()->showAppMessage(errorString);
    return false;
  }
DonLakeFlyer's avatar
DonLakeFlyer committed
479

480 481 482 483
  _beginResetIfNotActive();
  clear();
  appendVertices(rgCoords);
  _endResetIfNotActive();
DonLakeFlyer's avatar
DonLakeFlyer committed
484

485
  return true;
DonLakeFlyer's avatar
DonLakeFlyer committed
486
}
487

488 489
double QGCMapPolygon::area(void) const {
  // https://www.mathopenref.com/coordpolygonarea2.html
490

491 492 493
  if (_polygonPath.count() < 3) {
    return 0;
  }
DonLakeFlyer's avatar
DonLakeFlyer committed
494

495 496 497 498 499 500 501 502 503
  double coveredArea = 0.0;
  QList<QPointF> nedVertices = nedPolygon();
  for (int i = 0; i < nedVertices.count(); i++) {
    if (i != 0) {
      coveredArea += nedVertices[i - 1].x() * nedVertices[i].y() -
                     nedVertices[i].x() * nedVertices[i - 1].y();
    } else {
      coveredArea += nedVertices.last().x() * nedVertices[i].y() -
                     nedVertices[i].x() * nedVertices.last().y();
504
    }
505 506
  }
  return 0.5 * fabs(coveredArea);
507
}
508

509 510 511 512
void QGCMapPolygon::verifyClockwiseWinding(void) {
  if (_polygonPath.count() <= 2) {
    return;
  }
513

514 515 516 517 518 519
  double sum = 0;
  for (int i = 0; i < _polygonPath.count(); i++) {
    QGeoCoordinate coord1 = _polygonPath[i].value<QGeoCoordinate>();
    QGeoCoordinate coord2 = (i == _polygonPath.count() - 1)
                                ? _polygonPath[0].value<QGeoCoordinate>()
                                : _polygonPath[i + 1].value<QGeoCoordinate>();
520

521 522 523
    sum += (coord2.longitude() - coord1.longitude()) *
           (coord2.latitude() + coord1.latitude());
  }
524

525 526
  if (sum < 0.0) {
    // Winding is counter-clockwise and needs reversal
527

528 529 530
    QList<QGeoCoordinate> rgReversed;
    for (const QVariant &varCoord : _polygonPath) {
      rgReversed.prepend(varCoord.value<QGeoCoordinate>());
Don Gagne's avatar
Don Gagne committed
531
    }
532 533 534 535 536 537

    _beginResetIfNotActive();
    clear();
    appendVertices(rgReversed);
    _endResetIfNotActive();
  }
Don Gagne's avatar
Don Gagne committed
538 539
}

540 541 542
void QGCMapPolygon::beginReset(void) {
  _resetActive = true;
  _polygonModel.beginReset();
Don Gagne's avatar
Don Gagne committed
543 544
}

545 546 547 548 549
void QGCMapPolygon::endReset(void) {
  _resetActive = false;
  _polygonModel.endReset();
  emit pathChanged();
  emit centerChanged(_center);
Don Gagne's avatar
Don Gagne committed
550 551
}

552 553 554 555
void QGCMapPolygon::_beginResetIfNotActive(void) {
  if (!_resetActive) {
    beginReset();
  }
Don Gagne's avatar
Don Gagne committed
556 557
}

558 559 560 561
void QGCMapPolygon::_endResetIfNotActive(void) {
  if (!_resetActive) {
    endReset();
  }
562
}
563

564
QDomElement QGCMapPolygon::kmlPolygonElement(KMLDomDocument &domDocument) {
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
#if 0
    <Polygon id="ID">
      <!-- specific to Polygon -->
      <extrude>0</extrude>                       <!-- boolean -->
      <tessellate>0</tessellate>                 <!-- boolean -->
      <altitudeMode>clampToGround</altitudeMode>
            <!-- kml:altitudeModeEnum: clampToGround, relativeToGround, or absolute -->
            <!-- or, substitute gx:altitudeMode: clampToSeaFloor, relativeToSeaFloor -->
      <outerBoundaryIs>
        <LinearRing>
          <coordinates>...</coordinates>         <!-- lon,lat[,alt] -->
        </LinearRing>
      </outerBoundaryIs>
      <innerBoundaryIs>
        <LinearRing>
          <coordinates>...</coordinates>         <!-- lon,lat[,alt] -->
        </LinearRing>
      </innerBoundaryIs>
    </Polygon>
#endif

586
  QDomElement polygonElement = domDocument.createElement("Polygon");
587

588
  domDocument.addTextElement(polygonElement, "altitudeMode", "clampToGround");
589

590 591 592
  QDomElement outerBoundaryIsElement =
      domDocument.createElement("outerBoundaryIs");
  QDomElement linearRingElement = domDocument.createElement("LinearRing");
593

594 595
  outerBoundaryIsElement.appendChild(linearRingElement);
  polygonElement.appendChild(outerBoundaryIsElement);
596

597 598 599 600 601 602 603 604
  QString coordString;
  for (const QVariant &varCoord : _polygonPath) {
    coordString += QStringLiteral("%1\n").arg(
        domDocument.kmlCoordString(varCoord.value<QGeoCoordinate>()));
  }
  coordString += QStringLiteral("%1\n").arg(
      domDocument.kmlCoordString(_polygonPath.first().value<QGeoCoordinate>()));
  domDocument.addTextElement(linearRingElement, "coordinates", coordString);
605

606
  return polygonElement;
607
}
DoinLakeFlyer's avatar
DoinLakeFlyer committed
608

609 610 611 612 613
void QGCMapPolygon::setTraceMode(bool traceMode) {
  if (traceMode != _traceMode) {
    _traceMode = traceMode;
    emit traceModeChanged(traceMode);
  }
DoinLakeFlyer's avatar
DoinLakeFlyer committed
614
}
615

616 617 618 619 620
void QGCMapPolygon::setShowAltColor(bool showAltColor) {
  if (showAltColor != _showAltColor) {
    _showAltColor = showAltColor;
    emit showAltColorChanged(showAltColor);
  }
621
}
622

623 624 625
void QGCMapPolygon::selectVertex(int index) {
  if (index == _selectedVertexIndex)
    return; // do nothing
626

627 628 629 630 631 632 633 634 635 636 637
  if (-1 <= index && index < count()) {
    _selectedVertexIndex = index;
  } else {
    if (!qgcApp()->runningUnitTests()) {
      qCWarning(ParameterManagerLog)
          << QString(
                 "QGCMapPolygon: Selected vertex index (%1) is out of bounds! "
                 "Polygon vertices indexes range is [%2..%3].")
                 .arg(index)
                 .arg(0)
                 .arg(count() - 1);
638
    }
639 640
    _selectedVertexIndex = -1; // deselect vertex
  }
641

642
  emit selectedVertexChanged(_selectedVertexIndex);
643
}