452 lines
15 KiB
C++
Executable File
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 ¤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
|