/** * @RDE_HEADER@ * $Id: ppm_msdl_edit.h 52239 2020-10-05 11:33:12Z thoden $ * $HeadURL: svn://192.168.133.21/TDS/branches/product/features/3dws_devel/Lts/Plugins/ParticipantManagement/Gui/MSDL/ppm_msdl_edit.h $ * $Author: thoden $ * Created by Hannes Fritsche, ESZS, 2024-08-05 */ #include "cds_create_simulation_dialog.h" #include "ui_cds_create_simulation_dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cds_simulation_dialog.h" #include "cds_plan_creator.h" #include "cds_pdf_creator.h" #include #include #include "cds_calculations.h" namespace CollateralDamageSimulation { CreateSimulationDialog::CreateSimulationDialog(QWidget *parent) : LtsDialog(parent, LtsWidgets::ButtonCombination::OkApplyCancel) , m_ui(new Ui::CreateSimulationDialog) { m_ui->setupUi(this); m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->hide(); m_buttonBox->getButton(LtsWidgets::DialogButton::Apply)->setText(tr("Create")); setOnlineHelpFilename(QStringLiteral("CDS_Create_Simulation_Dialog")); } CreateSimulationDialog::~CreateSimulationDialog() { if (m_simulationDialog) { m_simulationDialog->closeRequest(); } } bool CreateSimulationDialog::init(LtsSession::LtsSession *session ) { if (session == nullptr) return false; m_session = session; if (!m_session->getDtc(m_ismDtc)) { ERROR_LOG(m_logger) << " failed to get ismDtc"; return false; } if (!m_session->getDtc(m_flmDtc)) { ERROR_LOG(m_logger) << " failed to get flmDtc"; return false; } if (!m_session->getDtc(m_ppmDtc)) { ERROR_LOG(m_logger) << " failed to get ppmDtc"; return false; } if (!m_session->getDtc(m_pdmDtc)) { ERROR_LOG(m_logger) << " failed to get pdmDtc"; return false; } if (!m_session->getForeignGuiInterface(m_sitMapGuiIfc)) return false; if (!m_session->getForeignGuiInterface(m_pdmCbx)) return false; if (!m_planCreator) m_planCreator = new PlanCreator(this); std::shared_ptr flmFtc; if (!m_session->getFtc(flmFtc)) { ERROR_LOG(m_logger) << " failed to get flmFtc"; return false; } m_planCreator->init(m_ismDtc, m_ppmDtc, flmFtc); if (!m_pdfCreator) m_pdfCreator = new PdfCreator(this); m_pdfCreator->init(m_ismDtc, m_ppmDtc, flmFtc); m_session = session; fillCharges(); connect(m_ui->pbPosition, &QPushButton::clicked, this, &CreateSimulationDialog::onPositionClicked, Qt::UniqueConnection); connect(m_ui->pbClear, &QPushButton::clicked, m_ui->positionEdit, &LtsCoordWidget::LtsCoordWidget::clear, Qt::UniqueConnection); connect(m_ui->cbChargeTypes, qOverload(&LtsWidgets::LtsComboBox::currentIndexChanged), this, &CreateSimulationDialog::onChargeTypeChanged, Qt::UniqueConnection); connect(m_ui->cbIgniterType, qOverload(&LtsWidgets::LtsComboBox::currentIndexChanged), this, &CreateSimulationDialog::onIgniterTypeChanged, Qt::UniqueConnection); return true; } void CreateSimulationDialog::retranslate() { translate(m_ui, tr("Create Simulation")); m_uiRequirements->clear(); m_uiRequirements->addRequirement( { m_ui->lblName, [this]() { return isNameValid(m_ui->leName->text()); } } ); m_uiRequirements->addRequirement( { m_ui->lblPosition, [this]() { return !m_ui->positionEdit->coordinate().isNull(); } } ); } void CreateSimulationDialog::onChargeTypeChanged(int index) { Q_UNUSED(index); // update comboBox IgniterType: m_pdmCbx->fillIgniterComboBox(m_ui->cbIgniterType, m_ui->cbChargeTypes->currentData().toInt()); drawDetonationArea(); } void CreateSimulationDialog::onIgniterTypeChanged(int index) { Q_UNUSED(index); drawDetonationArea(); } void CreateSimulationDialog::onPositionClicked() { if (!has2DMap()) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("no")); return; } m_sitMapGuiIfc->getCoordinate(this, SLOT(onCoordinate(const QPointF &)), nullptr); } void CreateSimulationDialog::onCoordinate(const QPointF &coordinate) { m_ui->positionEdit->setCoordinate(coordinate); drawDetonationArea(); } bool CreateSimulationDialog::apply() { const int folderId = getFolderId(); const QPointF impactPosition = m_ui->positionEdit->coordinate(); const QPolygonF detonationCircle = AreaEffectSimulation::Polygons::circle(impactPosition, getCurrentImpactRadius()); const QVector participantIdsInRange = m_ppmDtc->getParticipantsByPolygon(detonationCircle, false, false); QVector buildingIdsInRange = m_ismDtc->getBuildingByPolygon(m_session->exerciseId(), detonationCircle, false, true); const QVector undergroundBuildingIds = Calculations::getUndergroundBuildingIds(buildingIdsInRange, m_ismDtc); // remove the underground buildings for (int undergroundBuildingId : undergroundBuildingIds) buildingIdsInRange.removeAll(undergroundBuildingId); buildingIdsInRange.squeeze(); const double lethalRadius = getLethalRadius(); const double nonLethalRadius = getNonLethalRadius(); const QHash buildingFootprints = Calculations::getBuildingFootprints(buildingIdsInRange, m_ismDtc, impactPosition); const QVector directlyHitBuildingIds = Calculations::getDirectlyHitBuildings(buildingFootprints, impactPosition); const QVector protectionAreas = Calculations::getProtectionAreas(buildingFootprints, nonLethalRadius > lethalRadius ? nonLethalRadius : lethalRadius, impactPosition); Calculations::ParticipantProtectionInfo participantProtectionInfo = Calculations::getParticipantProtectionInfo(participantIdsInRange, protectionAreas, undergroundBuildingIds, directlyHitBuildingIds, buildingFootprints, m_ppmDtc, m_ismDtc); if (!m_simulationDialog) { m_simulationDialog = new SimulationDialog(this); m_simulationDialog->init(directlyHitBuildingIds, participantProtectionInfo.withoutProtection, participantProtectionInfo.withProtection + participantProtectionInfo.participantsInUnderground, m_ismDtc, m_ppmDtc); addDialog(m_simulationDialog, menuGroup(), QLatin1String("Collateral Damage Simulation"), LtsWidgets::LayoutArea::eDialogTab); connect(m_simulationDialog, &SimulationDialog::destroyed, this, [this]() {m_simulationDialog = nullptr; }); } else { m_simulationDialog->init(directlyHitBuildingIds, participantProtectionInfo.withoutProtection, participantProtectionInfo.withProtection + participantProtectionInfo.participantsInUnderground, m_ismDtc, m_ppmDtc); focusDialog(menuGroup(), QLatin1String("Collateral Damage Simulation"), LtsWidgets::LayoutArea::eDialogTab); } if (!m_planCreator) WARNING_LOG(m_logger) << "Plan Creator not present --> not possible to create a tactical plan."; else { m_planCreator->clear(); m_planCreator->addProtectionAreas(protectionAreas); m_planCreator->addBuildings(buildingFootprints, directlyHitBuildingIds); m_planCreator->addDetonationArea(impactPosition, lethalRadius, nonLethalRadius); m_planCreator->addParticipantsWithoutProtection(participantProtectionInfo.withoutProtection); m_planCreator->addParticipantsWithProtection(participantProtectionInfo.withProtection); m_planCreator->addParticipantsInUnderground(participantProtectionInfo.participantsInUnderground); m_planCreator->save(m_ui->leName->text(), folderId, m_session->exerciseId()); } if (!m_pdfCreator) WARNING_LOG(m_logger) << "PDF Creator not present --> not possible to create PDF file."; else { QString positionText = LtsCoordTrans::mgrsFromGeographic(impactPosition.x(), impactPosition.y()); m_pdfCreator->clear(); m_pdfCreator->addInformation(m_ui->leName->text(), m_ui->cbChargeTypes->currentText(), m_ui->cbIgniterType->currentText(), positionText); m_pdfCreator->addParticipantsWithoutProtection(participantProtectionInfo.withoutProtection); m_pdfCreator->addParticipantsWithProtection(participantProtectionInfo.withProtection); m_pdfCreator->addBuildings(directlyHitBuildingIds); m_pdfCreator->save(m_ui->leName->text(), folderId, m_session->exerciseId()); } return true; } bool CreateSimulationDialog::has2DMap() { if (!m_sitMapGuiIfc || !m_session) { ERROR_LOG(m_logger) << "Not possible to draw on map without sitMapGuiIfc or session."; return false; } if (m_sitMapGuiIfc->mapMode() != SituationMap::MapMode::MapMode_2D) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("Only available in 2D mode")); return false; } return true; } void CreateSimulationDialog::drawDetonationArea() { if (!has2DMap()) return; const QPointF centerCoordinate = m_ui->positionEdit->coordinate(); if (centerCoordinate == QPointF()) { DEBUG_LOG(m_logger) << "Not possible to draw detonation area. No position given."; return; } QVector previewCircleDataVec; SituationMap::ForeignGuiInterface::PreviewCircleData circleData; previewCircleDataVec.reserve(3); circleData.centerGeo = centerCoordinate; // detonation point circleData.radius = 1.0; circleData.fillColor = Qt::black; circleData.lineWidth = 1; previewCircleDataVec.append(circleData); // lethal radius circleData.radius = getLethalRadius(); circleData.fillPattern = SituationMap::MapFillPattern::Null; circleData.lineWidth = 3; previewCircleDataVec.append(circleData); // nonlethal radius const double nonLethalRadius = getNonLethalRadius(); if (nonLethalRadius > 0.0) { circleData.radius = nonLethalRadius; circleData.linePattern = SituationMap::MapLinePattern::Dash; previewCircleDataVec.append(circleData); } else previewCircleDataVec.squeeze(); m_sitMapGuiIfc->displayPreviewObjects(this, previewCircleDataVec); } void CreateSimulationDialog::fillCharges() { m_ui->cbChargeTypes->clear(); if (!m_pdmDtc) { ERROR_LOG(m_logger) << "no m_pdmDtc"; return; } using namespace ProjectDataManagement; const QVector gearClasses = { GearClass_def::AirSupport, GearClass_def::Artillery, GearClass_def::Mortar }; for (GearClass_def::type currentGearClass : gearClasses) { const QVector &chargeTypes = m_pdmDtc->chargesForGearClass(currentGearClass); for (const ChargeType ¤tChargeType : chargeTypes) { const QJsonDocument doc(QJsonDocument::fromJson(QByteArray::fromRawData(currentChargeType.properties().c_str(), currentChargeType.properties().size()))); const QJsonObject obj(doc.object()); if (!obj.contains(QLatin1String("nasatId"))) continue; m_ui->cbChargeTypes->addItem(QString::fromStdString(currentChargeType.name()), currentChargeType.id()); } } // update comboBox IgniterType: m_pdmCbx->fillIgniterComboBox(m_ui->cbIgniterType, m_ui->cbChargeTypes->currentData().toInt()); } double CreateSimulationDialog::getCurrentImpactRadius() const { const int chargeTypeId = m_ui->cbChargeTypes->currentData().toInt(); const int igniterTypeId = m_ui->cbIgniterType->currentData().toInt(); double impactRadius = m_pdmDtc->getChargeTypeIgniterTypeIgniterFunction(chargeTypeId, igniterTypeId, 0).impactRadius(); DEBUG_LOG(m_logger) << "got impact radius " << impactRadius << " for current charge type " << chargeTypeId << " and igniterTypeId " << igniterTypeId; return impactRadius; } double CreateSimulationDialog::getLethalRadius() const { const int currentChargeTypeId = m_ui->cbChargeTypes->currentData().toInt(); const ProjectDataManagement::ChargeType ¤tChargeType = m_pdmDtc->chargeType(currentChargeTypeId); const QJsonObject properties = QJsonDocument::fromJson(QByteArray::fromStdString(currentChargeType.properties())).object(); const QString keyImpactRadius = QStringLiteral("impactRadius"); const QString keyCasEffects = QStringLiteral("casEffects"); double lethalRadius = DBL_MAX; if (properties.contains(keyCasEffects)) { const QJsonArray casEffects = properties.value(keyCasEffects).toArray(); for (const QJsonValue ¤tCasEffect : casEffects) { QJsonObject currentCasEffectObj = currentCasEffect.toObject(); double tmpRadius = currentCasEffectObj.value(keyImpactRadius).toDouble(); if (tmpRadius < lethalRadius) lethalRadius = tmpRadius; } } else lethalRadius = properties.value(keyImpactRadius).toDouble(); return lethalRadius; } double CreateSimulationDialog::getNonLethalRadius() const { const int currentChargeTypeId = m_ui->cbChargeTypes->currentData().toInt(); const ProjectDataManagement::ChargeType ¤tChargeType = m_pdmDtc->chargeType(currentChargeTypeId); const QJsonObject properties = QJsonDocument::fromJson(QByteArray::fromStdString(currentChargeType.properties())).object(); const QString keyImpactRadius = QStringLiteral("impactRadius"); const QString keyCasEffects = QStringLiteral("casEffects"); double nonLethalRadius = 0.0; if (!properties.contains(keyCasEffects)) return 0.0; const QJsonArray casEffects = properties.value(keyCasEffects).toArray(); if (casEffects.size() < 2) return 0.0; for (const QJsonValue ¤tCasEffect : casEffects) { QJsonObject currentCasEffectObj = currentCasEffect.toObject(); double tmpRadius = currentCasEffectObj.value(keyImpactRadius).toDouble(); if (tmpRadius > nonLethalRadius) nonLethalRadius = tmpRadius; } return nonLethalRadius; } int CreateSimulationDialog::getFolderId() const { int exeFolderId = m_flmDtc->getExerciseFolderId(m_session->exerciseId()); return m_flmDtc->findFolder(exeFolderId, QStringLiteral("BDA")); } bool CreateSimulationDialog::isNameValid(const QString &name) const { if (name.isEmpty()) return false; QStringList fileNames; const int folderId = getFolderId(); m_flmDtc->getAllFiles(FileManagement::FileType::Pdf, folderId, fileNames); if (fileNames.contains(name)) return false; fileNames.clear(); m_flmDtc->getAllFiles(FileManagement::FileType::TacticalPlan, folderId, fileNames); if (fileNames.contains(name)) return false; return true; } } //namespace CollateralDamageSimulation