Files
qt5-legatus-legacy/Lts/Plugins/CollateralDamageSimulation/Gui/CreateSimulation/cds_create_simulation_dialog.cpp
T
2026-08-13 18:33:10 +02:00

452 lines
15 KiB
C++
Executable File

/**
* @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 <Plugins/SituationMapPlugin/situation_map_gui_plugin_interface.h>
#include <Plugins/ProjectDataManagement/pdm_gui_plugin_interface.h>
#include <LtsSession/lts_session.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include <PluginInterfaces/gui_plugin_interface.h>
#include <PluginInterfaces/data_plugin_interface.h>
#include <FileManagement/Data/flm_data_plugin.h>
#include <InfrastructureManagement/Data/ism_data_plugin.h>
#include <ParticipantManagement/Data/ppm_data_plugin.h>
#include <ProjectDataManagement/Data/pdm_data_plugin.h>
#include <ProjectDataManagement/pdm_translation.h>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <AreaEffectSimulation/Utils/polygons.h>
#include "cds_simulation_dialog.h"
#include "cds_plan_creator.h"
#include "cds_pdf_creator.h"
#include <LtsCoordTrans/lts_coord_trans.h>
#include <float.h>
#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<InfrastructureManagement::DataInterface>(m_ismDtc))
{
ERROR_LOG(m_logger) << " failed to get ismDtc";
return false;
}
if (!m_session->getDtc<FileManagement::DataInterface>(m_flmDtc))
{
ERROR_LOG(m_logger) << " failed to get flmDtc";
return false;
}
if (!m_session->getDtc<ParticipantManagement::DataInterface>(m_ppmDtc))
{
ERROR_LOG(m_logger) << " failed to get ppmDtc";
return false;
}
if (!m_session->getDtc<ProjectDataManagement::DataInterface>(m_pdmDtc))
{
ERROR_LOG(m_logger) << " failed to get pdmDtc";
return false;
}
if (!m_session->getForeignGuiInterface<SituationMap::ForeignGuiInterface>(m_sitMapGuiIfc))
return false;
if (!m_session->getForeignGuiInterface<ProjectDataManagement::ForeignGuiInterface>(m_pdmCbx))
return false;
if (!m_planCreator)
m_planCreator = new PlanCreator(this);
std::shared_ptr<FileManagement::Interface::Ftc> flmFtc;
if (!m_session->getFtc<FileManagement::DataInterface>(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<int>(&LtsWidgets::LtsComboBox::currentIndexChanged),
this, &CreateSimulationDialog::onChargeTypeChanged, Qt::UniqueConnection);
connect(m_ui->cbIgniterType, qOverload<int>(&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<int> participantIdsInRange = m_ppmDtc->getParticipantsByPolygon(detonationCircle, false, false);
QVector<int> buildingIdsInRange = m_ismDtc->getBuildingByPolygon(m_session->exerciseId(), detonationCircle, false, true);
const QVector<int> 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<int, QPolygonF> buildingFootprints = Calculations::getBuildingFootprints(buildingIdsInRange, m_ismDtc, impactPosition);
const QVector<int> directlyHitBuildingIds = Calculations::getDirectlyHitBuildings(buildingFootprints, impactPosition);
const QVector<QPolygonF> 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<SituationMap::ForeignGuiInterface::PreviewCircleData> 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<GearClass_def::type> gearClasses = { GearClass_def::AirSupport,
GearClass_def::Artillery,
GearClass_def::Mortar };
for (GearClass_def::type currentGearClass : gearClasses)
{
const QVector<ChargeType> &chargeTypes = m_pdmDtc->chargesForGearClass(currentGearClass);
for (const ChargeType &currentChargeType : 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 &currentChargeType = 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 &currentCasEffect : 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 &currentChargeType = 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 &currentCasEffect : 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