AreaData.cc 14.5 KB
Newer Older
1
#include "AreaData.h"
2

3 4
#include "geometry/MeasurementArea.h"
#include "geometry/SafeArea.h"
5
#include "geometry/geometry.h"
6

7
#include "JsonHelper.h"
8
#include "QGCApplication.h"
9 10
#include "QGCLoggingCategory.h"
#include "QGCQGeoCoordinate.h"
11

12
QGC_LOGGING_CATEGORY(AreaDataLog, "AreaDataLog")
13

14
const char *originKey = "Origin";
15
const char *areaListKey = "AreaList";
16

17
AreaData::AreaData(QObject *parent) : QObject(parent) {}
18

19
AreaData::~AreaData() {}
20

21
AreaData::AreaData(const AreaData &other, QObject *parent) : QObject(parent) {
22
  *this = other;
23 24
}

25
AreaData &AreaData::operator=(const AreaData &other) {
26 27 28 29 30 31 32
  this->clear();

  // Clone elements.
  for (int i = 0; i < other._areaList.count(); ++i) {
    auto obj = other._areaList[i];
    auto area = qobject_cast<const GeoArea *>(obj);
    this->insert(area->clone(this));
33
  }
34 35 36

  _origin = other._origin;

37
  return *this;
38 39
}

40
bool AreaData::insert(GeoArea *areaData) {
41 42 43
  if (areaData != nullptr) {
    if (Q_LIKELY(!this->_areaList.contains(areaData))) {
      _areaList.append(areaData);
44
      emit areaListChanged();
45 46 47 48 49 50 51

      auto *measurementArea = qobject_cast<MeasurementArea *>(areaData);
      if (measurementArea != nullptr) {
        connect(measurementArea, &MeasurementArea::centerChanged, this,
                &AreaData::_updateOrigin);
        _setOrigin(measurementArea->center());
      }
52
      return true;
53
    }
54 55
  }

56
  return false;
57 58
}

59 60 61 62
void AreaData::remove(GeoArea *areaData) {
  int index = _areaList.indexOf(areaData);
  if (index >= 0) {
    QObject *obj = _areaList.removeAt(index);
63

64 65 66 67 68 69
    auto *measurementArea = qobject_cast<MeasurementArea *>(areaData);
    if (measurementArea != nullptr) {
      disconnect(measurementArea, &MeasurementArea::centerChanged, this,
                 &AreaData::_updateOrigin);
      _setOrigin(QGeoCoordinate());
    }
70

71
    if (obj->parent() == nullptr || obj->parent() == this) {
72
      obj->deleteLater();
73 74
    }

75 76
    emit areaListChanged();
  }
77 78
}

79 80
void AreaData::clear() {
  if (_areaList.count() > 0) {
81 82
    while (_areaList.count() > 0) {
      remove(_areaList.value<GeoArea *>(0));
83 84 85
    }
    emit areaListChanged();
  }
86
  _errorString.clear();
87 88
}

89
QmlObjectListModel *AreaData::areaList() { return &_areaList; }
90

91
const QmlObjectListModel *AreaData::areaList() const { return &_areaList; }
92

93
QGeoCoordinate AreaData::origin() const { return _origin; }
94

95
bool AreaData::isCorrect(bool showError) {
96 97 98 99 100 101
  if (!initialized()) {
    qCWarning(AreaDataLog) << "isCorrect(): not initialized";
    return false;
  }

  // Check if areas are correct
102
  if (!_areasCorrect(showError)) {
103 104 105 106 107 108
    return false;
  }

  // Check if areas where added.
  MeasurementArea *measurementArea = nullptr;
  SafeArea *safeArea = nullptr;
109
  if (!_getAreas(&measurementArea, &safeArea, showError)) {
110 111
    return false;
  }
112

113 114 115 116 117 118
  // Check if measurement area is covered by safe area.
  if (!_origin.isValid()) {
    qCWarning(AreaDataLog) << "isCorrect(): origin invalid";
    return false;
  }
  const auto &origin = this->origin();
119 120 121 122
  geometry::FPolygon safeAreaENU;
  geometry::areaToEnu(origin, safeArea->pathModel(), safeAreaENU);
  geometry::FPolygon measurementAreaENU;
  geometry::areaToEnu(origin, measurementArea->pathModel(), measurementAreaENU);
123 124 125 126 127 128
  //  qDebug() << "origin" << origin;
  //  std::stringstream ss;
  //  ss << "measurementAreaENU: " << bg::wkt(measurementAreaENU) << std::endl;
  //  ss << "safeAreaENU: " << bg::wkt(safeAreaENU) << std::endl;
  //  qDebug() << ss.str().c_str();
  if (!bg::covered_by(measurementAreaENU, safeAreaENU)) {
129 130 131
    _processError(tr("Measurement Area is not covered by Safe "
                     "Area. Please adjust "
                     "the areas accordingly.\n"),
132
                  showError);
133 134
    return false;
  }
135

136 137 138 139 140 141 142 143 144 145 146
  return true;
}

bool AreaData::initialize(const QGeoCoordinate &bottomLeft,
                          const QGeoCoordinate &topRight) {
  // bottomLeft and topRight define the bounding box.
  if (bottomLeft.isValid() && topRight.isValid() && bottomLeft != topRight) {
    auto *measurementArea = getGeoArea<MeasurementArea *>(_areaList);
    auto *safeArea = getGeoArea<SafeArea *>(_areaList);

    if (safeArea == nullptr) {
147 148 149
      safeArea = new SafeArea(this);
      if (!insert(safeArea)) {
        safeArea->deleteLater();
150 151 152 153 154
        qCCritical(AreaDataLog)
            << "initialize(): safeArea == nullptr, but insert() failed.";
        return false;
      }
    }
155

156
    if (measurementArea == nullptr) {
157 158 159
      measurementArea = new MeasurementArea(this);
      if (!insert(measurementArea)) {
        measurementArea->deleteLater();
160 161 162 163 164
        qCCritical(AreaDataLog) << "initialize(): measurementArea == nullptr, "
                                   "but insert() failed.";
        return false;
      }
    }
165

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    // Fit safe area to bounding box.
    safeArea->clear();
    safeArea->appendVertex(bottomLeft);
    safeArea->appendVertex(
        QGeoCoordinate(topRight.latitude(), bottomLeft.longitude()));
    safeArea->appendVertex(topRight);
    safeArea->appendVertex(
        QGeoCoordinate(bottomLeft.latitude(), topRight.longitude()));

    // Put measurement area inside safeArea;
    measurementArea->clear();
    measurementArea->appendVertex(QGeoCoordinate(
        0.8 * bottomLeft.latitude() + 0.2 * topRight.latitude(),
        0.8 * bottomLeft.longitude() + 0.2 * topRight.longitude()));
    measurementArea->appendVertex(QGeoCoordinate(
        0.2 * bottomLeft.latitude() + 0.8 * topRight.latitude(),
        0.8 * bottomLeft.longitude() + 0.2 * topRight.longitude()));
    measurementArea->appendVertex(QGeoCoordinate(
        0.2 * bottomLeft.latitude() + 0.8 * topRight.latitude(),
        0.2 * bottomLeft.longitude() + 0.8 * topRight.longitude()));
    measurementArea->appendVertex(QGeoCoordinate(
        0.8 * bottomLeft.latitude() + 0.2 * topRight.latitude(),
        0.2 * bottomLeft.longitude() + 0.8 * topRight.longitude()));
189 190 191 192 193 194 195 196

    // Set depot
    safeArea->setDepot(QGeoCoordinate(
        safeArea->vertexCoordinate(0).latitude() * 0.5 +
            measurementArea->vertexCoordinate(0).latitude() * 0.5,
        safeArea->vertexCoordinate(0).longitude() * 0.5 +
            measurementArea->vertexCoordinate(0).longitude() * 0.5));

197 198 199 200 201 202 203 204
    return true;
  } else {
    qCWarning(AreaDataLog)
        << "initialize(): bounding box invaldid (bottomLeft, topRight) "
        << bottomLeft << "," << topRight;
    return false;
  }
}
205

206 207 208 209 210 211
bool AreaData::initialized() {
  auto measurementArea = getGeoArea<MeasurementArea *>(_areaList);
  auto safeArea = getGeoArea<SafeArea *>(_areaList);
  return measurementArea != nullptr && safeArea != nullptr &&
         measurementArea->count() >= 3 && safeArea->count() >= 3;
}
212

213 214
void AreaData::intersection(bool showError) {
  if (initialized() && _areasCorrect(showError)) {
215 216
    MeasurementArea *measurementArea = nullptr;
    SafeArea *safeArea = nullptr;
217
    if (_getAreas(&measurementArea, &safeArea, showError)) {
218 219 220

      // convert to ENU
      const auto origin = this->origin();
221 222 223 224
      geometry::FPolygon safeAreaENU;
      geometry::areaToEnu(origin, safeArea->pathModel(), safeAreaENU);
      geometry::FPolygon measurementAreaENU;
      geometry::areaToEnu(origin, measurementArea->pathModel(),
225 226 227
                       measurementAreaENU);

      // do intersection
228
      std::deque<geometry::FPolygon> outputENU;
229 230 231
      boost::geometry::intersection(measurementAreaENU, safeAreaENU, outputENU);

      if (outputENU.size() < 1 || outputENU[0].outer().size() < 4) {
232 233 234 235
        _processError("Intersection did't deliver any result. The Measurement "
                      "Area(s) and "
                      "The Safe Area must touch each other.",
                      showError);
236 237 238 239 240 241 242
        return;
      }

      if (outputENU[0].inners().size() > 0 || outputENU.size() > 1) {
        _processError(
            "Hint: Only simple polygons can be displayed. If Intersection"
            "produces polygons with holes or multi polygons, only "
243 244
            "partial information can be displayed.",
            showError);
245 246 247 248
      }

      // Shrink the result if safeAreaENU doesn't cover it.
      auto large = std::move(outputENU[0]);
249
      geometry::FPolygon small;
250
      while (!bg::covered_by(large, safeAreaENU)) {
251
        geometry::offsetPolygon(large, small, -0.1);
252 253 254
        large = std::move(small);
      }

255 256 257 258 259 260 261
      // Check if result is different from input.
      if (!bg::equals(large, measurementAreaENU)) {
        // Convert.
        measurementArea->clear();
        for (auto it = large.outer().begin(); it != large.outer().end() - 1;
             ++it) {
          QGeoCoordinate c;
262
          geometry::fromENU(origin, *it, c);
263 264
          measurementArea->appendVertex(c);
        }
265 266 267
      }
    }
  }
268 269
}

270 271 272 273 274 275
QVector<MeasurementArea *> AreaData::measurementAreaArray() {
  return getGeoAreaList<MeasurementArea *, QVector>(_areaList);
}

QVector<SafeArea *> AreaData::safeAreaArray() {
  return getGeoAreaList<SafeArea *, QVector>(_areaList);
276 277
}

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
QmlObjectListModel *AreaData::measurementAreaList() {
  _measurementAreaList.clear();
  auto array = getGeoAreaList<MeasurementArea *, QVector>(_areaList);
  for (auto area : array)
    _measurementAreaList.append(area);
  return &_measurementAreaList;
}

QmlObjectListModel *AreaData::safeAreaList() {
  _safeAreaList.clear();
  auto array = getGeoAreaList<SafeArea *, QVector>(_areaList);
  for (auto &area : array)
    _safeAreaList.append(area);
  return &_safeAreaList;
}
293

294
bool AreaData::operator==(const AreaData &other) const {
295 296 297 298 299 300 301 302 303 304
  if (_areaList.count() == other._areaList.count()) {
    for (int i = 0; i < _areaList.count(); ++i) {
      if (_areaList[i] != other._areaList[i]) {
        return false;
      }
    }
    return true;
  } else {
    return false;
  }
305 306 307 308 309
}
bool AreaData::operator!=(const AreaData &other) const {
  return !(*this == other);
}

310 311 312 313
bool AreaData::load(const QJsonObject &obj, QString &errorString) {
  bool returnValue = true;

  // load areaList.
314 315 316 317 318 319 320 321 322 323 324 325 326 327
  if (obj.contains(areaListKey) && obj[areaListKey].isArray()) {

    this->clear();

    // iterate over json array
    for (const auto valueRef : obj[areaListKey].toArray()) {
      const auto jsonArea = valueRef.toObject();

      // check if area type key is present
      if (jsonArea.contains(GeoArea::areaTypeKey) &&
          jsonArea[GeoArea::areaTypeKey].isString()) {

        // load MeasurementArea
        if (jsonArea[GeoArea::areaTypeKey].toString() ==
328
            MeasurementArea::nameString) {
329 330 331 332 333 334 335 336 337

          auto area = getGeoArea<MeasurementArea *>(_areaList);

          if (area == nullptr) {

            auto area = new MeasurementArea(this);
            QString e;
            if (area->loadFromJson(jsonArea, e)) {
              this->insert(area);
338 339
            } else {
              returnValue = false;
340 341 342
              errorString.append(e);
              errorString.append("\n");
              area->deleteLater();
343
            }
344 345 346 347
          } else {
            returnValue = false;
            errorString.append(
                tr("Multiple Measurement Areas detected. Area was ignored."));
348
          }
349 350
        }
        // load SafeArea
351 352
        else if (jsonArea[GeoArea::areaTypeKey].toString() ==
                 SafeArea::nameString) {
353 354 355 356 357 358 359 360

          auto area = getGeoArea<SafeArea *>(_areaList);
          if (area == nullptr) {

            auto area = new SafeArea(this);
            QString e;
            if (area->loadFromJson(jsonArea, e)) {
              this->insert(area);
361 362
            } else {
              returnValue = false;
363 364 365
              errorString.append(e);
              errorString.append("\n");
              area->deleteLater();
366
            }
367
          } else {
368
            returnValue = false;
369 370
            errorString.append(
                tr("Multiple Safe Areas detected. Area was ignored."));
371 372
          }
        }
373
        // unknown area
374 375
        else {
          returnValue = false;
376 377
          errorString.append(tr("Unknown area type: ") +
                             jsonArea[GeoArea::areaTypeKey].toString());
378
        }
379 380 381 382 383
      } else {
        // GeoArea::areaTypeKey missing
        returnValue = false;
        errorString.append(
            "Area type key missing, not able to determine area type.\n");
384 385
      }
    }
386
  } else {
387
    // AreaList missing
388 389
    returnValue = false;
    errorString.append("Not able to load areas.\n");
390 391 392
  }

  // load origin
393 394
  if (obj.contains(originKey) && obj[originKey].isObject()) {
    QGeoCoordinate origin;
395
    QString e;
396 397
    if (JsonHelper::loadGeoCoordinate(obj[originKey], false, origin, e)) {
      _origin = origin;
398 399 400 401
    }
  }

  return returnValue;
402 403
}

404 405 406 407
bool AreaData::save(QJsonObject &obj) {
  QJsonObject temp;

  QJsonValue jsonOrigin;
408
  JsonHelper::saveGeoCoordinate(_origin, false, jsonOrigin);
409
  temp[originKey] = jsonOrigin;
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426

  QJsonArray jsonAreaList;
  for (int i = 0; i < _areaList.count(); ++i) {
    auto qobj = _areaList[i];
    auto area = qobject_cast<GeoArea *>(qobj);
    QJsonObject jsonArea;
    if (area->saveToJson(jsonArea)) {
      jsonAreaList.append(jsonArea);
    } else {
      qDebug(AreaDataLog) << "save(): not able to save area: "
                          << area->objectName();
      return false;
    }
  }
  temp[areaListKey] = jsonAreaList;

  obj = std::move(temp);
427 428 429
  return true;
}

430
void AreaData::_setOrigin(const QGeoCoordinate &origin) {
431 432 433 434 435
  if (this->_origin != origin) {
    this->_origin = origin;
    emit originChanged();
  }
}
436

437
void AreaData::_processError(const QString &str, bool showError) {
438 439
  this->_errorString = str;
  emit error();
440
  if (showError) {
441 442
    qgcApp()->informationMessageBoxOnMainThread(tr("Area Editor"),
                                                this->errorString());
443
  }
444
}
445

446
bool AreaData::_areasCorrect(bool showError) {
447 448
  // Check if areas are correct.
  for (int i = 0; i < _areaList.count(); ++i) {
449
    auto *area = _areaList.value<GeoArea *>(i);
450
    if (!area->isCorrect()) {
451
      _processError(area->errorString(), showError);
452
      return false;
453 454 455
    }
  }

456 457 458
  return true;
}

459 460
bool AreaData::_getAreas(MeasurementArea **measurementArea, SafeArea **safeArea,
                         bool showError) {
461 462 463
  *measurementArea = getGeoArea<MeasurementArea *>(_areaList);
  if (*measurementArea == nullptr) {
    _processError(
464
        tr("Measurement Area is missing. Please define a Measurement Area."),
465
        showError);
466 467 468 469
    return false;
  }
  *safeArea = getGeoArea<SafeArea *>(_areaList);
  if (*safeArea == nullptr) {
470
    _processError(tr("Safe Area is missing. Please define a Safe Area."),
471
                  showError);
472 473 474 475
    return false;
  }

  return true;
476
}
477 478 479 480 481 482 483 484 485

void AreaData::_updateOrigin() {
  auto *measurementArea = getGeoArea<MeasurementArea *>(_areaList);
  if (measurementArea != nullptr) {
    _setOrigin(measurementArea->center());
  }
}

QString AreaData::errorString() const { return _errorString; }