Files
qt5-legatus-legacy/Lts/Plugins/AreaEffectSimulation/Gui/Explosion/aes_explosions_edit.cpp
T
2026-08-13 18:33:10 +02:00

2733 lines
108 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @RDE_HEADER@
* AreaEffectSimulation - Explosions Edit Dialog
* $Id$
* $HeadURL$
* $Author$
* Created by Steve Tougnia, SLTE, 2023-09-05
*/
#include "aes_explosions_edit.h"
#include "ui_aes_explosions_edit.h"
#include "aes_rocket_utility.h"
#include <LtsSession/lts_session_data.h>
#include <PluginInterfaces/gui_plugin_interface.h>
#include <Plugins/AreaEffectSimulation/aes_data_plugin_interface.h>
#include <Plugins/SituationMapPlugin/situation_map_gui_plugin_interface.h>
#include <Services/AreaEffectSimulation/Utils/aes_helper.h>
#include <Services/AreaEffectSimulation/Utils/polygons.h>
#include <LtsUtilsSettings/settings.h>
#include <Services/AreaEffectSimulation/settings.h>
#include <Plugins/AdvancedSymbology/asb_data_plugin_interface.h>
#include <Plugins/ParticipantManagement/Gui/ParticipantSelection/participant_selection.h>
#include <AreaEffectSimulation/aes_translation.h>
#include <ProjectDataManagement/pdm_translation.h>
#include <Trajectory/trajectory_generator.h>
#include <Trajectory/trajectory_helper.h>
#include <AssetManagement/asm_data_plugin_interface.h>
#include <ExerciseManagement/exm_data_plugin_interface.h>
#include <LtsElevation/lts_elevation_manager.h>
#include <MathUtils/simple_helper.h>
#include <QButtonGroup>
#include <QDateTime>
#include <QHash>
#include <QJsonDocument>
#include <QSpinBox>
#include <QStandardItem>
#include <QTimer>
namespace AreaEffectSimulation
{
double ExplosionsEdit::s_minWidth;
double ExplosionsEdit::s_maxWidth;
double ExplosionsEdit::s_stepWidth;
double ExplosionsEdit::s_minHeight;
double ExplosionsEdit::s_maxHeight;
double ExplosionsEdit::s_stepHeight;
double ExplosionsEdit::s_minRadius;
double ExplosionsEdit::s_maxRadius;
double ExplosionsEdit::s_stepRadius;
int ExplosionsEdit::s_maxChargeUsage;
double ExplosionsEdit::s_minAltitude;
double ExplosionsEdit::s_maxAltitude;
qint64 ExplosionsEdit::s_maxDurationSeconds;
int ExplosionsEdit::s_dropAngleDown;
int ExplosionsEdit::s_dropAngleUp;
static int s_timeUntilMinimumSec = 10; // sec timeUntilSec
static const char *const VALUE_VALID_PROPERTY_NAME = "VALUE_VALID";
static const char *const LAST_VALUE = "LAST_VALUE";
static const int s_laserPrecisionChargeTypeId = 376; // Jira Task E01562-366
QPair<int, int> getMinMaxDistance(ProjectDataManagement::GearClass_def::type gearClass, int chargeTypeId)
{
// [GUEZE-2458] Minimaler und maximaler Abstand Feuereinheit Wirkeld Steilfeuer
// Es soll zwischen dem Mittelpunkt der Feuereinheit und dem Mittelpunkt des zu bewirkenden Wirkfeldes folgender Abstand als Mindestabstand:
// Mörser: 800m
// Rakete: 15000m
// Haubitze: 3500m
// Minenrakete: 10000m
// Und einem maximalen Abstand von:
// Mörser: 7800m
// Rakete: 90000m
// Haubitze: 40000m
// Minenrakete: 38400m
static QHash<int, QPair<int, int>> contraintsDistance;
if (contraintsDistance.isEmpty())
{
contraintsDistance.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Artillery), {3500, 40000}); // Haubitze
contraintsDistance.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Mortar), {800, 7800}); // Mörser
// contraints.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Mortar), {15000, 90000}); // Rakete
// contraints.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Mortar), {10000, 38400}); // Minenrakete
}
static QHash<int, QPair<int, int>> contraintsDistanceVulcano;
if (contraintsDistanceVulcano.isEmpty())
{
contraintsDistanceVulcano.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Artillery), {10000, 70000}); // Haubitze
contraintsDistanceVulcano.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Rocket), {10000, 70000}); // Rakete
}
if (chargeTypeIdIsVulcano(chargeTypeId))
{
auto it = contraintsDistanceVulcano.find(static_cast<int>(gearClass));
if (it == contraintsDistanceVulcano.end())
return {0, 100000};
return *it;
}
// chargeTypeId Is _NOT_ Vulcano
auto it = contraintsDistance.find(static_cast<int>(gearClass));
if (it == contraintsDistance.end())
return {0, 100000};
return *it;
}
int getMinDistance(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId) { return getMinMaxDistance(gearClass, chargeTypeId).first; }
int getMaxDistance(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId) { return getMinMaxDistance(gearClass, chargeTypeId).second; }
QPair<int, int> getMinMaxFlightDurationSec(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId)
{
// [GUEZE-2460] Grenzwert Flugdauer Steilfeuer
// Haubitze: von 6 bis 150 Sekunden
// Rakete: von 15 bis 200 Sekunden
// Mörser: Von 20 bis 60 Sekunden
// Minenrakete: von 40 bis 200 Sekunden
static QHash<int, QPair<int, int>> contraintsFlightDuration;
if (contraintsFlightDuration.isEmpty())
{
contraintsFlightDuration.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Artillery), {6, 150}); // Haubitze
contraintsFlightDuration.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Mortar), {20, 60}); // Mörser
}
static QHash<int, QPair<int, int>> contraintsFlightDurationVulcano;
if (contraintsFlightDurationVulcano.isEmpty())
{
contraintsFlightDurationVulcano.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Artillery), {0, 999}); // Haubitze
contraintsFlightDurationVulcano.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Rocket), {0, 999}); // Rakete
}
if (chargeTypeIdIsVulcano(chargeTypeId))
{
auto it = contraintsFlightDurationVulcano.find(static_cast<int>(gearClass));
if (it == contraintsFlightDurationVulcano.end())
return {0, 100000};
return *it;
}
// chargeTypeId Is _NOT_ Vulcano
auto it = contraintsFlightDuration.find(static_cast<int>(gearClass));
if (it == contraintsFlightDuration.end())
return {10, 100};
return *it;
}
int getMinFlightDurationSec(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId)
{
return getMinMaxFlightDurationSec(gearClass, chargeTypeId).first;
}
int getMaxFlightDurationSec(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId)
{
return getMinMaxFlightDurationSec(gearClass, chargeTypeId).second;
}
QPair<int, int> getMinMaxPeakHeight(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId)
{
// [GUEZE-2460] Grenzwert Flugdauer Steilfeuer
// Haubitze: von 250 bis 15000m
// Rakete: von 100 bis 27000m
// Mörser: von 500 bis 3000m
// Minenrakete: von 900 bis 18000m
static QHash<int, QPair<int, int>> contraintsPeak;
if (contraintsPeak.isEmpty())
{
contraintsPeak.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Artillery), {250, 15000}); // Haubitze
contraintsPeak.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Mortar), {500, 3000}); // Mörser
}
static QHash<int, QPair<int, int>> contraintsPeakVulcano;
if (contraintsPeakVulcano.isEmpty())
{
contraintsPeakVulcano.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Artillery), {1000, 37500}); // Haubitze
contraintsPeakVulcano.insert(static_cast<int>(ProjectDataManagement::GearClass_def::Rocket), {1000, 37500}); // Rakete
}
if (chargeTypeIdIsVulcano(chargeTypeId))
{
auto it = contraintsPeakVulcano.find(static_cast<int>(gearClass));
if (it == contraintsPeakVulcano.end())
return {0, 100000};
return *it;
}
// chargeTypeId Is _NOT_ Vulcano
auto it = contraintsPeak.find(static_cast<int>(gearClass));
if (it == contraintsPeak.end())
return {100, 10000};
return *it;
}
int getMinPeakHeight(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId) { return getMinMaxPeakHeight(gearClass, chargeTypeId).first; }
int getMaxPeakHeight(ProjectDataManagement::GearClass::inner_enum gearClass, int chargeTypeId) { return getMinMaxPeakHeight(gearClass, chargeTypeId).second; }
/*!
* \brief ExplosionsEdit::ExplosionsEdit
* \param menuGroup
* \param parent
*/
QString ExplosionsEdit::toString(ExplosionsEdit::FlighDurationOrPeakHeight val)
{
switch (val)
{
case ExplosionsEdit::FlighDurationOrPeakHeight::FDPH_flightDuration:
return QStringLiteral("FDPH_flightDuration");
break;
case ExplosionsEdit::FlighDurationOrPeakHeight::FDPH_peakHeight:
return QStringLiteral("FDPH_peakHeight");
break;
default:
return QStringLiteral("PHFD_Unknow");
break;
}
return QStringLiteral("PHFD_Unknow");
}
////////////////////////////////////////////////////////////////////////////////
// class ExplosionsEdit
////////////////////////////////////////////////////////////////////////////////
ExplosionsEdit::ExplosionsEdit(const QString &menuGroup, QWidget *parent)
: BaseEdit(menuGroup, QStringLiteral("ExplosionsEdit"), parent)
, m_ui(new Ui::ExplosionsEdit)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " START";
m_ui->setupUi(this);
s_minWidth = 5;
s_maxWidth = 1275;
s_stepWidth = 5;
s_minHeight = 5;
s_maxHeight = 1275;
s_minRadius = 5;
s_maxRadius = 255;
s_stepRadius = 1;
s_stepHeight = 5;
s_minAltitude = 0;
s_maxAltitude = 1000;
s_maxChargeUsage = 1000;
s_maxDurationSeconds = 5940; // 99 Minutes
s_dropAngleDown = 30;
s_dropAngleUp = 70;
m_geometrieButtonGroup = new QButtonGroup(this);
m_geometrieButtonGroup->addButton(m_ui->geometrieAimPointBtn, static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint));
m_geometrieButtonGroup->addButton(m_ui->geometrieAreaBtn, static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgArea));
m_geometrieButtonGroup->addButton(m_ui->geometrieLineBtn, static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgLine));
m_geometrieButtonGroup->addButton(m_ui->geometriePointBtn, static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgPoint));
m_geometrieButtonGroup->addButton(m_ui->geometrieRadialBtn, static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgRadial));
m_geometrieButtonGroup->setExclusive(true);
m_gearClassFilter = {ProjectDataManagement::GearClass::Artillery, ProjectDataManagement::GearClass::Mortar};
LtsUtils::ProjectSettings *ps = LtsUtils::ProjectSettings::getInstance();
const bool enableRocket = ps->value(QStringLiteral("aes"), QStringLiteral("enableRocket"), false).toBool();
if (enableRocket)
m_gearClassFilter += ProjectDataManagement::GearClass::Rocket;
m_uiRequirements->addRequirement({m_ui->lblPosition, [this]() { return (!m_coord.isNull()); }});
m_uiRequirements->addRequirement({m_ui->lblRadiusBreite, [this]() { return (m_ui->sbDimensionX_BreiteRadius->value() >= s_minWidth); }});
m_uiRequirements->addRequirement({m_ui->lblDimensionY_Tiefe, [this]() { return (m_ui->sbDimensionY_Tiefe->value() >= s_minHeight); }});
// m_uiRequirements->addRequirement({m_ui->lblName, [this]() { return (!m_ui->leElement->text().isEmpty()); }});
m_uiRequirements->addRequirement({m_ui->lblZpNumber, [this]() { return (!m_ui->leZpNumber->text().isEmpty()); }});
m_uiRequirements->addRequirement({m_ui->lblChargeType, [this]() { return (!m_ui->cbChargeType->currentData().isNull()); }});
m_uiRequirements->addRequirement({m_ui->lblIgniter, [this]() { return (m_ui->cbIgniterType->currentData().toInt() >= 0); }});
m_uiRequirements->addRequirement({m_ui->lblParticipantUnits, [this]() { return (m_ui->cbParticipantUnits->styleSheet().isEmpty()); }}); // empty -> ok ; set -> invalid
m_uiRequirements->addRequirement({m_ui->lblAnnotationString, [this]() { return (m_temporaryData.m_annotationString4Dlg.isEmpty()); }}); // empty -> ok ; set -> invalid
m_uiRequirements->addRequirement({m_ui->lblTargetGeometrie,
[this]()
{
return (m_ui->geometrieAimPointBtn->isChecked() || m_ui->geometrieRadialBtn->isChecked() || m_ui->geometrieAreaBtn->isChecked()
|| m_ui->geometrieLineBtn->isChecked() || m_ui->geometriePointBtn->isChecked());
}});
// either no effect start is set or effect start is greater than request time
m_uiRequirements->addRequirement({m_ui->lblEffectStart,
[this]()
{
// Requirements are checked after data changes on DDS.
// So also, if data isn´t modifible anymore or becomes EffectState::Active...
// so don´t check if data isn't modifible anymore:
// DEBUG_LOG(m_logger) << "m_explosionClusterData " << m_explosionClusterData;
if (!m_explosionClusterData.isObjectModifiable())
return true;
bool bEffectStartValid = true;
// dteEffectStart: check if property exists. Initially the prop is NOT set.
// (partitionTime has to change first....)
if (m_ui->dteEffectStart->isCheckable() && m_ui->dteEffectStart->isChecked()
&& m_ui->dteEffectStart->property(VALUE_VALID_PROPERTY_NAME).isValid())
bEffectStartValid = m_ui->dteEffectStart->property(VALUE_VALID_PROPERTY_NAME).toBool();
return hasValidEffectStart(m_ui->dteEffectStart) && bEffectStartValid;
}});
m_uiRequirements->addRequirement({m_ui->lblActors, [this]() { return !m_ui->gbEffectMarkingInTa->isChecked() || (m_ui->cbActors->currentData().toInt() > 0); }});
// UI-Requirements seems to work on current tab only!?
// m_uiRequirements->addRequirement({m_ui->lblLcPrf, [this]() { return (m_ui->sbLcPrf->value() != m_ui->sbLcTermination->value()); }});
// m_uiRequirements->addRequirement({m_ui->lblLcTermination,[this]() { return (m_ui->sbLcPrf->value() != m_ui->sbLcTermination->value()); }});
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
// prefer new idClicked...
connect(m_geometrieButtonGroup, &QButtonGroup::idClicked, this, &ExplosionsEdit::onGeometrySelected);
#else
// ... but be compatible to older versions of qt:
connect(m_geometrieButtonGroup, QOverload<int>::of(&QButtonGroup::buttonClicked), this, &ExplosionsEdit::onGeometrySelected);
#endif // QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(m_ui->cbGearClass, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExplosionsEdit::onGearClassChanged);
connect(m_ui->cbParty, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExplosionsEdit::onPartyChanged);
connect(m_ui->cbChargeType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExplosionsEdit::onChargeTypeChanged);
connect(m_ui->cbIgniterType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExplosionsEdit::onIgniterTypeChanged);
connect(m_ui->cbFirePurpose, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExplosionsEdit::onFirePurposeChanged);
connect(m_ui->pbClear, &QPushButton::clicked, m_ui->positionEdit, &LtsCoordWidget::LtsCoordWidget::clear);
connect(m_ui->pbClear, &QPushButton::clicked, m_ui->sbHeight1SBox, &QSpinBox::clear);
connect(m_ui->pbClear, &QPushButton::clicked, m_ui->positionEdit_2, &LtsCoordWidget::LtsCoordWidget::clear);
connect(m_ui->pbClear, &QPushButton::clicked, m_ui->sbHeight2SBox, &QSpinBox::clear);
// limit impact to limit number of resulting trajectories.
// The range of dds_ids of the of the sample AesItemView (with AtExplosionClusterSingleTrajectory) is limited
// There is a single sample per each explosionNumber.
// See object_explosion_cluster.cpp
// GUEZ: Hein: max 120
// update: The maximum is now configurable via lts-cc.conf
m_ui->sbCannons->setMaximum(Settings::getMaxRoundsAreaWideFire()); // (max should be less than AreaEffectSimulation::s_maxTrajectoriesPerExplosionCluster);
// PositionEdit
connect(m_ui->positionEdit, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, this, &ExplosionsEdit::setCoord);
connect(m_ui->positionEdit, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, this, &ExplosionsEdit::onPositionChanged);
connect(m_ui->positionEdit, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, this, &ExplosionsEdit::onLineP1Changed);
connect(m_ui->positionEdit_2, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, this, &ExplosionsEdit::onLineP2Changed);
// Connect Breite (Radius) und Tiefe if m_geometry == Radial
connect(m_ui->sbDimensionX_BreiteRadius, QOverload<int>::of(&QSpinBox::valueChanged), this, &ExplosionsEdit::onWidthChanged);
// "Flugzeit" and "Gipfelhöhe" can not be set at the same time
connect(m_ui->sBox_flightDuration, QOverload<int>::of(&QSpinBox::valueChanged), this, &ExplosionsEdit::onFlightDurationChanged);
connect(m_ui->sBox_peakHeight, QOverload<int>::of(&QSpinBox::valueChanged), this, &ExplosionsEdit::onPeakHeigthChanged);
// Duration (Effect)
connect(m_ui->sbDurationSec, QOverload<int>::of(&QSpinBox::valueChanged), this, &ExplosionsEdit::onEffectDurationChanged);
// Aiming Axis (for line geometry)
connect(m_ui->sbAimingAxis, QOverload<int>::of(&QSpinBox::valueChanged), this, &ExplosionsEdit::onAimingAxisChanged);
// "Einschießen" is hidden for now
m_ui->lblFire->hide();
m_ui->einschlJaBtn->hide();
m_ui->einschlNoBtn->hide();
// "Wirkungsfeuer" is hidden for now
m_ui->lblFire_2->hide();
m_ui->wirkungJaBtn->hide();
m_ui->wirkungNoBtn->hide();
// Obere/Untere Winkelgruppen is hidden for now
m_ui->rbDropAngleDown->hide();
m_ui->rbDropAngleUp->hide();
m_ui->leElement->setMaxLength(30);
DEBUG_LOG(m_logger) << LOGFUNCTION << " END";
}
/*!
* \brief ExplosionsEdit::~ExplosionsEdit
*/
ExplosionsEdit::~ExplosionsEdit()
{
if (m_sitMapGuiIfc)
m_sitMapGuiIfc->removeObjectIndicator();
delete m_ui;
}
/*!
* \brief AreaEffectSimulation::ExplosionsEdit::init
* \param aesItemId
* \param session
* \return
*/
bool ExplosionsEdit::init(int aesItemId, bool copy, LtsSession::LtsSession *session)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " START";
if (!BaseEdit::init(aesItemId, session))
return false;
std::shared_ptr<EffectShowManagement::ForeignGuiComboboxInterface> esmActorCbx;
if (!m_session->getForeignGuiInterface(esmActorCbx))
return false;
if (!m_session->getForeignDataInterface<AdvancedSymbology::DataInterface>(m_asbIfc))
return false;
if (!session->getForeignGuiInterface(m_esmActorCbx))
return false;
// P _D_ M
if (!m_session->getDtc<ProjectDataManagement::DataInterface>(m_pdmDtc))
{
ERROR_LOG(m_logger) << " failed to get pdmDtc";
return false;
}
m_pdmDtc->waitForHistoricalDataChargeType(1000);
m_pdmDtc->waitForHistoricalDataGearType(1000);
m_pdmDtc->waitForHistoricalDataGearTypeChargeType(1000);
m_dtc->waitForHistoricalDataExplosionCluster(1000);
// P _P_ M
if (!m_session->getDtc<ParticipantManagement::DataInterface>(m_ppmDtc))
{
ERROR_LOG(m_logger) << " failed to get ppmDtc";
return false;
}
if (!m_session->getForeignGuiInterface<ProjectDataManagement::ForeignGuiInterface>(m_pdmCbx))
return false;
// A _E_ S
if (!m_session->getDtc<AreaEffectSimulation::DataInterface>(m_aesDtc))
{
ERROR_LOG(m_logger) << " failed to get aesDtc";
return false;
}
// initial adjustment:
m_explosionClusterData.explosionGeometry(AreaEffectSimulation::AesExplosionGeometry_def::EgArea);
onGeometrySelected(static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgArea));
// pay attention!!! firePurposeId vs purpose !!!
m_pdmCbx->fillFirePurposeComboBox(m_ui->cbFirePurpose);
onFirePurposeChanged();
fillGearClasses();
m_ui->dteEffectStart->setMinimumTimeMSec(m_session->partitionTime());
m_ui->dteEffectStart->setRetrieveCurrentTimeCallback(
[this]()
{
if (QGuiApplication::keyboardModifiers().testFlag(Qt::KeyboardModifier::ShiftModifier))
if (m_temporaryData.m_estimatedFlightDurationMsec > 0)
return m_session->partitionTime() + m_temporaryData.m_estimatedFlightDurationMsec + 10000;
else
return m_session->partitionTime();
else
return m_session->partitionTime();
});
// LtsUtils::ProjectSettings *ps = LtsUtils::ProjectSettings::getInstance();
// const bool enableEffectShowManagement = ps->value(QStringLiteral("esm"),
// QStringLiteral("enableEffectShowManagement")).toBool();
// m_ui->cbEffectMarkingInTa->setVisible(enableEffectShowManagement &&
// ((m_session->app() == LtsAppDescriptor::App::Situation ||
// m_session->app() == LtsAppDescriptor::App::SituationTablet) &&
// (!m_session->isReplayPartition())));
// onGearClassChanged will implicitly switchTo(FlighDurationOrPeakHeight)
onGearClassChanged();
fillCharges();
// // onChargeTypeChanged();
// onGearClassChanged will implicitly switchTo..
// switchTo(FlighDurationOrPeakHeight::FDPH_flightDuration);
if (m_sitMapGuiIfc)
connect(m_ui->pbPosition, &QPushButton::clicked, this, &ExplosionsEdit::getPosition);
else
m_ui->pbPosition->setEnabled(false);
m_ui->gbEffectMarkingInTa->setChecked(false);
if (aesItemId > 0)
{
const ExplosionCluster &exData = m_dtc->explosionCluster(aesItemId);
setExplosionClusterData(exData);
m_ui->gbEffectMarkingInTa->setChecked(exData.effectMarkingInTa());
if (copy)
{
m_aesItem.id(0);
m_aesItem.effectStateId(AreaEffectSimulation::AesEffectState_def::get_default());
m_explosionClusterData.id(m_aesItem.id());
m_explosionClusterData.effectStateId(m_aesItem.effectStateId());
}
}
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantChanged, this, &ExplosionsEdit::onPartyChanged, Qt::UniqueConnection);
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantPositionChanged, this, &ExplosionsEdit::onParticipantPositionChanged, Qt::UniqueConnection);
m_ui->cbActors->setEnabled(m_ui->gbEffectMarkingInTa->isChecked());
onFirePurposeChanged();
connect(m_ui->gbEffectMarkingInTa, &QGroupBox::toggled, m_ui->cbActors, &QComboBox::setEnabled);
connect(m_ui->gbEffectMarkingInTa, &QGroupBox::toggled, this, &ExplosionsEdit::onFirePurposeChanged);
connect(m_dtc.get(), &AreaEffectSimulation::Interface::Dtc::explosionClusterChanged, this, &ExplosionsEdit::onExplosionClusterChanged, Qt::UniqueConnection);
// cbParticipantUnits:
connect(m_ui->cbParticipantUnits, QOverload<int>::of(&LtsWidgets::LtsComboBox::currentIndexChanged), this, &ExplosionsEdit::onParticipantUnitChanged);
connect(m_ui->cbParticipantUnits, &LtsWidgets::LtsComboBox::signalEnterEvent, this, &ExplosionsEdit::onParticipantUnitEntered);
connect(m_ui->cbParticipantUnits, &LtsWidgets::LtsComboBox::signalLeaveEvent, this, &ExplosionsEdit::onParticipantUnitLeaved);
connect(m_ui->pbFire, &QPushButton::clicked, this, &ExplosionsEdit::onFireClicked);
connect(m_session, &LtsSession::LtsSession::partitionTimeChanged, this, &ExplosionsEdit::onPartitionTimeChanged, Qt::UniqueConnection);
setOnlineHelpFilename(QStringLiteral("Situation-Add_Steilfeuer"));
if (!m_elevationManager)
{
std::shared_ptr<AssetManagement::Interface::Dtc> asmDtc;
if (!m_session->getDtc<AssetManagement::DataInterface>(asmDtc))
{
WARNING_LOG(m_logger) << "getDtc for AssetManagement::DataInterface failed.";
}
std::shared_ptr<ExerciseManagement::Interface::Dtc> exmDtc;
if (!m_session->getDtc<ExerciseManagement::DataInterface>(exmDtc))
{
WARNING_LOG(m_logger) << "getDtc for ExerciseManagement::DataInterface failed.";
}
m_elevationManager = LtsElevation::LtsElevationManager::createInstance(m_session->exerciseId(), exmDtc, asmDtc);
}
m_initDone = true;
doCalculations();
RocketUtility::enaDisaWirkmittel(m_ui->cbGearClass, m_currentGeometry.underlying());
DEBUG_LOG(m_logger) << LOGFUNCTION << " END";
return true;
}
void ExplosionsEdit::setExplosionClusterData(const ExplosionCluster &ecData)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " START, ecData=" << ecData;
m_ui->leZpNumber->setText(QString::fromStdString(ecData.name()));
m_ui->leElement->setText(QString::fromStdString(ecData.element()));
m_ui->dteEffectStart->setMsecsSinceEpoch(ecData.effectStart().is_set() ? ecData.effectStart().get() : 0);
// Geometry/Area
const AreaEffectSimulation::AesExplosionGeometry_def::type geometryType = ecData.explosionGeometry().underlying();
switch (geometryType)
{
case AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint:
m_ui->geometrieAimPointBtn->setChecked(true);
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgPoint:
m_ui->geometriePointBtn->setChecked(true);
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgArea:
m_ui->geometrieAreaBtn->setChecked(true);
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgLine:
m_ui->geometrieLineBtn->setChecked(true);
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgRadial:
m_ui->geometrieRadialBtn->setChecked(true);
break;
default:
// empty
break;
} // switch
onGeometrySelected(static_cast<int>(geometryType)); // which in turn calls fillCharges();
m_ui->sbDurationSec->setValue(ecData.effectDuration() / 1000); // msecs -> mins
// 1st GearClass
{
QSignalBlocker locked(m_ui->cbGearClass);
ProjectDataManagement::GearClass gc = m_pdmDtc->gearClassFromChargeTypeId(ecData.chargeStruct().chargeTypeId());
m_ui->cbGearClass->setCurrentIndex(m_ui->cbGearClass->findData(static_cast<int>(gc.underlying())));
onGearClassChanged(); // update depending comboBox
}
// 2nd Party
{
QSignalBlocker locked(m_ui->cbParty);
if (ecData.partyId().is_set())
{
m_ui->cbParty->setCurrentIndex(m_ui->cbParty->findData(ecData.partyId().get()));
onPartyChanged(); // update depending comboBox
}
}
// 3rd Originator
{
if (ecData.participantId().is_set())
{
m_ui->cbParticipantUnits->setCurrentIndex(m_ui->cbParticipantUnits->findData(ecData.participantId().get()));
onParticipantUnitChanged({});
}
}
FlightCurve fc(-1, -1, -1, -1, -1, -1, -1, -1);
if (ecData.flightCurve().is_set())
fc = ecData.flightCurve().get();
if (fc.dropAngle() <= s_dropAngleDown)
{
m_ui->rbDropAngleDown->setChecked(true);
m_ui->rbDropAngleUp->setChecked(false);
}
else
{
m_ui->rbDropAngleDown->setChecked(false);
m_ui->rbDropAngleUp->setChecked(true);
}
m_ui->sbCannons->setValue(ecData.impactCount());
m_ui->cbChargeType->setCurrentIndex(m_ui->cbChargeType->findData(ecData.chargeStruct().chargeTypeId()));
m_ui->cbIgniterType->setCurrentIndex(m_ui->cbIgniterType->findData(ecData.chargeStruct().igniterTypeId()));
m_ui->cbFirePurpose->setCurrentIndex(m_ui->cbFirePurpose->findData(ecData.firePurposeId()));
m_ui->sbZog->setValue(ecData.tla()); // tla == zog
const AreaEffectSimulation::Area &areaRef = ecData.area();
const QPointF bottomLeft(areaRef.cornerBottomLeft().lon(), areaRef.cornerBottomLeft().lat());
const QPointF centerpoint = Polygons::bottomLeft2CenterPoint(bottomLeft, areaRef.width(), areaRef.height(), areaRef.rotation());
if (!bottomLeft.isNull())
{
m_ui->positionEdit->setCoordinate(centerpoint);
setCoord(centerpoint);
}
if (ecData.explosionGeometry().underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial)
{
m_ui->sbDimensionY_Tiefe->setValue(areaRef.height() / 2);
m_ui->sbDimensionX_BreiteRadius->setValue(areaRef.width() / 2);
m_ui->sbAimingAxis->setValue(0);
}
else
{
m_ui->sbDimensionY_Tiefe->setValue(areaRef.height());
m_ui->sbDimensionX_BreiteRadius->setValue(areaRef.width());
m_ui->sbAimingAxis->setValue(fmod((90.0 + areaRef.rotation()) * 6400 / 360, 6400));
}
if (!ecData.esmParticipantIds().empty())
{
m_ui->cbActors->setCurrentIndex(m_ui->cbActors->findData(ecData.esmParticipantIds().at(0)));
if (ecData.esmParticipantIds().size() > 1)
m_ui->cbActors_2->setCurrentIndex(m_ui->cbActors_2->findData(ecData.esmParticipantIds().at(1)));
}
// peakHeight | flightDuration
{
if (fc.requestedFlightDuration() > 0)
{
// m_ui->sBox_flightDuration->setValue(ecData.originalFlightDuration() / 1000.0);
switchTo(FlighDurationOrPeakHeight::FDPH_flightDuration);
setValueAccordingContraints(fc.requestedFlightDuration() / 1000.0, FlighDurationOrPeakHeight::FDPH_flightDuration);
}
if (fc.requestedRelativePeakHeight() > 0)
{
// m_ui->sBox_peakHeight->setValue(ecData.originalPeakHeight());
switchTo(FlighDurationOrPeakHeight::FDPH_peakHeight);
setValueAccordingContraints(fc.requestedRelativePeakHeight(), FlighDurationOrPeakHeight::FDPH_peakHeight);
}
LaserGuiding lg(0, 0, -1, std::numeric_limits<int>::min());
if (ecData.laserGuiding().is_set())
lg = ecData.laserGuiding().get();
m_ui->sbLcAttack_Prf->setValue(lg.lcAttack());
m_ui->sbLcTermination_Prf->setValue(lg.lcTermination());
// keep specialValueText for cloudBase in mind:
m_ui->spCloudBase->setValue(lg.cloudBase());
onExplosionClusterChanged(ecData, DataModification::eAdded);
}
DEBUG_LOG(m_logger) << LOGFUNCTION << " END, ecData=" << ecData;
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void ExplosionsEdit::onPartyChanged() { fillParticipantUnit(); }
void ExplosionsEdit::onChargeTypeChanged()
{
int lastChargeTypeId = -1;
QVariant lastVal = m_ui->cbChargeType->property(LAST_VALUE);
if (!lastVal.isNull())
lastChargeTypeId = lastVal.toInt();
const int chargeTypeId = m_ui->cbChargeType->currentData().toInt();
if (chargeTypeId <= 0)
{
WARNING_LOG(m_logger) << LOGFUNCTION << " chargeTypeId is invalid -> return. chargeTypeId = " << chargeTypeId;
return;
}
// update comboBox IgniterType:
m_pdmCbx->fillIgniterComboBox(m_ui->cbIgniterType, chargeTypeId);
handleLaserPrecisionChargeType(chargeTypeId); // Jira Task E01562-366
if (isGuiVulcano())
switchTo(FlighDurationOrPeakHeight::FDPH_peakHeight);
FlighDurationOrPeakHeight phfd = FlighDurationOrPeakHeight::FDPH_peakHeight;
const bool useFlightDuration = m_ui->sBox_flightDuration->styleSheet().isEmpty();
if (useFlightDuration)
phfd = FlighDurationOrPeakHeight::FDPH_flightDuration;
const qint32 gearClassId = m_ui->cbGearClass->currentData().toInt();
ProjectDataManagement::GearClass::type gearClassType = static_cast<ProjectDataManagement::GearClass::type>(gearClassId);
setMinMaxConstraints(phfd, gearClassType, chargeTypeId);
// only for geometryType::Point:
if (m_currentGeometry.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgPoint)
{
int igniterTypeId = m_ui->cbIgniterType->currentData().toInt();
const double impactRadius = m_pdmDtc->impactRadius(chargeTypeId, igniterTypeId, 0); // GUEZE-2634
if (impactRadius <= 0)
ERROR_LOG(m_logger) << LOGFUNCTION << " impactRadius failed for chargeTypeId=" << chargeTypeId;
m_ui->sbDimensionX_BreiteRadius->setValue(std::round(impactRadius * 2.0));
// to simplify uiRequirements, set that value also to dimY:
m_ui->sbDimensionY_Tiefe->setValue(std::round(impactRadius * 2.0));
}
// switching from/to vulcano?
if (lastChargeTypeId != chargeTypeId)
{
bool lastWasVulcano = chargeTypeIdIsVulcano(lastChargeTypeId);
bool newIsVulcano = chargeTypeIdIsVulcano(chargeTypeId);
if (lastWasVulcano != newIsVulcano)
doCalculations();
}
// update last value:
m_ui->cbChargeType->setProperty(LAST_VALUE, chargeTypeId);
updateActorComboBox(m_coordGeo);
}
void ExplosionsEdit::onIgniterTypeChanged() {}
void ExplosionsEdit::onParticipantUnitChanged(int idx)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
Q_UNUSED(idx);
bool bOk = false;
int particpantUnitId = m_ui->cbParticipantUnits->currentData(Qt::UserRole).toInt(&bOk);
if (!bOk)
particpantUnitId = 0; // should be interpreted as error since particpantUnitId ir required in this dialog
QPointF centerFiringUnitGeo;
if (particpantUnitId > 0)
centerFiringUnitGeo = centerOfFiringUnitGeo(particpantUnitId);
if (centerFiringUnitGeo.isNull())
m_ui->cbParticipantUnits->setStyleSheet(QStringLiteral("background-color: orangered;"));
else
m_ui->cbParticipantUnits->setStyleSheet(QLatin1String(""));
// maybe empty item at beginning?
// if (particpantUnitId > 0)
// centerFiringUnitGeo = centerOfFiringUnitGeo(particpantUnitId);
// if ((particpantUnitId > 0) && (centerFiringUnitGeo.isNull()))
// m_ui->cbParticipantUnits->setStyleSheet(QStringLiteral("background-color: orangered;"));
// else
// m_ui->cbParticipantUnits->setStyleSheet(QLatin1String(""));
doCalculations();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void ExplosionsEdit::onParticipantUnitEntered()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " ENTER"
<< " id = " << m_ui->cbParticipantUnits->currentData().toInt();
showCenterOfFiringUnit(m_ui->cbParticipantUnits->currentData().toInt());
}
void ExplosionsEdit::onParticipantUnitLeaved()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " LEAVE"
<< " id = " << m_ui->cbParticipantUnits->currentData().toInt();
showCenterOfFiringUnit(0);
}
void ExplosionsEdit::onParticipantPositionChanged()
{
onParticipantUnitChanged(-1); // index is not used
}
void ExplosionsEdit::getPosition()
{
if (!has2DMap())
return;
m_previewCircleCenter = QPointF();
m_previewPolygon.clear();
m_previewCircleRadius = 0.0;
switch (m_currentGeometry.underlying())
{
case AreaEffectSimulation::AesExplosionGeometry_def::EgArea:
{
const bool isNull = qFuzzyCompare(m_coord.x(), 0) && qFuzzyCompare(m_coord.y(), 0);
const QPointF bottomLeft(Polygons::centerPoint2BottomLeft(m_coord,
m_ui->sbDimensionX_BreiteRadius->value(),
m_ui->sbDimensionY_Tiefe->value(),
(m_ui->sbAimingAxis->value() - 1600) / 6400. * 360));
m_sitMapGuiIfc->getRectangle(this,
SLOT(onMapRecData(QPointF, double, double, double, QPolygonF)),
nullptr,
s_minWidth,
s_maxWidth,
s_minHeight,
s_maxHeight,
isNull ? m_coord : bottomLeft,
m_ui->sbDimensionX_BreiteRadius->value(),
m_ui->sbDimensionY_Tiefe->value(),
(m_ui->sbAimingAxis->value() - 1600) / 6400. * 360);
return;
}
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint:
case AreaEffectSimulation::AesExplosionGeometry_def::EgPoint:
m_sitMapGuiIfc->getCoordinate(this, SLOT(onCoordinate(QPointF)), nullptr);
return;
case AreaEffectSimulation::AesExplosionGeometry_def::EgLine:
{
m_sitMapGuiIfc->getPolyline(this, SLOT(onPolylineData(QPolygonF)), nullptr, 2, 2, -1, -1, getCoordinatesQPointF());
return;
}
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgRadial:
m_sitMapGuiIfc->getCircle(this,
SLOT(onCircleData(QPointF, double)),
nullptr,
s_minRadius,
s_maxRadius,
m_ui->positionEdit->coordinate(),
m_ui->sbDimensionX_BreiteRadius->value()); // max
return;
default:
WARNING_LOG(m_logger) << " Unknown Geometry!";
return;
}
}
/*!
* \brief ExplosionsEdit::onCoordinate
* \param centerPoint
*/
void ExplosionsEdit::onCoordinate(const QPointF &centerPoint)
{
m_previewCircleCenter = centerPoint;
m_ui->positionEdit->setCoordinate(centerPoint);
const int chargeTypeId = m_ui->cbChargeType->currentData().toInt();
const int igniterTypeId = m_ui->cbIgniterType->currentData().toInt();
// NOTE: 0 = Default
const int igniterFunctionId = 0;
const double impactRadius = m_pdmDtc->impactRadius(chargeTypeId, igniterTypeId, igniterFunctionId);
m_previewCircleRadius = impactRadius;
onPositionChanged({});
showPreviewObject();
}
/*!
* \brief ExplosionsEdit::onMapRecData
* \param bottomLeft
* \param width
* \param height
* \param angleDegNorth
* \param bufferOutlinePolygon
*/
void ExplosionsEdit::onMapRecData(const QPointF &bottomLeft, double width, double height, double angleDegNorth, const QPolygonF &bufferOutlinePolygon)
{
m_previewPolygon = bufferOutlinePolygon;
m_ui->positionEdit->setCoordinate(Polygons::bottomLeft2CenterPoint(bottomLeft, width, height, angleDegNorth));
m_ui->sbDimensionX_BreiteRadius->setValue(round(width));
m_ui->sbDimensionY_Tiefe->setValue(round(height));
m_ui->sbAimingAxis->setValue(fmod((90.0 + angleDegNorth) * 6400 / 360, 6400));
onPositionChanged({});
showPreviewObject();
const Core::GeoPosition coordGeo(bottomLeft.x(), bottomLeft.y(), 0);
m_coordGeo = coordGeo;
updateActorComboBox(coordGeo);
}
void ExplosionsEdit::onCircleData(const QPointF &position, double radius)
{
m_previewCircleCenter = position;
m_ui->sbDimensionX_BreiteRadius->setValue(radius);
m_previewCircleRadius = m_ui->sbDimensionX_BreiteRadius->value();
m_ui->positionEdit->setCoordinate(position);
DEBUG_LOG(m_logger) << "Pos X: " << position.x() << " Y: " << position.y() << " R: " << radius;
showPreviewObject();
}
void ExplosionsEdit::onPolylineData(const QPolygonF &polyline)
{
if (polyline.size() < 2)
return;
m_previewPolygon = polyline;
setCoordinates(polyline, false);
const QPointF &geoCoord1 = polyline[0];
const QPointF &geoCoord2 = polyline[1];
const LtsCoordTrans::UtmCoordinate utm1 = LtsCoordTrans::UtmCoordinate::fromGeographic(geoCoord1);
const LtsCoordTrans::UtmCoordinate utm2 = LtsCoordTrans::UtmCoordinate::fromGeographic(geoCoord2);
const double angleDegNorth = SituationMap::MathUtils::getAngleDegreeNorth(QPointF(utm1.x, utm1.y), QPointF(utm2.x, utm2.y));
const double distance = LtsCoordTrans::distance(utm1, utm2);
DEBUG_LOG(m_logger) << " onPolylineData "
<< " geoCoord1: (" << geoCoord1.x() << "|" << geoCoord1.y() << ")"
<< " geoCoord2: (" << geoCoord2.x() << "|" << geoCoord2.y() << ")"
<< " geoCoord1Utm: (" << utm1.x << "|" << utm1.y << ")"
<< " geoCoord2Utm: (" << utm2.x << "|" << utm2.y << ")"
<< " angleDegNorth: " << angleDegNorth << " distance: " << distance;
m_ui->positionEdit->setCoordinate(utm1.geographic());
m_ui->positionEdit_2->setCoordinate(utm2.geographic());
m_ui->sbAimingAxis->setValue(fmod((angleDegNorth) * 6400 / 360, 6400));
m_ui->sbDimensionY_Tiefe->setValue(5);
m_ui->sbDimensionX_BreiteRadius->setValue(distance);
const Core::GeoPosition coordGeo2(utm2.geographic().x(), utm2.geographic().y(), 0);
const double height2 = getElevation(coordGeo2.lon(), coordGeo2.lat());
m_ui->sbHeight2SBox->setValue(height2);
DEBUG_LOG(m_logger) << "sbHeight2SBox setValue=" << height2;
showPreviewObject();
}
void ExplosionsEdit::updateActorComboBox(const Core::GeoPosition &coordGeo)
{
bool ok;
int esmParticipantId1 = m_ui->cbActors->currentData().toInt(&ok);
int esmParticipantId2 = m_ui->cbActors_2->currentData().toInt(&ok);
DEBUG_LOG(m_logger) << "currentIndex: " << m_ui->cbActors->currentIndex() << ", currentText: " << m_ui->cbActors->currentText()
<< ", esmParticipantId1: " << esmParticipantId1 << ", esmParticipantId2: " << esmParticipantId2 << ", ok: " << ok;
m_esmActorCbx->fillActorComboBox(m_ui->cbActors, coordGeo, isSmoke(), 0);
m_esmActorCbx->fillActorComboBox(m_ui->cbActors_2, coordGeo, isSmoke(), 0);
m_ui->cbActors->setCurrentIndex(m_ui->cbActors->findData(esmParticipantId1));
m_ui->cbActors_2->setCurrentIndex(m_ui->cbActors_2->findData(esmParticipantId2));
}
void ExplosionsEdit::setCoord(const QPointF &coord)
{
m_coord = coord;
const Core::GeoPosition coordGeo(coord.x(), coord.y(), 0);
m_coordGeo = coordGeo;
const double height = getElevation(coordGeo.lon(), coordGeo.lat());
m_ui->sbHeight1SBox->setValue(height);
updateActorComboBox(coordGeo);
}
bool ExplosionsEdit::isSmoke()
{
const qint32 chargeTypeId = m_ui->cbChargeType->currentData().toInt();
const qint32 igniterTypeId = m_ui->cbIgniterType->currentData().toInt();
if (chargeTypeId <= 0)
return false;
if (m_pdmDtc)
return m_pdmDtc->isSmoke(chargeTypeId, igniterTypeId, 0); // NOTE: IgniterFunciton = 0 as long as ExplosionsEdit does not contain values
return false;
}
void ExplosionsEdit::fillParticipantUnit()
{
QHash<int, QSet<int>> units;
const int partyId = m_ui->cbParty->currentData().toInt();
gatherParticipantUnits(partyId, units);
QSignalBlocker locked(m_ui->cbParticipantUnits);
const QVariant &savedCurrentData = m_ui->cbParticipantUnits->currentData();
m_ui->cbParticipantUnits->clear();
// // m_ui->cbParticipantUnits->addItem(QString()); // empty item
// sort displayed units texts on combobox
std::vector<std::tuple<QString, int, int>> unitList;
// Name, partId, count
const QList<int> &superiorIds = units.keys();
for (auto &superiorId : superiorIds)
{
const ParticipantManagement::Participant &part = m_ppmDtc->participant(superiorId);
unitList.push_back(std::tuple<QString, int, int>(QString::fromStdString(part.designator()) + QStringLiteral(" (%1)").arg(units.value(superiorId).count()),
part.id(),
units.value(superiorId).count()));
}
std::sort(unitList.begin(), unitList.end());
// set Combobox
int idx = 0;
for (const auto &ul : unitList)
{
m_ui->cbParticipantUnits->insertItem(idx, std::get<0>(ul), std::get<1>(ul));
m_ui->cbParticipantUnits->setItemData(idx, std::get<2>(ul), Qt::UserRole + 1);
++idx;
}
// restore current if possible, otherwise: if just one, preSelect that:
int idxToRestore = m_ui->cbParticipantUnits->findData(savedCurrentData);
if (idxToRestore >= 0)
m_ui->cbParticipantUnits->setCurrentIndex(idxToRestore);
// call xyChanged slot direct:
onParticipantUnitChanged(-1); // idx is not used within function.
}
void ExplosionsEdit::onFirePurposeChanged()
{
const qint32 currentFirePurposeId = m_ui->cbFirePurpose->currentData().toInt();
/*
// in 3D Wisi we need the duration otherwiese the old area effect simulation will fail or behave unexpected
const bool hasDuration = m_pdmDtc->getWithDurationFirePurpose(currentFirePurposeId);
m_ui->lblDuration->setVisible(hasDuration);
m_ui->sbDurationSec->setVisible(hasDuration);
*/
const int amountFireMarker = m_pdmDtc->getAmountFireMarkers(currentFirePurposeId);
m_ui->cbActors_2->setEnabled(amountFireMarker > 1 && m_ui->gbEffectMarkingInTa->isChecked());
}
void ExplosionsEdit::onDropAngleChanged() { doCalculations(true); }
void ExplosionsEdit::onWidthChanged(int width)
{
if (m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial)
{
m_ui->sbDimensionY_Tiefe->setValue(width);
return;
}
if (m_ui->positionEdit->coordinate() == QPoint() || m_ui->positionEdit_2->coordinate() == QPoint()
|| m_currentGeometry != AreaEffectSimulation::AesExplosionGeometry::EgLine)
return;
const LtsCoordTrans::UtmCoordinate utm1 = LtsCoordTrans::UtmCoordinate::fromGeographic(m_ui->positionEdit->coordinate());
const LtsCoordTrans::UtmCoordinate utm2 = LtsCoordTrans::UtmCoordinate::fromGeographic(m_ui->positionEdit_2->coordinate());
QLineF line(utm1.toPoint(), utm2.toPoint());
line.setLength(width);
const LtsCoordTrans::UtmCoordinate utm2New = LtsCoordTrans::UtmCoordinate(utm2.zone, line.p2().x(), line.p2().y());
const QSignalBlocker signalBlocker(m_ui->positionEdit_2);
m_ui->positionEdit_2->setCoordinate(utm2New.geographic());
}
void ExplosionsEdit::onAimingAxisChanged(int aimingAxis)
{
if (m_ui->positionEdit->coordinate() == QPoint() || m_ui->positionEdit_2->coordinate() == QPoint()
|| m_currentGeometry != AreaEffectSimulation::AesExplosionGeometry::EgLine)
return;
const LtsCoordTrans::UtmCoordinate utm1 = LtsCoordTrans::UtmCoordinate::fromGeographic(m_ui->positionEdit->coordinate());
const LtsCoordTrans::UtmCoordinate utm2 = LtsCoordTrans::UtmCoordinate::fromGeographic(m_ui->positionEdit_2->coordinate());
QLineF line(utm1.toPoint(), utm2.toPoint());
line.setAngle(fmod((aimingAxis * 360.0 / 6400.0) - 90.0 + 360.0, 360.0));
const LtsCoordTrans::UtmCoordinate utm2New = LtsCoordTrans::UtmCoordinate(utm2.zone, line.p2().x(), line.p2().y());
const QSignalBlocker signalBlocker(m_ui->positionEdit_2);
m_ui->positionEdit_2->setCoordinate(utm2New.geographic());
const Core::GeoPosition coordGeo(utm2New.geographic().x(), utm2New.geographic().y(), 0);
const double height = getElevation(coordGeo.lon(), coordGeo.lat());
m_ui->sbHeight1SBox->setValue(height);
}
void ExplosionsEdit::onLineP1Changed(const QPointF &point)
{
if (m_ui->positionEdit_2->coordinate() == QPoint() || m_currentGeometry != AreaEffectSimulation::AesExplosionGeometry::EgLine)
return;
const LtsCoordTrans::UtmCoordinate utm1 = LtsCoordTrans::UtmCoordinate::fromGeographic(point);
const LtsCoordTrans::UtmCoordinate utm2 = LtsCoordTrans::UtmCoordinate::fromGeographic(m_ui->positionEdit_2->coordinate());
const QLineF line(utm1.toPoint(), utm2.toPoint());
const QSignalBlocker signalBlockerAxis(m_ui->sbAimingAxis);
const QSignalBlocker signalBlockerWidth(m_ui->sbDimensionX_BreiteRadius);
m_ui->sbAimingAxis->setValue(fmod((line.angle() + 90.0) * 6400 / 360, 6400));
m_ui->sbDimensionX_BreiteRadius->setValue(line.length());
}
void ExplosionsEdit::onLineP2Changed(const QPointF &point)
{
if (m_ui->positionEdit->coordinate() == QPoint() || m_currentGeometry != AreaEffectSimulation::AesExplosionGeometry::EgLine)
return;
const LtsCoordTrans::UtmCoordinate utm1 = LtsCoordTrans::UtmCoordinate::fromGeographic(m_ui->positionEdit->coordinate());
const LtsCoordTrans::UtmCoordinate utm2 = LtsCoordTrans::UtmCoordinate::fromGeographic(point);
const QLineF line(utm1.toPoint(), utm2.toPoint());
const QSignalBlocker signalBlockerAxis(m_ui->sbAimingAxis);
const QSignalBlocker signalBlockerWidth(m_ui->sbDimensionX_BreiteRadius);
m_ui->sbAimingAxis->setValue(fmod((line.angle() + 90.0) * 6400 / 360, 6400));
m_ui->sbDimensionX_BreiteRadius->setValue(line.length());
}
void ExplosionsEdit::onPeakHeigthChanged(int data)
{
Q_UNUSED(data);
switchTo(FlighDurationOrPeakHeight::FDPH_peakHeight);
QSpinBox *sender = m_ui->sBox_peakHeight;
if (sender)
doCalculations();
}
void ExplosionsEdit::onFlightDurationChanged(int data)
{
Q_UNUSED(data);
switchTo(FlighDurationOrPeakHeight::FDPH_flightDuration);
QSpinBox *sender = m_ui->sBox_flightDuration;
if (!sender)
return;
doCalculations();
}
void ExplosionsEdit::onEffectDurationChanged(int value) { m_ui->sbDurationSec->setToolTip(QDateTime::fromSecsSinceEpoch(value).toString(QStringLiteral("mm:ss"))); }
void ExplosionsEdit::setMinMaxConstraints(ExplosionsEdit::FlighDurationOrPeakHeight phfd, ProjectDataManagement::GearClass_def::type gearClass, int chargeTypeId)
{
DEBUG_LOG(m_logger) << " setMinMaxConstraints START";
const QPair<int, int> &minMaxFd = getMinMaxFlightDurationSec(gearClass, chargeTypeId);
const QPair<int, int> &minMaxPh = getMinMaxPeakHeight(gearClass, chargeTypeId);
switch (phfd)
{
case FlighDurationOrPeakHeight::FDPH_flightDuration:
{
m_ui->sBox_flightDuration->setMinimum(minMaxFd.first);
m_ui->sBox_flightDuration->setMaximum(minMaxFd.second);
m_ui->sBox_flightDuration->setToolTip(QString(QStringLiteral("%1 - %2")).arg(m_ui->sBox_flightDuration->minimum()).arg(m_ui->sBox_flightDuration->maximum()));
{
// release constraints of other
m_ui->sBox_peakHeight->setMinimum(0);
m_ui->sBox_peakHeight->setMaximum(std::numeric_limits<int>::max());
// but show their limitations:
m_ui->sBox_peakHeight->setToolTip(QString(QStringLiteral("%1 - %2")).arg(minMaxPh.first).arg(minMaxPh.second));
}
// if constraints changes, we have to call setValueAccordingContraints again:
setValueAccordingContraints(m_ui->sBox_flightDuration->value(), phfd);
}
break;
case FlighDurationOrPeakHeight::FDPH_peakHeight:
{
m_ui->sBox_peakHeight->setMinimum(minMaxPh.first);
m_ui->sBox_peakHeight->setMaximum(minMaxPh.second);
m_ui->sBox_peakHeight->setToolTip(QString(QStringLiteral("%1 - %2")).arg(m_ui->sBox_peakHeight->minimum()).arg(m_ui->sBox_peakHeight->maximum()));
{
// release constraints of other
m_ui->sBox_flightDuration->setMinimum(0);
m_ui->sBox_flightDuration->setMaximum(std::numeric_limits<int>::max());
// but show their limitations:
m_ui->sBox_flightDuration->setToolTip(QString(QStringLiteral("%1 - %2")).arg(minMaxFd.first).arg(minMaxFd.second));
}
// if constraints changes, we have to call setValueAccordingContraints again:
setValueAccordingContraints(m_ui->sBox_peakHeight->value(), phfd);
}
break;
default:
break;
}
DEBUG_LOG(m_logger) << " setMinMaxConstraints END";
}
bool ExplosionsEdit::setValueAccordingContraints(int newValue, ExplosionsEdit::FlighDurationOrPeakHeight phfd)
{
DEBUG_LOG(m_logger) << " setValueAccordingContraints START, new Val = " << newValue << " # " << toString(phfd);
bool bRet = false;
const qint32 gearClassId = m_ui->cbGearClass->currentData().toInt();
const ProjectDataManagement::GearClass::type gearClass = static_cast<ProjectDataManagement::GearClass::type>(gearClassId);
const qint32 chargeTypeId = m_ui->cbChargeType->currentData().toInt();
switch (phfd)
{
case ExplosionsEdit::FlighDurationOrPeakHeight::FDPH_flightDuration:
{
bRet = isInBounds(newValue, getMinMaxFlightDurationSec(gearClass, chargeTypeId));
if (bRet)
{
m_ui->sBox_flightDuration->setValue(newValue);
m_ui->sBox_flightDuration->setProperty(VALUE_VALID_PROPERTY_NAME, true);
}
else
{
m_ui->sBox_flightDuration->setStyleSheet(QStringLiteral("background-color: orange;"));
m_ui->sBox_flightDuration->setProperty(VALUE_VALID_PROPERTY_NAME, false);
}
}
break;
case ExplosionsEdit::FlighDurationOrPeakHeight::FDPH_peakHeight:
{
bRet = isInBounds(newValue, getMinMaxPeakHeight(gearClass, chargeTypeId));
if (bRet)
{
m_ui->sBox_peakHeight->setValue(newValue);
m_ui->sBox_peakHeight->setProperty(VALUE_VALID_PROPERTY_NAME, true);
}
else
{
m_ui->sBox_peakHeight->setStyleSheet(QStringLiteral("background-color: orange;"));
m_ui->sBox_peakHeight->setProperty(VALUE_VALID_PROPERTY_NAME, false);
}
}
break;
default:
break;
}
INFO_LOG(m_logger) << " setValueAccordingContraints END, new Val = " << newValue << " # " << toString(phfd) << ", returns " << bRet;
return bRet;
}
bool ExplosionsEdit::isGuiVulcano()
{
const int curChargeTypeId = m_ui->cbChargeType->currentData().toInt();
return chargeTypeIdIsVulcano(curChargeTypeId);
}
void ExplosionsEdit::switchTo(FlighDurationOrPeakHeight phfd)
{
INFO_LOG(m_logger) << " switchTo " << toString(phfd);
switch (phfd)
{
case FlighDurationOrPeakHeight::FDPH_flightDuration:
m_ui->sBox_flightDuration->setStyleSheet(QLatin1String());
m_ui->sBox_peakHeight->setStyleSheet(QStringLiteral("background-color: gray;"));
break;
case FlighDurationOrPeakHeight::FDPH_peakHeight:
m_ui->sBox_flightDuration->setStyleSheet(QStringLiteral("background-color: gray;"));
m_ui->sBox_peakHeight->setStyleSheet(QLatin1String());
break;
default:
break;
}
const qint32 gearClassId = m_ui->cbGearClass->currentData().toInt();
ProjectDataManagement::GearClass::type gearClassType = static_cast<ProjectDataManagement::GearClass::type>(gearClassId);
const qint32 chargeTypeId = m_ui->cbChargeType->currentData().toInt();
setMinMaxConstraints(phfd, gearClassType, chargeTypeId);
}
class DoCalculationsExitGuard
{
public:
DoCalculationsExitGuard(ExplosionsEdit *parent, bool executeEnableUiElements)
: m_parent(parent)
, m_executeEnableUiElements(executeEnableUiElements)
{
}
~DoCalculationsExitGuard()
{
if (!m_parent)
return;
m_parent->updateAnnotationStr();
if (m_executeEnableUiElements)
m_parent->enableUiElements();
}
ExplosionsEdit *m_parent{nullptr};
bool m_executeEnableUiElements{true};
};
void ExplosionsEdit::doCalculations(bool executeEnableUiElements)
{
if (!m_initDone)
return;
// do not update while EsPause or similar:
if ((m_explosionClusterData.effectStateId() == AesEffectState_def::EsPaused) || (m_explosionClusterData.effectStateId() == AesEffectState_def::EsExpired))
return;
DoCalculationsExitGuard exitGuard(this, executeEnableUiElements);
QObject *senderObj = sender();
qDebug() << " doCalculations, sender = " << senderObj;
resetTempAndAnnotationStr();
// startObj / firingUnit
const int firingUnitPartId = m_ui->cbParticipantUnits->currentData(Qt::UserRole).toInt();
if (firingUnitPartId <= 0)
{
INFO_LOG(m_logger) << " firingUnitPartId invalid, firingUnitPartId = " << firingUnitPartId;
addAnnotationStr(tr("no originator"));
return;
}
const QPointF &centerFiringUnitGeo = centerOfFiringUnitGeo(firingUnitPartId);
if (centerFiringUnitGeo.isNull())
{
INFO_LOG(m_logger) << LOGFUNCTION << " centerOfFiringUnitGeo is null for firingUnitPartId: " << firingUnitPartId;
addAnnotationStr(tr("originator has no position"));
return;
}
// endObj / Area
const QPointF &posCoordGeo = m_coord;
if (posCoordGeo.isNull())
{
INFO_LOG(m_logger) << "posCoordGeo is null";
addAnnotationStr(tr("no target origin"));
return;
}
LtsUtils::QPoint3D startPos, endPos;
// toUtm: (pay attention to the zone)
// add z-Values:
{
LtsCoordTrans::UtmCoordinate endPosUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(posCoordGeo);
LtsCoordTrans::UtmCoordinate startPosUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerFiringUnitGeo, endPosUtm.zone);
m_temporaryData.m_distanceFiringToTarget = LtsCoordTrans::distance(endPosUtm, startPosUtm);
// static LtsElevation::LTS_ELEVATION_MANAGER_TYPE s_ltsElevationManager = nullptr;
// if (!s_ltsElevationManager)
// {
// std::shared_ptr<AssetManagement::Interface::Dtc> asmDtc;
// if (!m_session->getDtc<AssetManagement::DataInterface>(asmDtc))
// {
// WARNING_LOG(m_logger) << "getDtc for AssetManagement::DataInterface failed.";
// }
// std::shared_ptr<ExerciseManagement::Interface::Dtc> exmDtc;
// if (!m_session->getDtc<ExerciseManagement::DataInterface>(exmDtc))
// {
// WARNING_LOG(m_logger) << "getDtc for ExerciseManagement::DataInterface failed.";
// }
// s_ltsElevationManager = LtsElevation::LtsElevationManager::createInstance(m_session->exerciseId(), exmDtc, asmDtc);
// }
if (!m_elevationManager)
{
m_temporaryData.m_annotationStringFiring4Dlg = tr("Elevation failed");
WARNING_LOG(m_logger) << "LtsElevationManager::createInstance.";
return;
}
double zValue = getElevation(centerFiringUnitGeo.x(), centerFiringUnitGeo.y());
if (zValue < -300)
{
WARNING_LOG(m_logger) << "centerFiringUnitGeo zValue is very small, set to 100";
zValue = 100;
}
startPos = LtsUtils::QPoint3D(startPosUtm.x, startPosUtm.y, zValue);
zValue = getElevation(posCoordGeo.x(), posCoordGeo.y());
if (zValue < -300)
{
WARNING_LOG(m_logger) << "posCoordGeo zValue is very small, set to 100";
zValue = 100;
}
endPos = LtsUtils::QPoint3D(endPosUtm.x, endPosUtm.y, zValue);
}
if (isGuiVulcano())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " vulcano...";
const double vOut = TrajectoryGeneratorVulcano::defaultv0();
const double betaRad = XY::calculateBetaRad(vOut, m_ui->sBox_peakHeight->value()); // betaRad == Abschusswinkel
const double alphaRad = currentDropAngleRadiant();
DEBUG_LOG(m_logger) << LOGFUNCTION << " vulcano... "
<< ", vOut = " << vOut << ", betaDeg = " << betaRad * LtsCoordTrans::rad2deg() << ", alphaDeg = " << alphaRad * LtsCoordTrans::rad2deg();
// ErrorHandling
if (std::isnan(betaRad) || (betaRad * LtsCoordTrans::rad2deg() < 0) || (betaRad * LtsCoordTrans::rad2deg() >= 90))
{
ERROR_LOG(m_logger) << LOGFUNCTION << " calculateBetaRad failed."
<< ", betaRad = " << betaRad << "'";
addAnnotationStr(tr("calculateBetaRad failed (%1)").arg(betaRad));
return;
}
TrajectoryGeneratorVulcano::Options opts = TrajectoryGeneratorVulcano::OptIncludeIdealHit;
TrajectoryGeoPositionRelTime trajectoryOut;
Core::GeoPosition idealTapAbsoluteGeoOut;
QString reasonOut;
const QPoint3D startPos_geo3D(centerFiringUnitGeo.x(), centerFiringUnitGeo.y(), startPos.z());
const QPoint3D endPos_geo3D(posCoordGeo.x(), posCoordGeo.y(), endPos.z());
TrajectoryGenerator::generateTrajectoryVulcano(startPos_geo3D,
endPos_geo3D,
vOut,
betaRad,
alphaRad,
TrajectoryGeneratorVulcano::defaultTapTimeSec(),
opts,
trajectoryOut,
idealTapAbsoluteGeoOut,
&reasonOut);
// ErrorHandling
if (!reasonOut.isEmpty())
{
WARNING_LOG(m_logger) << LOGFUNCTION << " TrajectoryGenerator::generateTrajectoryVulcano failed."
<< ", reasonOut = " << reasonOut << "'";
addAnnotationStr(tr("flightCurve failed (%1)").arg(reasonOut));
return;
}
if (trajectoryOut.empty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " TrajectoryGenerator::generateTrajectoryVulcano failed."
<< ", trajectoryOut is empty";
addAnnotationStr(tr("flightCurve failed (size=%1)").arg(trajectoryOut.size()));
return;
}
const qint64 calculatedFlightDurationMsec = trajectoryOut.back().relativeTime();
m_temporaryData.m_estimatedFlightDurationMsec = Polygons::explosionClusterEstimatedMaxFlightDuration2DMSec(calculatedFlightDurationMsec); // ( *1.1) + (10 *1000)
m_temporaryData.m_idealTapAbsoluteGeoOut = idealTapAbsoluteGeoOut;
DEBUG_LOG(m_logger) << LOGFUNCTION << " calculatedFlightDurationMsec " << calculatedFlightDurationMsec;
DEBUG_LOG(m_logger) << LOGFUNCTION << " m_estimatedFlightDurationMsec " << m_temporaryData.m_estimatedFlightDurationMsec;
DEBUG_LOG(m_logger) << LOGFUNCTION << " m_estimatedFlightDurationSec " << m_temporaryData.m_estimatedFlightDurationMsec / 1000.0;
DEBUG_LOG(m_logger) << LOGFUNCTION << " m_distanceFiringToTarget " << m_temporaryData.m_distanceFiringToTarget;
DEBUG_LOG(m_logger) << LOGFUNCTION << " idealTapAbsoluteGeoOut " << m_temporaryData.m_idealTapAbsoluteGeoOut;
m_temporaryData.m_annotationStringFiring4Dlg = tr("Firing: %1° %2 m/s %3 sec TAP: %4 m")
.arg(std::round(betaRad * LtsCoordTrans::rad2deg() * 10) / 10)
.arg(std::round(vOut))
.arg(std::round(calculatedFlightDurationMsec / 1000.0))
.arg(std::round(m_temporaryData.m_idealTapAbsoluteGeoOut.z()));
QSignalBlocker locked(m_ui->sBox_flightDuration);
m_ui->sBox_flightDuration->setValue(std::round(calculatedFlightDurationMsec / 1000.0));
return;
}
QString reasonOut;
double betaRadiantOut, vOut;
const bool usePeakHeight = m_ui->sBox_peakHeight->styleSheet().isEmpty();
const bool useFlightDuration = m_ui->sBox_flightDuration->styleSheet().isEmpty();
DEBUG_LOG(m_logger) << LOGFUNCTION << ", usePeakHeight " << usePeakHeight << ", useFlightDuration " << useFlightDuration;
if (usePeakHeight)
{
// peak to duration
DEBUG_LOG(m_logger) << LOGFUNCTION << " peak to duration...";
const double heightAbs = startPos.z() + m_ui->sBox_peakHeight->value();
const qint64 durationSec = TrajectoryHelper::flightDurationSec(startPos, endPos, heightAbs, &betaRadiantOut, &vOut, &reasonOut);
DEBUG_LOG(m_logger) << LOGFUNCTION << " peak to duration... durationSec=" << durationSec << ", reasonOut=" << reasonOut;
if (!reasonOut.isEmpty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " TrajectoryHelper::flightDurationSec failed."
<< ", reasonOut = " << reasonOut << "'";
addAnnotationStr(tr("flightDurationSec failed (%1)").arg(reasonOut));
return;
}
m_temporaryData.m_annotationStringFiring4Dlg = tr("Firing: %1° %2 m/s").arg(std::round(betaRadiantOut * LtsCoordTrans::rad2deg() * 10) / 10).arg(std::round(vOut));
QSignalBlocker locked(m_ui->sBox_flightDuration);
if (!setValueAccordingContraints(durationSec, FlighDurationOrPeakHeight::FDPH_flightDuration))
addAnnotationStr(tr("FlightDuration (%1 sec) exceeds limits").arg(std::round(durationSec)));
}
else if (useFlightDuration)
{
// duration to peak
// DEBUG_LOG(m_logger) << LOGFUNCTION << " duration to peak...";
const double timeSec = m_ui->sBox_flightDuration->value();
const double peakHeightAbs = TrajectoryHelper::peakHeightAbs(startPos, endPos, timeSec, /*tm,*/ &betaRadiantOut, &vOut, &reasonOut);
const double peakHeightRel = peakHeightAbs - startPos.z();
if (!reasonOut.isEmpty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " TrajectoryHelper::peakHeightAbs failed."
<< ", reasonOut = " << reasonOut << "'";
addAnnotationStr(tr("peakHeightAbs failed (%1)").arg(reasonOut));
return;
}
m_temporaryData.m_annotationStringFiring4Dlg = tr("Firing: %1° %2 m/s").arg(std::round(betaRadiantOut * LtsCoordTrans::rad2deg() * 10) / 10).arg(std::round(vOut));
QSignalBlocker locked(m_ui->sBox_peakHeight);
if (!setValueAccordingContraints(peakHeightRel, FlighDurationOrPeakHeight::FDPH_peakHeight))
addAnnotationStr(tr("PeakHeight (%1 m rel) exceeds limits").arg(std::round(peakHeightRel)));
}
// calc estimatedFlightDuration: ! should be identical to svc !
Database::AesExplosionCluster explosionCluster;
explosionCluster.firingCenterPosition = LtsUtils::QPoint3D::fromQPointF(posCoordGeo);
explosionCluster.impactPosition = centerFiringUnitGeo;
if (usePeakHeight)
explosionCluster.peakHeight = m_ui->sBox_peakHeight->value();
if (useFlightDuration)
explosionCluster.flightDuration = m_ui->sBox_flightDuration->value() * 1000;
const qint64 calculatedFlightDurationMsec = Polygons::explosionClusterCalculatedFlightDuration2DMSec(explosionCluster);
m_temporaryData.m_estimatedFlightDurationMsec = Polygons::explosionClusterEstimatedMaxFlightDuration2DMSec(calculatedFlightDurationMsec); // ( *1.1) + (10 *1000)
DEBUG_LOG(m_logger) << LOGFUNCTION << " calculatedFlightDurationMsec " << calculatedFlightDurationMsec;
DEBUG_LOG(m_logger) << LOGFUNCTION << " m_estimatedFlightDurationMsec " << m_temporaryData.m_estimatedFlightDurationMsec;
DEBUG_LOG(m_logger) << LOGFUNCTION << " m_estimatedFlightDurationSec " << m_temporaryData.m_estimatedFlightDurationMsec / 1000.0;
DEBUG_LOG(m_logger) << LOGFUNCTION << " m_distanceFiringToTarget " << m_temporaryData.m_distanceFiringToTarget;
//
// check some contraints:
// [GUEZE-2458] , [GUEZE-2459]
//
const qint32 gearClassId = m_ui->cbGearClass->currentData().toInt();
ProjectDataManagement::GearClass::type gearClassType = static_cast<ProjectDataManagement::GearClass::type>(gearClassId);
const qint32 chargeTypeId = m_ui->cbChargeType->currentData().toInt();
if (m_temporaryData.m_distanceFiringToTarget < getMinDistance(gearClassType, chargeTypeId))
addAnnotationStr(tr("Originator target (%1 m) distance to low").arg(m_temporaryData.m_distanceFiringToTarget));
else if (m_temporaryData.m_distanceFiringToTarget > getMaxDistance(gearClassType, chargeTypeId))
addAnnotationStr(tr("Originator target (%1 m) distance to high").arg(m_temporaryData.m_distanceFiringToTarget));
// doCalcExitGuard will call enableUiElements
// if (executeEnableUiElements)
// enableUiElements();
// doCalcExitGuard will call enableUiElements
}
void ExplosionsEdit::onPartitionTimeChanged(qint64 newPartitionTimeMsec)
{
// TRACE_LOG(m_logger) << LOGFUNCTION << " START";
if (newPartitionTimeMsec <= 0)
newPartitionTimeMsec = m_session->partitionTime();
m_temporaryData.m_remainingTimeStr.clear();
LtsWidgets::LtsDateTimeEdit *dte = m_ui->dteEffectStart;
if (!dte)
return;
QString styleSheetStr;
qint64 timeUntilSecInclMinimum = 999999999; // simply big means okay :-)
// if there is visible a enabled/disable checkbox -> also allow 0 as effect start
if (dte->isCheckable() && dte->isChecked() && (!m_coord.isNull()))
{
qint64 timeUntilSec = (dte->msecsSinceEpoch() - m_temporaryData.m_estimatedFlightDurationMsec - newPartitionTimeMsec) / 1000;
timeUntilSecInclMinimum = timeUntilSec - s_timeUntilMinimumSec;
static const int timeFrame = 180; // 180 sec == 3 min
if (timeUntilSecInclMinimum <= 0)
styleSheetStr = QStringLiteral("QLineEdit { background-color: red; }");
else if (timeUntilSec < timeFrame)
{
// here timeUntilSec should be >= 10sec
double remainFactor = timeUntilSec / static_cast<double>(timeFrame);
if (remainFactor > 1)
remainFactor = 1;
const int opacity = 255 * remainFactor;
// DEBUG_LOG(m_logger) << LOGFUNCTION << " dte->msecsSinceEpoch() " << dte->msecsSinceEpoch()
// << ", m_estimatedFlightDurationMsec " << m_temporaryData.m_estimatedFlightDurationMsec
// << ", newPartitionTimeMsec " << newPartitionTimeMsec
// << ", dteEffectStart timeUntilSec " << timeUntilSec
// << ", remainFactor " << remainFactor
// << ", opacity " << opacity;
styleSheetStr = QString(QStringLiteral("QLineEdit { background-color: rgba(255, 165, 0, %1); }")).arg(255 - opacity);
}
if ((timeUntilSecInclMinimum >= 0) && (timeUntilSec < (60 * 60))) // 60*60 = 1h
m_temporaryData.m_remainingTimeStr = tr("remaining editing time: %1").arg(QDateTime::fromSecsSinceEpoch(timeUntilSecInclMinimum).toString(QStringLiteral("mm:ss")));
} // if (dte->withCheckBox() && ...
// DEBUG_LOG(m_logger) << LOGFUNCTION << " dteEffectStart styleSheetStr = '" << styleSheetStr << "'" << ", timeUntilSec >= 10 -> " << (timeUntilSec >= 10);
// property:
dte->setProperty(VALUE_VALID_PROPERTY_NAME, timeUntilSecInclMinimum > 0);
dte->setStyleSheet(styleSheetStr);
updateAnnotationStr();
// TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
/*!
* \brief ExplosionsEdit::dialogIdentifier
* \return
*/
QString ExplosionsEdit::dialogIdentifier() const
{
return m_aesItem.id() <= 0 ? QStringLiteral("AddExplosionsArtillery") : QStringLiteral("EditExplosionsArtillery%1").arg(m_aesItem.id());
}
void ExplosionsEdit::onExplosionClusterChanged(const ExplosionCluster &ecData, DataModification modType)
{
if (ecData.id() != m_aesItem.id())
return;
BaseEdit::onAreaChanged(ecData, modType);
if (modType == DataModification::eRemoved)
{
m_explosionClusterData = ExplosionCluster();
return;
}
// detect, if isObjectModifiable() changes to false:
if (
// (m_explosionClusterData.isObjectModifiable()) &&
(!ecData.isObjectModifiable()))
{
INFO_LOG(m_logger) << " object becames not modifiable";
enableUiFireButton(); // "Start" -> "Pause"...
if (!m_enableFireBtnTimer)
{
m_enableFireBtnTimer = new QTimer(this);
// m_enableFireBtnTimer->setSingleShot(true);
m_enableFireBtnTimer->setInterval(5000);
connect(m_enableFireBtnTimer, &QTimer::timeout, this, &ExplosionsEdit::enableUiFireButton, Qt::UniqueConnection);
}
m_enableFireBtnTimer->start();
}
m_explosionClusterData = ecData;
m_currentGeometry = m_explosionClusterData.explosionGeometry();
if (ecData.flightCurve().is_set())
{
const FlightCurve &fc = ecData.flightCurve().get();
m_ui->leCalcFlightDuration->setText(QString(QStringLiteral("%1 sec")).arg(fc.calculatedFlightDuration() / 1000.0));
m_ui->leCalcPeakHeight->setText(QString(QStringLiteral("%1 m abs")).arg(fc.calculatedPeakHeightAbs()));
}
else
{
m_ui->leCalcFlightDuration->setText(QString(QStringLiteral("- sec")));
m_ui->leCalcPeakHeight->setText(QString(QStringLiteral("- m abs")));
}
m_ui->leDescription->setText(QString::fromStdString(ecData.annotationString()));
m_ui->leDescription->setToolTip(m_ui->leDescription->text());
if (!m_ui->sbDurationSec->isEnabled())
{
const bool hasChanges = hasUnsavedChanges();
m_ui->sbDurationSec->setValue(m_explosionClusterData.effectDuration() / 1000); // msec -> sec
if (!hasChanges)
__unchange__();
}
m_uiRequirements->checkRequirements(this);
doCalculations(true);
}
void ExplosionsEdit::handleLaserPrecisionChargeType(const int chargeTypeId)
{
const auto geoButtons = m_geometrieButtonGroup->buttons();
if (chargeTypeId == s_laserPrecisionChargeTypeId)
{
onGeometrySelected(static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgPoint));
for (QAbstractButton *btn : geoButtons)
{
if (m_geometrieButtonGroup->id(btn) != static_cast<int>(AreaEffectSimulation::AesExplosionGeometry_def::EgPoint))
btn->setCheckable(false);
}
}
else
{
for (QAbstractButton *btn : geoButtons)
btn->setCheckable(true);
}
}
QPointF ExplosionsEdit::centerOfFiringUnitGeo(int particpantUnitId)
{
if (!m_sitMapGuiIfc || !m_ppmDtc)
return {};
if (particpantUnitId <= 0)
return {};
std::vector<Core::GeoPosition> singlePositionsOut_unused;
const LtsUtils::QPoint3D &centerCoordGeo = Polygons::calculateFiringUnitCenterNonRecursive(m_ppmDtc, particpantUnitId, singlePositionsOut_unused, m_logger);
if (centerCoordGeo.isNull() || std::isnan(centerCoordGeo.x()))
{
// may happen in real life: so WARNING_LOG to DEBUG_LOG
DEBUG_LOG(m_logger) << LOGFUNCTION << " calculateFiringUnitCenterNonRecursive failed, particpantUnitId = " << particpantUnitId;
return {};
}
return centerCoordGeo.toPointF();
}
void ExplosionsEdit::showCenterOfFiringUnit(int particpantUnitId)
{
if (!m_sitMapGuiIfc || !m_ppmDtc)
return;
TRACE_LOG(m_logger) << LOGFUNCTION << " START particpantUnitId: " << particpantUnitId;
if (particpantUnitId <= 0)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " showCenterOfFiringUnit: " << particpantUnitId << ", removeObjectIndicator";
m_sitMapGuiIfc->removeObjectIndicator();
return;
}
const QPointF &centerFiringUnitGeo = centerOfFiringUnitGeo(particpantUnitId);
if (centerFiringUnitGeo.isNull())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " showCenterOfFiringUnit: " << particpantUnitId << ", removeObjectIndicator due to invalid position";
m_sitMapGuiIfc->removeObjectIndicator();
return;
}
TRACE_LOG(m_logger) << LOGFUNCTION << " END particpantUnitId: " << particpantUnitId;
m_sitMapGuiIfc->objectIndicatorToPoint(centerFiringUnitGeo); // you can provide a duration: lifetimeMsec = -1
}
/*!
* \brief ExplosionsEdit::retranslate
*/
void ExplosionsEdit::retranslate()
{
QString title = tr("Artillery");
translate(m_ui, m_aesItem, title);
}
void ExplosionsEdit::enableUiElements()
{
DEBUG_LOG(m_logger) << " enableUiElements()"
<< ", m_explosionClusterData.id() = " << m_explosionClusterData.id() << ", isModifiable() = " << isModifiable()
<< ", isOnlyNameModifiable() = " << isOnlyNameModifiable() << ", ena = " << (isModifiable() && !isOnlyNameModifiable());
if (m_explosionClusterData.id() == 0) // while creation, not modification
enableUiElements(true);
else
enableUiElements(isModifiable() && !isOnlyNameModifiable());
}
/*!
* \brief ExplosionsEdit::enableUiElements
* \param enable
*/
void ExplosionsEdit::enableUiElements(bool enable)
{
DEBUG_LOG(m_logger) << "************* enableUiElements enable=" << enable << ", isOnlyNameModifiable = " << isOnlyNameModifiable();
// // onGeometrySelected(static_cast<int>(m_explosionClusterData.explosionGeometry().underlying()));
m_ui->cbChargeType->setEnabled(enable);
m_ui->cbIgniterType->setEnabled(enable && m_ui->cbIgniterType->count() > 1);
m_ui->sbDurationSec->setEnabled(enable);
m_ui->cbParty->setEnabled(enable);
m_ui->cbParticipantUnits->setEnabled(enable);
setEffectStartEnabled(m_ui->dteEffectStart, enable);
m_ui->pbPosition->setEnabled(m_sitMapGuiIfc && enable);
m_ui->positionEdit->setEnabled(enable);
m_ui->sbDimensionX_BreiteRadius->setEnabled(enable
&& (m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgArea
|| m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial
|| m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgLine));
m_ui->sbDimensionY_Tiefe->setEnabled(enable && m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgArea);
m_ui->sbHeight1SBox->setEnabled(enable);
m_ui->sbHeight2SBox->setEnabled(enable && m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgLine);
m_ui->sbAimingAxis->setEnabled(
enable
&& (m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgArea || m_currentGeometry == AreaEffectSimulation::AesExplosionGeometry_def::EgLine));
m_ui->cbFirePurpose->setEnabled(enable);
m_ui->cbGearClass->setEnabled(enable);
m_ui->cbActors->setEnabled(enable & m_ui->gbEffectMarkingInTa->isChecked());
const bool bIsPoint = isPointSimilar(m_currentGeometry);
const bool bIsVulcano = isGuiVulcano();
m_ui->sbCannons->setEnabled(enable /*&& !bIsPoint*/ && !bIsVulcano);
m_ui->rbDropAngleDown->setEnabled(enable && bIsPoint && bIsVulcano);
m_ui->rbDropAngleUp->setEnabled(enable && bIsPoint && bIsVulcano);
// m_ui->sbRounds->setEnabled(enable && m_type == etArea);
// m_ui->cbEffectMarkingInTa->setEnabled(enable);
// [GUEZE-2960] Explizite Kunden-Forderung:
// ZpNumber nicht_ mehr modifizierbar, wenn Wirkfeld aktiv
m_ui->leZpNumber->setEnabled((enable || isOnlyNameModifiable()) && m_aesItem.effectStateId() != AesEffectState_def::EsActivated);
m_ui->leElement->setEnabled((enable || isOnlyNameModifiable()) && m_aesItem.effectStateId() != AesEffectState_def::EsActivated);
m_ui->pbClear->setEnabled(enable);
// tbc
m_ui->sbZog->setEnabled(enable);
m_ui->sBox_flightDuration->setEnabled(enable && !bIsVulcano);
m_ui->sBox_peakHeight->setEnabled(enable);
int numberTrajectoriesRef = 0;
{
const int simToRestrict = m_aesItem.id();
// avoid data copying:
auto cb = [simToRestrict, &numberTrajectoriesRef](const AreaEffectSimulation::TrajectoryTimeAware &traj)
{
// iterateAll
// be aware of: [E01562-259] use originatorId, originatorTypeId (instead of aesItemId, aesTypeId)
if (traj.originatorTypeId() != ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation)
return;
const int aesItemId = traj.originatorId();
if (simToRestrict == aesItemId)
++numberTrajectoriesRef;
};
m_dtc->trajectoryTimeAwares(cb);
}
DEBUG_LOG(m_logger) << "************* enableUiElements numberTrajectoriesRef=" << numberTrajectoriesRef;
if ((numberTrajectoriesRef > 0) && !isPointSimilar(m_explosionClusterData.explosionGeometry()))
{
// since first TrajectoryTimeAware:
enableUiFireButton();
}
else
{
// until first TrajectoryTimeAware:
m_ui->pbFire->setEnabled(enable && m_aesItem.id() > 0); // tbc
}
// GUEZE-3520: get rid of cbFlightCurve
// {
// // GUEZE-2459
// // Munitionstyp "Mörser" muss die obere Winkelgruppe ausgewählt && die Eingabemaske "Flugkurve" soll ausgrauen.
// // Beim Wechsel auf einen anderen Munitionstyp soll wieder als Default die untere Winkelgruppe ausgewählt werden &&
// // die Eingabemaske muss wieder auswählbar sein.
// const ProjectDataManagement::GearClass_def::type gearClassType =
// static_cast<ProjectDataManagement::GearClass_def::type>(m_ui->cbGearClass->currentData().toInt());
// // const bool usePeakHeight = m_ui->sBox_peakHeight->styleSheet().isEmpty();
// const bool useFlightDuration = m_ui->sBox_flightDuration->styleSheet().isEmpty();
// m_ui->cbFlightCurve->setEnabled(enable &&
// (gearClassType != ProjectDataManagement::GearClass_def::Mortar) &&
// useFlightDuration
// );
// }
const QList<QAbstractButton *> &geoButtons = m_geometrieButtonGroup->buttons();
for (QAbstractButton *btn : qAsConst(geoButtons))
btn->setEnabled(enable);
// For PreFat
m_ui->geometrieAimPointBtn->hide();
}
void ExplosionsEdit::enableUiFireButton()
{
INFO_LOG(m_logger) << " START pbFire_immediately isEnabled = " << m_ui->pbFire->isEnabled();
// In general available 'States' are: Start | Pause | Resume
m_ui->pbFire->setEnabled(false);
//
// Start (immediately) should only be enabled, if object is in modification
// (not initial creation)
//
bool enableBasics = (m_aesItem.id() > 0) // while creation, not modification
&& isDataSufficiant();
INFO_LOG(m_logger) << " enableBasics = " << enableBasics;
if (!enableBasics)
return; // stay disabled
if (m_explosionClusterData.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsExpired)
return; // stay disabled
int numberTrajectoriesRef = 0;
{
const int simToRestrict = m_aesItem.id();
// avoid data copying:
auto cb = [simToRestrict, &numberTrajectoriesRef](const AreaEffectSimulation::TrajectoryTimeAware &traj)
{
// [E01562-259] use originatorId, originatorTypeId (instead of aesItemId, aesTypeId)
if ((traj.originatorTypeId() == ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation) && (simToRestrict == traj.originatorId()))
++numberTrajectoriesRef;
};
m_dtc->trajectoryTimeAwares(cb);
}
INFO_LOG(m_logger) << " numberTrajectoriesRef = " << numberTrajectoriesRef;
// keep in mind:
// a) during typical usage by operator
// b) for re-openening dialog / restart svcAes
if ((numberTrajectoriesRef <= 0) && (m_explosionClusterData.effectDuration() == m_explosionClusterData.originalEffectDuration()))
{
INFO_LOG(m_logger) << " Firing has NOT started yet";
return; // stay disabled
}
else if (numberTrajectoriesRef >= m_explosionClusterData.impactCount())
{
INFO_LOG(m_logger) << " Firing has finished";
return; // stay disabled
}
// const bool bIsPointSimilar = isPointSimilar(m_currentGeometry);
// if firing started once, -> 'States' are reduced: Pause | Resume
// toogle via property 'resumeExecution'
bool bPause = true;
{
const QVariant &resumeExecutionVariant = m_ui->pbFire->property("resumeExecution");
if (resumeExecutionVariant.isValid())
{
INFO_LOG(m_logger) << " resumeExecutionVariant = " << resumeExecutionVariant.toBool();
bPause = !resumeExecutionVariant.toBool();
}
else
{
// re-openening dialog:
if (m_explosionClusterData.effectStateId() == AesEffectState_def::EsPaused)
bPause = false;
}
}
if (bPause)
{
// Pause
m_ui->pbFire->setEnabled(true);
m_ui->pbFire->setText(tr("Pause"));
m_ui->pbFire->setIcon(QIcon(QStringLiteral("icon:avm_pause.svg")));
m_ui->pbFire->setProperty("resumeExecution", false);
}
else
{
// Resume
// but it does not makes sense, if there is no ammo left:
if ((m_explosionClusterData.impactCount() > 1) && (m_explosionClusterData.impactCount() >= m_explosionClusterData.remainExplosionNumber()))
{
m_ui->pbFire->setEnabled(true);
m_ui->pbFire->setText(tr("Start"));
m_ui->pbFire->setIcon(QIcon(QStringLiteral("icon:avm_start.svg")));
m_ui->pbFire->setProperty("resumeExecution", true);
}
}
INFO_LOG(m_logger) << " pbFire_immediately text = " << m_ui->pbFire->text();
INFO_LOG(m_logger) << " END pbFire_immediately isEnabled = " << m_ui->pbFire->isEnabled();
}
/*!
* \brief ExplosionsEdit::showPreviewObject
*/
void ExplosionsEdit::showPreviewObject()
{
if (!has2DMap(false)) // showMsgBoxIfNot2D
return;
switch (m_currentGeometry.underlying())
{
case AreaEffectSimulation::AesExplosionGeometry_def::EgArea:
if (m_previewPolygon.isEmpty())
return;
m_sitMapGuiIfc->displayPreviewObject(this, m_previewPolygon, true);
return;
case AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint:
case AreaEffectSimulation::AesExplosionGeometry_def::EgPoint:
if (m_previewCircleCenter.isNull())
return;
m_sitMapGuiIfc->displayPreviewObject(this, m_previewCircleCenter, m_previewCircleRadius);
return;
case AreaEffectSimulation::AesExplosionGeometry_def::EgLine:
if (m_previewPolygon.isEmpty())
return;
m_sitMapGuiIfc->displayPreviewObject(this, m_previewPolygon, false);
return;
case AreaEffectSimulation::AesExplosionGeometry_def::EgRadial:
if (m_previewCircleCenter.isNull())
return;
m_sitMapGuiIfc->displayPreviewObject(this, m_previewCircleCenter, m_previewCircleRadius);
return;
default:
WARNING_LOG(m_logger) << " Unknown Geometry!";
return;
}
}
bool ExplosionsEdit::isDataSufficiant()
{
// identifier:
if (m_ui->leZpNumber->text().isEmpty())
return false;
// originator:
const int partId = m_ui->cbParticipantUnits->currentData(Qt::UserRole).toInt();
bool bFiringUnitOk = (partId > 0) && m_ui->cbParticipantUnits->styleSheet().isEmpty();
if (!bFiringUnitOk)
return false;
// target:
bool bHasTargetArea = !m_coord.isNull();
switch (m_currentGeometry.underlying())
{
case AreaEffectSimulation::AesExplosionGeometry_def::EgArea:
bHasTargetArea &= m_ui->sbDimensionX_BreiteRadius->value() > 0;
bHasTargetArea &= m_ui->sbDimensionY_Tiefe->value() > 0;
break;
default:
break;
}
if (!bHasTargetArea)
return false;
// ammo
const bool bHasAmmo = m_ui->cbChargeType->currentData().toInt() > 0;
if (!bHasAmmo)
return false;
return true;
}
/*!
* \brief ExplosionsEdit::apply
* \return
*/
bool ExplosionsEdit::apply()
{
ExplosionClusterModifyRequest request;
request.id(m_aesItem.id());
request.element(m_ui->leElement->text().toStdString());
request.name(m_ui->leZpNumber->text().toStdString());
if (m_ui->dteEffectStart->isChecked())
request.effectStart().set(m_ui->dteEffectStart->msecsSinceEpoch());
if (m_ui->sbDurationSec->isVisible())
request.effectDuration(m_ui->sbDurationSec->value() * 1000);
const bool bIsPoint = isPointSimilar(m_currentGeometry);
// Area: (for area-geometry and point-geometry)
request.explosionGeometry(m_currentGeometry);
Core::GeoPosition2D bottomLeftGeo;
switch (m_currentGeometry.underlying())
{
case AesExplosionGeometry_def::EgRadial:
{
QPointF bottomLeft(Polygons::centerPoint2BottomLeft(m_coord, m_ui->sbDimensionX_BreiteRadius->value() * 2, m_ui->sbDimensionY_Tiefe->value() * 2, 0.0));
bottomLeftGeo = Core::GeoPosition2D(bottomLeft.x(), bottomLeft.y());
request.area().cornerBottomLeft(bottomLeftGeo);
request.area().height(m_ui->sbDimensionY_Tiefe->value() * 2);
request.area().width(m_ui->sbDimensionX_BreiteRadius->value() * 2);
break;
}
case AesExplosionGeometry_def::EgLine:
{
const QLineF line(m_ui->positionEdit->coordinate(), m_ui->positionEdit_2->coordinate());
QPointF bottomLeft(Polygons::centerPoint2BottomLeft(line.center(),
m_ui->sbDimensionX_BreiteRadius->value(),
m_ui->sbDimensionY_Tiefe->value(),
(m_ui->sbAimingAxis->value() - 1600) * 360.0 / 6400));
bottomLeftGeo = Core::GeoPosition2D(bottomLeft.x(), bottomLeft.y());
request.area().cornerBottomLeft(bottomLeftGeo);
request.area().height(m_ui->sbDimensionY_Tiefe->value());
request.area().width(m_ui->sbDimensionX_BreiteRadius->value());
break;
}
case AesExplosionGeometry_def::EgAimPoint:
case AesExplosionGeometry_def::EgArea:
case AesExplosionGeometry_def::EgPoint:
default:
{
QPointF bottomLeft(Polygons::centerPoint2BottomLeft(m_coord,
m_ui->sbDimensionX_BreiteRadius->value(),
m_ui->sbDimensionY_Tiefe->value(),
(m_ui->sbAimingAxis->value() - 1600) * 360.0 / 6400));
bottomLeftGeo = Core::GeoPosition2D(bottomLeft.x(), bottomLeft.y());
request.area().cornerBottomLeft(bottomLeftGeo);
request.area().height(m_ui->sbDimensionY_Tiefe->value());
request.area().width(m_ui->sbDimensionX_BreiteRadius->value());
break;
}
}
QVariant currentData = m_ui->cbActors->currentData();
// DMF (Actors)
int esmParticipantId1 = m_ui->cbActors->currentData().toInt(); // participant ID
int esmParticipantId2 = m_ui->cbActors_2->currentData().toInt(); // participant ID
DEBUG_LOG(m_logger) << "currentIndex: " << m_ui->cbActors->currentIndex() << ", currentText: " << m_ui->cbActors->currentText()
<< ", participantId1: " << esmParticipantId1 << ", participantId2: " << esmParticipantId2;
if (esmParticipantId1 > 0 || esmParticipantId2 > 0)
{
request.esmParticipantIds().reserve(2);
if (esmParticipantId1 > 0)
request.esmParticipantIds().push_back(esmParticipantId1);
if (esmParticipantId2 > 0 && esmParticipantId1 != esmParticipantId2 && m_ui->cbActors_2->isEnabled())
request.esmParticipantIds().push_back(esmParticipantId2);
}
if (bIsPoint || m_currentGeometry.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial)
request.area().rotation(0);
else
request.area().rotation((m_ui->sbAimingAxis->value() - 1600) * 360.0 / 6400);
request.tla(m_ui->sbZog->value()); // tla: aka zog / ZielOrtungsGenauigkeit
// mhpCount:
if (bIsPoint)
{
request.mhpCount(1);
}
else
{
if (m_ui->cbParticipantUnits->currentData().toString() != QString())
{
const qint32 mtpCount = m_ui->cbParticipantUnits->currentData(Qt::UserRole + 1).toInt(); //
const qint32 sbCannonsCount = m_ui->sbCannons->value();
if (mtpCount <= 0)
{
// maybe aes will crash on invalid mtpCount?
ERROR_LOG(m_logger) << LOGFUNCTION << " mtpCount invalid, (mtpCount=" << mtpCount << ")";
return false;
}
// Wenn weniger Schüsse als Einheiten dann nimm schüsse
if (mtpCount > sbCannonsCount)
request.mhpCount(sbCannonsCount);
else
request.mhpCount(mtpCount);
}
}
// partyId
request.partyId().set(m_ui->cbParty->currentData().toInt());
// participant id
bool bOk = false;
request.participantId().set(m_ui->cbParticipantUnits->currentData(Qt::UserRole).toInt(&bOk));
if (!bOk)
request.participantId(0); // should be interpreted as error since particpantUnitId ir required in this dialog
// request.flightCurve(static_cast<AreaEffectSimulation::AesFlightCurve::inner_enum>(m_ui->cbFlightCurve->currentData().toInt()));
request.impactCount(m_ui->sbCannons->value());
// add GearClass ???
request.chargeStruct().chargeTypeId(m_ui->cbChargeType->currentData().toInt());
request.chargeStruct().igniterTypeId(m_ui->cbIgniterType->currentData().toInt());
request.firePurposeId(m_ui->cbFirePurpose->currentData().toInt());
// peakHeight | flightDuration
{
request.peakHeight(-1);
request.flightDuration(-1);
const bool usePeakHeight = m_ui->sBox_peakHeight->styleSheet().isEmpty();
const bool useFlightDuration = m_ui->sBox_flightDuration->styleSheet().isEmpty();
if (usePeakHeight == useFlightDuration)
request.peakHeight(m_ui->sBox_peakHeight->value());
else if (useFlightDuration)
request.flightDuration(m_ui->sBox_flightDuration->value() * 1000);
else
request.peakHeight(m_ui->sBox_peakHeight->value());
}
if (request.flightDuration() >= 600 * 1000)
{
for (int i = 0; i < 10; ++i)
FATAL_LOG(m_logger) << LOGFUNCTION << " !!!!!!!!!!!!!!"
<< " flightDuration is exceeding limit!"
<< " m_ui->sBox_flightDuration->value() " << m_ui->sBox_flightDuration->value();
}
request.partyId().set(m_ui->cbParty->currentData().toInt());
request.effectMarkingInTa(m_ui->gbEffectMarkingInTa->isChecked());
request.effectSourceId(ProjectDataManagement::EffectSource::ES_Internal);
if (isGuiVulcano())
{
request.launchVelocity(TrajectoryGeneratorVulcano::defaultv0()); // Velocity == Speed ; m/s
request.launchAngleDeg(0); // calculated ; parameter is prepared for ADLER
request.dropAngle(currentDropAngleDegree());
if (request.effectStart().is_set())
request.tapTime(request.effectStart().get() - (TrajectoryGeneratorVulcano::defaultTapTimeSec() * 1000)); // default: 8 sec
request.lcAttack(m_ui->sbLcAttack_Prf->value());
request.lcTermination(m_ui->sbLcTermination_Prf->value());
const int cloudBaseValue = m_ui->spCloudBase->value();
// specialValueText
if (cloudBaseValue == m_ui->spCloudBase->minimum())
request.cloudBase(std::numeric_limits<int>::min());
else
request.cloudBase(cloudBaseValue);
} // if (isGuiVulcano())
m_requestHelper->addRequest([this, request]() { return m_ftc->explosionClusterModifyRequest(request, this); });
return m_requestHelper->startRequests();
}
/*!
* \brief ExplosionsEdit::onGeometrySelected
* \param type
*/
void ExplosionsEdit::onGeometrySelected(int explosionGeometryInt)
{
const AreaEffectSimulation::AesExplosionGeometry::inner_enum newGeometry = static_cast<AreaEffectSimulation::AesExplosionGeometry::inner_enum>(explosionGeometryInt);
if (m_currentGeometry == newGeometry)
return;
m_currentGeometry = newGeometry;
RocketUtility::enaDisaWirkmittel(m_ui->cbGearClass, m_currentGeometry.underlying());
// cancel active Rubberbands:
if (m_sitMapGuiIfc)
m_sitMapGuiIfc->cancelActiveRubberbands();
qint32 mtpCount = m_ui->cbParticipantUnits->currentData(Qt::UserRole + 1).toInt(); //
if (mtpCount < 1)
mtpCount = 4;
QSignalBlocker blocker(m_geometrieButtonGroup);
QAbstractButton *myBtn = m_geometrieButtonGroup->button(explosionGeometryInt);
if (myBtn)
myBtn->click();
if (m_sitMapGuiIfc)
m_sitMapGuiIfc->removePreviewObject();
switch (m_currentGeometry.underlying())
{
case AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint: // What is AimPoint?
case AreaEffectSimulation::AesExplosionGeometry_def::EgPoint:
{
m_ui->lblRadiusBreite->setText(tr("Breite [m]"));
m_ui->sbHeight2SBox->setEnabled(false);
m_ui->sbDimensionX_BreiteRadius->setEnabled(false);
m_ui->sbDimensionY_Tiefe->setEnabled(false);
m_ui->sbZog->setEnabled(true);
m_ui->sbAimingAxis->setEnabled(false);
m_ui->sbAimingAxis->setValue(1600);
m_ui->positionEdit->setEnabled(true);
m_ui->pbPosition->setEnabled(true);
m_ui->pbClear->setEnabled(true);
m_ui->positionEdit_2->setEnabled(false);
// m_ui->sbCannons->setEnabled(false);
m_ui->sbCannons->setMinimum(1);
m_ui->sbCannons->setValue(1);
m_ui->sbDurationSec->setValue(1);
m_ui->sbDurationSec->setSingleStep(1);
// adjust some values according geometryType:
onChargeTypeChanged();
}
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgArea:
{
m_ui->lblRadiusBreite->setText(tr("Breite [m]"));
m_ui->sbHeight2SBox->setEnabled(false);
m_ui->sbDimensionX_BreiteRadius->setEnabled(true);
m_ui->sbDimensionY_Tiefe->setEnabled(true);
m_ui->sbZog->setEnabled(true);
m_ui->sbAimingAxis->setEnabled(true);
m_ui->positionEdit->setEnabled(true);
m_ui->pbPosition->setEnabled(true);
m_ui->pbClear->setEnabled(true);
m_ui->positionEdit_2->setEnabled(false);
m_ui->sbCannons->setEnabled(true);
m_ui->sbCannons->setMinimum(2);
m_ui->sbCannons->setValue(mtpCount);
m_ui->sbDurationSec->setValue(60); // 1 min
m_ui->sbDurationSec->setSingleStep(60);
}
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgLine:
{
m_ui->lblRadiusBreite->setText(tr("Breite [m]"));
m_ui->sbHeight2SBox->setEnabled(true);
m_ui->sbDimensionX_BreiteRadius->setEnabled(true);
m_ui->sbDimensionY_Tiefe->setEnabled(false);
m_ui->sbZog->setEnabled(true);
m_ui->sbAimingAxis->setEnabled(true);
m_ui->positionEdit->setEnabled(true);
m_ui->pbPosition->setEnabled(true);
m_ui->pbClear->setEnabled(true);
m_ui->positionEdit_2->setEnabled(true);
m_ui->sbCannons->setEnabled(true);
m_ui->sbCannons->setMinimum(2); // ToDo
m_ui->sbDurationSec->setValue(60); // 1 min
}
break;
case AreaEffectSimulation::AesExplosionGeometry_def::EgRadial:
{
m_ui->lblRadiusBreite->setText(tr("Radius [m]"));
m_ui->sbHeight2SBox->setEnabled(false);
m_ui->sbDimensionX_BreiteRadius->setEnabled(true);
m_ui->sbDimensionY_Tiefe->setEnabled(false);
m_ui->sbZog->setEnabled(true);
m_ui->sbAimingAxis->setEnabled(false);
m_ui->positionEdit->setEnabled(true);
m_ui->pbPosition->setEnabled(true);
m_ui->pbClear->setEnabled(true);
m_ui->positionEdit_2->setEnabled(false);
m_ui->sbCannons->setEnabled(true);
m_ui->sbCannons->setMinimum(2); // ToDo
m_ui->sbDurationSec->setValue(60); // 1 min
}
break;
default:
WARNING_LOG(m_logger) << " Unknown Geometrie!";
break;
}
m_ui->sbDimensionX_BreiteRadius->clear();
m_ui->sbDimensionY_Tiefe->clear();
m_ui->positionEdit->clear();
m_ui->positionEdit_2->clear();
fillCharges();
}
void ExplosionsEdit::onFireClicked()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
if (((m_aesItem.effectStateId() == AesEffectState_def::EsUnknown) || // initially the effectStateId is Unknown...
(m_aesItem.effectStateId() == AesEffectState_def::EsError) || (m_aesItem.effectStateId() == AesEffectState_def::EsExpired)
|| (m_aesItem.effectStateId() == AesEffectState_def::EsRemoved) || (m_aesItem.effectStateId() == AesEffectState_def::EsReceived)
|| (m_aesItem.effectStateId() == AesEffectState_def::EsFired)
// || (m_aesItem.effectStateId() == AesEffectState_def::EsPaused) // Maybe EsPaused will be supported in future
)
|| m_aesItem.id() <= 0)
return;
if (!isDataSufficiant())
{
INFO_LOG(m_logger) << LOGFUNCTION << " onFireClicked "
<< ", data not sufficiant...";
return;
}
// skip ui requirement of effectStart by deactivating:
m_ui->dteEffectStart->setChecked(false);
onPartitionTimeChanged();
m_ui->pbFire->setEnabled(false);
const bool bIsObjectModifiable = m_explosionClusterData.isObjectModifiable();
if ((m_aesItem.effectStateId() == AesEffectState_def::EsCreated || m_aesItem.effectStateId() == AesEffectState_def::EsPrepared) && bIsObjectModifiable)
{
m_ui->dteEffectStart->setMsecsSinceEpoch(m_session->partitionTime() + m_temporaryData.m_estimatedFlightDurationMsec + 5000);
m_ui->dteEffectStart->setChecked(true);
INFO_LOG(m_logger) << LOGFUNCTION << " onFireClicked "
<< ", m_estimatedFlightDurationMsec = " << m_temporaryData.m_estimatedFlightDurationMsec
<< ", checkRequirements = " << m_uiRequirements->checkRequirements(this);
// Todo: Triggering by simulated button press is a little awkward...
if (m_uiRequirements->checkRequirements(this))
this->m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->click();
enableUiElements(); // to enable/disable fire-btn
return;
}
// dialog has to be re-opened.
// isObjectModifiable() should be false
if (!bIsObjectModifiable)
{
bool resumeExecution = false;
const QVariant &resumeExecutionVariant = m_ui->pbFire->property("resumeExecution");
if (resumeExecutionVariant.isValid())
resumeExecution = resumeExecutionVariant.toBool();
m_ui->pbFire->setProperty("resumeExecution", !resumeExecution);
DEBUG_LOG(m_logger) << LOGFUNCTION << " onFireClicked "
<< " ExplosionClusterPauseResumeRequest with "
<< " resumeExecution = " << resumeExecution;
// slow down FireBtn being pressed by restarting the timer:
if (m_enableFireBtnTimer)
m_enableFireBtnTimer->start(); // restart
m_requestHelper->addRequest(
[=]()
{
return m_ftc->explosionClusterPauseResumeRequest(ExplosionClusterPauseResumeRequest(m_aesItem.id(),
resumeExecution, // resumeExecution
dds::core::optional<rti::core::int64>(),
dds::core::optional<int32_t>(),
dds::core::optional<int32_t>()),
this);
});
m_requestHelper->startRequests();
} // if (!bIsObjectModifiable)
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
/*!
* \brief ExplosionsEdit::onGearClassChanged
*/
void ExplosionsEdit::onGearClassChanged()
{
const ProjectDataManagement::GearClass_def::type newGearClassType = static_cast<ProjectDataManagement::GearClass_def::type>(m_ui->cbGearClass->currentData().toInt());
{
QSignalBlocker sb(m_ui->cbChargeType);
fillCharges();
}
fillParties();
onPartyChanged();
ProjectDataManagement::GearClass_def::type lastGearClassType = ProjectDataManagement::GearClass_def::Mortar;
QVariant lastVal = m_ui->cbGearClass->property(LAST_VALUE);
if (!lastVal.isNull())
lastGearClassType = static_cast<ProjectDataManagement::GearClass_def::type>(lastVal.toInt());
{
// GUEZE-2459
// Munitionstyp "Mörser" muss die obere Winkelgruppe ausgewählt && die Eingabemaske "Flugkurve" soll ausgrauen.
// Beim Wechsel auf einen anderen Munitionstyp soll wieder als Default die untere Winkelgruppe ausgewählt werden &&
// die Eingabemaske muss wieder auswählbar sein.
// GUEZE-3520: Aufraeumen -> getm rid of AesFlightCurve
// if (newGearClassType == ProjectDataManagement::GearClass_def::Mortar)
// {
// DEBUG_LOG(m_logger) << " onGearClassChanged switch to Mortar";
// m_ui->cbFlightCurve->setCurrentIndex( m_ui->cbFlightCurve->findData(static_cast<int>(AreaEffectSimulation::AesFlightCurve_def::FcHigh)) );
// }
// else if (lastGearClassType == ProjectDataManagement::GearClass_def::Mortar)
// {
// DEBUG_LOG(m_logger) << " onGearClassChanged switch to Non-Mortar";
// m_ui->cbFlightCurve->setCurrentIndex( m_ui->cbFlightCurve->findData(static_cast<int>(AreaEffectSimulation::AesFlightCurve_def::FcLow)) );
// }l
}
// change?
if (lastGearClassType != newGearClassType)
{
DEBUG_LOG(m_logger) << " GearClass changed to " << newGearClassType;
// [GUEZE-2460]
// Die Eingabe von kleineren oder größeren Werten soll verblockt sein.
// -->>> // Bei Wechsel des Munitionstyps muss die Flugdauer übernommen werden und <<-----------
// dazu dann die neue Gipfelhöhe berechnet werden.
// Sofern vor dem Wechsel des Munitionstyp ein Wert kleiner oder größer der
// Grenzwerte des neuen Munitionstyp eingestellt war, muss dann
// dementsprechend die minimale oder maximale Gipfelhöhe eingestellt werden.
switchTo(FlighDurationOrPeakHeight::FDPH_flightDuration);
}
// update last value:
m_ui->cbGearClass->setProperty(LAST_VALUE, newGearClassType);
doCalculations();
}
void ExplosionsEdit::onPositionChanged(const QPointF &newGeoCoord)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
Q_UNUSED(newGeoCoord);
doCalculations();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void ExplosionsEdit::gatherParticipantUnits(const int &partyId, QHash<int, QSet<int>> &units) const
{
const qint32 gearClassId = m_ui->cbGearClass->currentData().toInt();
auto cb = [this, &partyId, &units, gearClassId](const ParticipantManagement::Participant &part)
{
if (part.isDeleted() || part.partyId() != partyId)
return;
const ParticipantManagement::ParticipantData &ppData = m_ppmDtc->convertParticipantToParticipantData(part);
const ParticipantManagement::Type &ppType = m_ppmDtc->type(ppData.typeId());
// do not use ADLER Verbund types
if (m_ppmDtc->isAdlerVerbund(ppType.id()))
return;
const QJsonObject typeProperties(QJsonDocument::fromJson(QByteArray::fromStdString(ppType.properties())).object());
// superior ids in participant ids will be saved
// participant ids with type units are ignored
// participant ids with type GroundVehicule are considered
// participant ids without properties "isArtillery=true" are ignored
if (typeProperties.isEmpty() || ppType.typeClass() == ParticipantManagement::TypeClass::Unit || //
!typeProperties[QStringLiteral("isArtillery")].toBool(false) || typeProperties[QStringLiteral("gearClassId")].toInt(-1) != gearClassId)
return;
units[part.superiorId()] += part.id();
};
m_ppmDtc->participants(cb);
// 2nd filtering remove superiors unit who does not have the property type "artillery"
for (auto it = units.begin(); it != units.end();)
{
const ParticipantManagement::Participant &pp = m_ppmDtc->participant(it.key());
const ParticipantManagement::Type &typ = m_ppmDtc->type(pp.typeId());
const QJsonObject typeProperties(QJsonDocument::fromJson(QByteArray::fromStdString(typ.properties())).object());
if ((typ.typeClass() != ParticipantManagement::TypeClass::Unit && typ.typeClass() != ParticipantManagement::TypeClass::GroundVehicle)
|| !typeProperties[QStringLiteral("isArtillery")].toBool(false))
it = units.erase(it++);
else
++it;
}
}
void ExplosionsEdit::fillParties()
{
// first fill all parties in
m_ppmGuiCbxIfc->fillPartyComboBox(m_ui->cbParty);
// there is a signal blocker in m_ppmGuiCbxIfc->fillPartyComboBox(), so it is ok to start here
const QSignalBlocker cbPartyBlocker(m_ui->cbParty);
// sort out parties without participants without the chosen gearType
for (qint32 partyIdx = m_ui->cbParty->count() - 1; partyIdx >= 0; --partyIdx)
{
const qint32 partyId = m_ui->cbParty->itemData(partyIdx, Qt::UserRole).toInt();
if (isValidParty(partyId))
continue;
m_ui->cbParty->removeItem(partyIdx);
}
}
bool ExplosionsEdit::isValidParty(qint32 partyId)
{
const qint32 gearClassId = m_ui->cbGearClass->currentData().toInt();
return m_ppmDtc->participants(static_cast<std::function<bool(const ParticipantManagement::Participant &)>>(
[this, partyId, gearClassId](const ParticipantManagement::Participant &participant)
{
if (participant.partyId() != partyId || participant.isDeleted())
return false;
const ParticipantManagement::Type type = m_ppmDtc->type(participant.typeId());
const QJsonObject typeProperties = QJsonDocument::fromJson(QByteArray::fromStdString(type.properties())).object();
const qint32 typeGearClass = typeProperties.value(QStringLiteral("gearClassId")).toInt(-1);
return typeGearClass == gearClassId;
}));
}
void ExplosionsEdit::fillCharges()
{
DEBUG_LOG(m_logger) << " fillCharges";
if (!m_pdmDtc)
{
ERROR_LOG(m_logger) << " no m_pdmDtc";
return;
}
const ProjectDataManagement::GearClass_def::type curGearClassType = static_cast<ProjectDataManagement::GearClass_def::type>(m_ui->cbGearClass->currentData().toInt());
const auto chargesForGearClass = m_pdmDtc->chargesForGearClass(curGearClassType);
const int curCtDataToRestore = m_ui->cbChargeType->currentData().toInt();
// current target geometry:
const bool bIsPoint = isPointSimilar(m_currentGeometry);
DEBUG_LOG(m_logger) << " fillCharges"
<< ", bIsPoint " << bIsPoint;
QSignalBlocker locked(m_ui->cbChargeType);
m_ui->cbChargeType->clear();
for (const ProjectDataManagement::ChargeType &chargeType : qAsConst(chargesForGearClass))
{
// Vulcano ist available for point geometry only:
const bool bIsVulcano = chargeTypeIdIsVulcano(chargeType.id());
// NOTE: igniterType/IgniterFunciton = 0 as long as ExplosionsEdit does not contain values
const double impactRadius4Debug = m_pdmDtc->impactRadius(chargeType.id(), 0, 0);
bool isAdlerChargeType = m_pdmDtc->isAdlerChargeType(chargeType.id());
DEBUG_LOG(m_logger) << " fillCharges"
<< ", id=" << chargeType.id() << ", name= " << chargeType.name() << ", bIsPoint " << bIsPoint << ", bIsVulcano " << bIsVulcano
<< ", impactRadius4Debug = " << impactRadius4Debug << ", isAdlerChargeType = " << isAdlerChargeType;
if (isAdlerChargeType || (bIsVulcano && !bIsPoint && curGearClassType != ProjectDataManagement::GearClass_def::Artillery)) // [Jira E01562-21]
continue;
m_ui->cbChargeType->addItem(QString::fromStdString(chargeType.name()), chargeType.id());
}
// restore last:
if (curCtDataToRestore > 0)
{
int curIdxFound = m_ui->cbChargeType->findData(curCtDataToRestore);
if (curIdxFound >= 0)
m_ui->cbChargeType->setCurrentIndex(curIdxFound);
}
onChargeTypeChanged();
}
void ExplosionsEdit::fillGearClasses()
{
const auto &gearClasses = m_pdmDtc->gearTypes();
for (auto type : qAsConst(gearClasses))
{
if (m_gearClassFilter.contains(type.gearClassId()) && m_pdmDtc->getAdlerWeaponSystemId(type.id()) <= 3)
m_ui->cbGearClass->addItem(m_pdmDtc->trGearTypeName(type.id()), type.gearClassId().underlying());
}
}
int ExplosionsEdit::currentDropAngleDegree()
{
if (m_ui->rbDropAngleDown->isChecked())
return s_dropAngleDown;
else
return s_dropAngleUp;
}
double ExplosionsEdit::currentDropAngleRadiant() { return currentDropAngleDegree() * LtsCoordTrans::deg2rad(); }
bool ExplosionsEdit::isOnlyNameModifiable() const
{
// BaseClass::isOnlyNameModifiable() -> EsPaused || EsActivated
// m_explosionClusterData.isObjectModifiable -> since PreStart and s_calculationOffsetMSec
const bool ret = BaseClass::isOnlyNameModifiable() || !m_explosionClusterData.isObjectModifiable();
return ret;
}
void ExplosionsEdit::resetTempAndAnnotationStr()
{
INFO_LOG(m_logger) << " RESET resetTempAndAnnotationStr";
m_temporaryData.reset();
// updateAnnotationStr();
}
void ExplosionsEdit::addAnnotationStr(const QString &newAnno)
{
m_temporaryData.m_annotationString4Dlg += newAnno;
updateAnnotationStr();
}
void ExplosionsEdit::updateAnnotationStr()
{
QStringList sl;
if (!m_temporaryData.m_annotationStringFiring4Dlg.isEmpty())
sl.append(m_temporaryData.m_annotationStringFiring4Dlg);
if (!m_temporaryData.m_annotationString4Dlg.isEmpty())
sl.append(m_temporaryData.m_annotationString4Dlg);
if (!m_temporaryData.m_remainingTimeStr.isEmpty())
sl.append(m_temporaryData.m_remainingTimeStr);
if (sl.isEmpty())
m_ui->lblAnnotationStringContent->setText(QStringLiteral("-"));
else if (sl.size() == 1)
m_ui->lblAnnotationStringContent->setText(sl.first());
else
m_ui->lblAnnotationStringContent->setText(sl.join(QStringLiteral(" ; ")));
}
double ExplosionsEdit::getElevation(const double lon, const double lat) const
{
if (m_elevationManager == nullptr)
return 0.0;
const double height = m_elevationManager->elevation(lon, lat);
DEBUG_LOG(m_logger) << "getElevation lon=" << lon << ", lat=" << lat << ", height=" << height;
return height;
}
} // namespace AreaEffectSimulation