Qt6 Migration: Complete migration with DDS/RTI support and build validation

Major Changes:
- Merged latest Qt5 devel content from dlbo (292 files changed, 7 new dirs)
- Consolidated forward-declarations and include folders
- Removed unneeded modules per Niklas' repo check list

Build System Fixes:
- Fixed CMake VERSION_GREATER syntax in ParticipantManagement/CMakeLists.txt
- Added VLC packages (libvlc-dev, vlc-plugin-base) to docker_build.sh
- Added PostgreSQL dev packages (libpq-dev, postgresql-server-dev-all) to Dockerfile
- Fixed locale warnings (ANSI_X3.4-1968 → C.UTF-8)
- Fixed ArcGIS SDK path warnings when BUILD_ARCGIS=OFF

Code Generation Fixes:
- Fixed IDL enum collisions (PortType→CorePortType/DdsutPortType, PdmEventType)
- Fixed Grantlee template svc_translation.cpp.grantlee:
  - Changed {{ idl_enum.name }}_def:: to {{ idl_enum.name }}::
  - Fixed .underlying() calls for C++11 scoped enums (enum class)
- Removed generated files from repo (moc_*, *_dtc.h, *_ftc.h)

Validation:
- BUILD_RTI=ON: SUCCESS
- BUILD_RTI=OFF: SUCCESS
- C++17 standard maintained for RTI 7.7 compatibility
- Ubuntu 26.04 Docker environment verified

Remaining Warnings (expected):
- BgViewer, KDChart, KDReports, Poppler-Qt6 not found (optional features)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
torstenandCopilot committed 2026-09-15 18:20:26 +00:00
1 parent f23384bb68
commit 7173785915
18662 files changed
+4115092 -5435

No files matched your search

@@ -1,18 +1,23 @@
/**
* @RDE_HEADER@
* AesHandler for AdlerJson Plugin
* $Id: aes_handler.cpp 62899 2024-05-29 11:08:50Z ewert_j $
* $HeadURL: svn://slssvn/TDS/branches/product/features/ufom/Lts/Proxies/TrainingAreaBase/Plugins/AdlerJson/aes_handler.cpp $
* $Author: ewert_j $
* Created by Julius Ewert, ESIM, 2024-03-20
*/
#include "aes_handler.h"
#include <utility>
#include "Core/core_types.h"
#include "DDS/Core/function_reply_object.h"
#include "aes.hpp"
#include "json_dispatcher.h"
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QPointF>
#include <QtCore/QRectF>
#include <QtCore/QString>
#include <QtGui/QTransform>
#include <QtCore/QVector>
#include <QJsonArray>
#include <QJsonDocument>
#include <QPointF>
#include <QRectF>
#include <QString>
#include <QTransform>
#include <QVector>
#include <time.h>
#include <vector>
@@ -31,6 +36,7 @@ namespace AdlerJson
AesHandler::AesHandler(std::shared_ptr<TrainingAreaBase::Interface::Fts> ftsTab, QObject *parent)
: QObject(parent)
, m_requestHelper(new LtsUtils::RequestHelper(this))
, m_logger(getLogger("AesHandler"))
{
Q_UNUSED(ftsTab)
}
@@ -48,20 +54,20 @@ bool AesHandler::init(MainDispatcher *mainDispatcher)
if (!m_ftcAes->enableAirSupportImpactModify() || !m_dtcAes->enableExplosionSingle())
{
ERROR_LOG(m_logger) << "Can not enableAirSupportImpactModify or enableExplosionSingle";
// return false;
}
if (!m_dtcAes->enableExplosionCluster() || !m_ftcAes->enableExplosionClusterModify() || !m_ftcAes->enableExplosionClusterReleaseReceived()
|| !m_ftcAes->enableExplosionClusterPauseResume() || !m_ftcAes->enableTrajectoryAdlerModify() || !m_ftcAes->enableEffectClusterFireHalt())
{
ERROR_LOG(m_logger) << "Can not enableExplosionCluster or enableExplosionClusterModify";
//
}
if (!m_dtcAes->enableEffectCluster() || !m_ftcAes->enableEffectClusterModify() || !m_ftcAes->enableEffectClusterReleaseReceived()
|| !m_ftcAes->enableEffectClusterPauseResume() || !m_ftcAes->enableEffectClusterRemove())
{
ERROR_LOG(m_logger) << "Can not enableEffectnCluster or enableEffectClusterModify";
//
}
DEBUG_LOG(m_logger) << "------ Connect Explosion Single Changed ------- ";
@@ -88,13 +94,13 @@ void AesHandler::onEffectClusterModifyReply(const Core::FunctionReplyObject &rep
if (replyObject.error())
{
// If the error isn't a json object (we use this to reply the reference and party id, as the object is not on dds yet)
// send an ExTq something went wront, either Data inconistencies or effector is alreaedy assigned.
DEBUG_LOG(m_logger) << "Got onEffectClusterModifyReply ERROR: " << replyObject.translated();
ExTqTechnischeQuittung extq;
extq.technischeDaten.beschreibung = replyObject.translated();
extq.technischeDaten.status = Status_L32::DAT;
// I dont Like it.. as we might get two request at the same time.. overriding but should work most of the
m_mainDispatcher->jsonDispatcher()->sendExTqTechnischeQuittung(extq, m_mainDispatcher->ppmHandler()->getDtc()->participant(m_participantId).partyId());
return;
}
@@ -114,7 +120,7 @@ void AesHandler::onEffectClusterModifyReply(const Core::FunctionReplyObject &rep
m_aesIdToFeuerkampfReferenz.insert(replyObject.id(), feuerkampfReferenz);
NotificationManagement::CreateNotificationRequest req;
// The message of the Notification.
QString content = QString::fromUtf8(tr_lts("Notification", "New Effect Cluster from SAA Verein= %1 <BR />[Open](//ExplosionGui/Adler/%2)"));
QStringList argsList;
argsList.append(m_wirkfeldPlanungsData.feuerkommando.verein);
@@ -122,24 +128,24 @@ void AesHandler::onEffectClusterModifyReply(const Core::FunctionReplyObject &rep
std::vector<std::string> args;
args.reserve(argsList.size());
for (const auto &str : std::as_const(argsList))
for (const auto &str : qAsConst(argsList))
args.emplace_back(str.toStdString());
req.notification().content(content.toStdString());
req.notification().contentArgs(args);
// Which Exercise should parse the notification
req.notification().exerciseId(m_mainDispatcher->exerciseId());
req.notification().timeStamp(m_mainDispatcher->ddsHandler()->partitionTime());
// Priority: Highest: Klick Links immediatly
// Priority: High: Show message Box
req.notification().priority(NotificationManagement::Priority::High);
// set priority after proccesed for all
req.notification().processedPriority(NotificationManagement::Priority::Low);
//req.notification().title(tr_lts("Notification", "Wirkfeldplanung"));
req.notification().title(tr_lts("Notification", "AES-Planning"));
// Who is the sender, most of the time a Service
NotificationManagement::Address addressSender;
addressSender.addressType(NotificationManagement::AddressType::Service);
auto saa = m_mainDispatcher->jsonDispatcher()->getSAAByParty(partyId);
@@ -148,16 +154,16 @@ void AesHandler::onEffectClusterModifyReply(const Core::FunctionReplyObject &rep
addressSender.name(saa->getSAAName().toStdString());
}else
{
// use Backup
addressSender.name(QStringLiteral("TAB").toStdString());
}
req.notification().sender(addressSender);
req.notification().notificationType(NotificationManagement::NotificationType::CombatOrder);
req.notification().messageType(NotificationManagement::MessageType(NotificationManagement::MessageType::Success));
// Notify all Adler User for Party
std::vector<NotificationManagement::Address> recipentVec;
// Honestly... I don't know if this is the correct Right? but will do for now
const QVector<int> recipientIds = m_mainDispatcher->ddsHandler()->dtcOpm()->getNotificationRecipents(m_mainDispatcher->exerciseId(),
partyId,
OperatorRights::AdlerExplosionClusterEdit,
@@ -165,7 +171,7 @@ void AesHandler::onEffectClusterModifyReply(const Core::FunctionReplyObject &rep
std::vector<NotificationManagement::Address> recipientIdsDds;
recipientIdsDds.reserve(recipientIds.size());
for (const auto &recipientId : std::as_const(recipientIds))
for (const auto &recipientId : qAsConst(recipientIds))
{
NotificationManagement::Address addr;
addr.addressId(recipientId);
@@ -182,7 +188,7 @@ void AesHandler::onEffectClusterModifyReply(const Core::FunctionReplyObject &rep
}
else
WARNING_LOG(m_logger) << "No recipent found for exercise";
// sendTrajectoryAdlerModifyRequest(m_wirkfeldPlanungsData, m_participantId, m_wirkmittelIndex, m_aesId);
}
void AesHandler::onCreateNotificationReply(const Core::FunctionReplyObject &replyObject)
@@ -262,15 +268,22 @@ void AesHandler::handleEXFire(const Feuerkampfdaten &data, int wsId)
outData.equipmentNumber() = particpantId;
outData.reference() = data.feuerkampfReferenz.toStdString();
outData.identifier() = data.verein.toStdString();
outData.effectorId() = data.kenner.underlying();
outData.effectorId() = data.kenner.underlying(); // static_cast<L27_EffectorId_def>((int)data.kenner);
m_ddsHandler->dtsTab()->publish(outData);
QTimer::singleShot(3000, [this, particpantId]() { m_ddsHandler->dtsTab()->removeEX_FireByKey(particpantId); });
const QString refTrimmed = data.feuerkampfReferenz.trimmed();
handleAmmoCount(data, saa->getPartyId(), m_feuerkampfReferenzUsedFeuerHalt[refTrimmed]);
// This is for pause Resume
/*
m_ftcAes->explosionClusterPauseResumeRequest(AreaEffectSimulation::ExplosionClusterPauseResumeRequest(aes.id(),
false, // resumeExecution
dds::core::optional<rti::core::int64>(),
dds::core::optional<int32_t>(),
dds::core::optional<int32_t>())
, this);
*/
}
void AesHandler::handleEXDontFire(const Feuerkampfdaten &data, int wsId)
@@ -286,8 +299,8 @@ void AesHandler::handleEXDontFire(const Feuerkampfdaten &data, int wsId)
<< "feuerkampfReferenz: " << data.feuerkampfReferenz;
handleAmmoCount(data, saa->getPartyId(), true);
// The EX-FQ(nFN) message is used to count shots during a General CEASE FIRE or a Referenced CEASE FIRE.
// After the CEASE FIRE is lifted, the remaining shots are transmitted again using an EX-FQ(FN) message.
const QString refTrimmed = data.feuerkampfReferenz.trimmed();
m_feuerkampfReferenzUsedFeuerHalt[refTrimmed] = true;
@@ -302,18 +315,18 @@ void AesHandler::onTrajectoryAdlerModifyReply(const Core::FunctionReplyObject &r
ERROR_LOG(m_logger) << reply.translated();
if (m_wirkmittelIndex == m_wirkfeldPlanungsData.feuerkommando.wirkmittel.size() - 1)
{
// TODO TAB->SAA EX-MQ
DEBUG_LOG(m_logger) << "TAB->SAA EX-MQ"
<< ", feuerkampfReferenz :" << m_wirkfeldPlanungsData.feuerkampfReferenz;
DEBUG_LOG(m_logger) << "onTrajectoryAdlerModifyReply " << reply.id();
ExFeuerbereit exFB;
// exFB.feuerkampfdaten.feuerkampfReferenz = QString::fromStdString(data.element());
exFB.feuerkampfdaten.feuerkampfReferenz = m_wirkfeldPlanungsData.feuerkampfReferenz;
// exFB.feuerkampfdaten.verein = QString::fromStdString(m_dtcPpm->participant(data.participantId().get()).name());
exFB.feuerkampfdaten.verein = m_wirkfeldPlanungsData.feuerkommando.verein;
// exFB.feuerkampfdaten.kenner =
// m_mainDispatcher->jsonDispatcher()->sendExFeuerbereit(exFB, data.partyId().get());
}
if (m_wirkmittelIndex < m_wirkfeldPlanungsData.feuerkommando.wirkmittel.size() - 1)
{
@@ -336,7 +349,7 @@ void AesHandler::sendTrajectoryAdlerModifyRequest(const WirkfeldplanungData &dat
QVector<int> subordinates = m_dtcPpm->getSubordinatesByParticipant(participantId);
QVector<ParticipantManagement::ParticipantPosition> subordinatePositions;
for (int subordinate : std::as_const(subordinates))
for (int subordinate : qAsConst(subordinates))
{
DEBUG_LOG(m_logger) << "subordinates(" << participantId << ") :" << subordinate;
subordinatePositions.append(m_dtcPpm->participantPosition(subordinate));
@@ -352,35 +365,46 @@ void AesHandler::sendTrajectoryAdlerModifyRequest(const WirkfeldplanungData &dat
double grad = (data.zieldaten.zielachse / 6400.) * 360.0;
for (Flugbahnpunkt pos : std::as_const(data.feuerkommando.wirkmittel.at(wirkmittelIndex).flugbahnpunkt))
for (Flugbahnpunkt pos : qAsConst(data.feuerkommando.wirkmittel.at(wirkmittelIndex).flugbahnpunkt))
{
/*
DEBUG_LOG(m_logger) << "Flugbahnpunkt:"
<< ", xRelativ :" << pos.xRelativ
<< ", yRelativ :" << pos.yRelativ
<< ", zRelativ :" << pos.zRelativ
<< ", zeit :" << pos.zeit
<< ", positionLaunch :" << positionLaunch.lon()
<< " / " << positionLaunch.lat()
<< " / " << positionLaunch.z()
<< "<< utmLaunch :" << utmLaunch.x
<< " / " << utmLaunch.y
<< " / " << utmLaunch.zone;
*/
QPointF utm = QPointF(pos.xRelativ, pos.zRelativ);
QPointF utmLaunchPt(utmLaunch.x, utmLaunch.y);
utm += utmLaunchPt;
double z = positionLaunch.z() + pos.yRelativ;
// see also: coordtrans_test::testRelativeMetricalOffet()
LtsCoordTrans::UtmCoordinate utmCoord;
utmCoord.x = utm.x();
utmCoord.y = utm.y();
utmCoord.zone = utmLaunch.zone;
QPointF pntGeo = utmCoord.geographic();
// Rotiert im Uhrzeigersinn um den Nullpunkt (0,0)
// double grad = (data.zieldaten.zielachse / 6400.) * 360;
// DEBUG_LOG(m_logger) << " milli rad to degree" <<(data.zieldaten.zielachse / 6400.) * 360;
// Rotiert im Uhrzeigersinn um den Nullpunkt (0,0)
// DEBUG_LOG(m_logger) << " mmilirad: " <<data.zieldaten.zielachse;
// double shussrichtung = effectorAdler.schussrichtung;
// DEBUG_LOG(m_logger) << " millii rad to degree" <<grad;
// use defatult degree from start to end! not target axis
// double shussrichtung = effectorAdler.schussrichtung;
QTransform matrix;
matrix.rotate(-grad);
@@ -393,12 +417,12 @@ void AesHandler::sendTrajectoryAdlerModifyRequest(const WirkfeldplanungData &dat
pntGeo.setX(rotatePoint.x());
pntGeo.setY(rotatePoint.y());
// DEBUG_LOG(m_logger) << "utmCoord :" << utmCoord.x << " / " << utmCoord.y << ", utmCoord.zone :" << utmCoord.zone
// << ", utmCoord.isValid() :" << utmCoord.isValid()
// << ", pntGeo :" << pntGeo.x() << " / " << pntGeo.y() << " / " << z;
// LS-669: Soweit ich das sehen kann, müssen hier relative Koordinaten übertragen werden.
const Core::RelativePositionRelativeTime relPos(pntGeo.x(), pntGeo.y(), z,
std::round(pos.zeit * 1000.0));
geometryRef.emplace_back(relPos);
@@ -430,44 +454,44 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
{
DEBUG_LOG(m_logger) << "AesHandler::handleEffectCluster (Empfang WirkfeldplanungData)"
<< ", feuerkampfReferenz :" << data.feuerkampfReferenz << ", wirkmittel.size() :" << data.feuerkommando.wirkmittel.size();
// here we convert the FQBB to create EffectCluster
if (!m_mainDispatcher->jsonDispatcher()->getSAAByWs(wsId))
{
ERROR_LOG(m_logger) << "!m_mainDispatcher->jsonDispatcher()->getSAAByWs(" << wsId << ") return";
return;
}
const QString &verein = data.feuerkommando.verein;
// #ifdef LTS_DEBUG
// const ParticipantManagement::Participant &part = m_dtcPpm->participant(89);
// DEBUG_LOG(m_logger) << "participantId :" << part.id()
// << ", partyId :" << part.partyId();
// #else
const int partyId = m_mainDispatcher->jsonDispatcher()->getSAAByWs(wsId)->getPartyId();
const bool isVerbund = m_mainDispatcher->jsonDispatcher()->getSAAByWs(wsId)->getIsVerbund();
const ParticipantManagement::Participant &part = m_mainDispatcher->ppmHandler()->findParticipantByInternalName(verein, partyId, isVerbund);
// #endif
if (part.id() <= 0)
{
ERROR_LOG(m_logger) << "Got EffectClusterData for " << verein << " but unit not found";
return;
}
// get overall impacts
m_wirkfeldPlanungsData = data;
m_participantId = part.id();
m_wirkmittelIndex = 0;
// for (const auto &wm : data.feuerkommando.wirkmittel)
// {
// impactCount += wm.geschoss.anzahl;
// mhpCount ++; //TODO we could get the correct point
// //chargeTypeId = // TODO MAP wm.geschoss.geschosssorte
// sendTrajectoryAdlerModifyRequest(data, part.id(), mhpCount);
// }
// The worst part about the SAA we don't even get all the data needed to create a usefull effect cluster.
// Sometimes we will get a geometry (e.g. for mortar) but most of the time the "Target area" is lost
// in that case we need to create our own area from the target points.. and probably "guess" the target geometry
AreaEffectSimulation::AesExplosionGeometry geometry = AreaEffectSimulation::AesExplosionGeometry::EgArea;
AreaEffectSimulation::Area area;
@@ -477,7 +501,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
if (!createArea && data.zieldaten.geometrie == ProjectDataManagement::L4_TargetGeometry::Point)
{
geometry = AreaEffectSimulation::AesExplosionGeometry::EgPoint;
// we need to set the area from pdm so we use the first actors charge type
const auto &chargeData = data.feuerkommando.wirkmittel.at(0).geschoss;
const int chargeTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getChargeTypeIdFromAdlerCharges(chargeData.geschosssorte);
@@ -492,27 +516,31 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
if (!createArea && data.zieldaten.geometrie == ProjectDataManagement::L4_TargetGeometry::Radial)
{
geometry = AreaEffectSimulation::AesExplosionGeometry::EgRadial;
// In Radial we need to double the radius and also set it as height/width
area.height(data.zieldaten.breiteRadius * 2);
area.width(data.zieldaten.breiteRadius * 2);
}
else if (!createArea)
else if (!createArea) // we need the rect or the line
{
// maye wrong?
// not only maybe.. we need to swap .. yes..
area.height(data.zieldaten.breiteRadius);
area.width(data.zieldaten.tiefe);
if (data.zieldaten.tiefe > 0)
area.width(data.zieldaten.tiefe);
else
if (data.zieldaten.geometrie == ProjectDataManagement::L4_TargetGeometry::Linear)
area.width(5); // E01562-472 [Verbund] ImpactWidth invalid. Ein Wirkfeld mit der Zielgeometrie "Linear" kann nicht verarbeitet werden.
}
if (!createArea)
{
area.rotation((data.zieldaten.zielachse / 6400.) * 360);
area.rotation((data.zieldaten.zielachse / 6400.) * 360); // UTM STRICH?
// point is center
const QPointF centerPoint(data.zieldaten.koordinate.koordinate.longitude * (M_PI / 180.0), data.zieldaten.koordinate.koordinate.latitude * (M_PI / 180.0));
QRectF rect(QPointF(), QSizeF(area.height(), area.width()));
rect.moveCenter(QPointF());
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
const QPointF bl = QTransform().rotate(-area.rotation()).map(rect.topLeft());
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerPoint);
DEBUG_LOG(m_logger) << "centerUtm="
@@ -539,7 +567,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
DEBUG_LOG(m_logger) << " Need to generate area SINGLE";
geometry = AreaEffectSimulation::AesExplosionGeometry::EgPoint;
// we need to set the area from pdm so we use the first actors charge type
const auto &wirkmittel = data.feuerkommando.wirkmittel.at(0);
const auto &chargeData = wirkmittel.geschoss;
@@ -553,12 +581,12 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
area.height(impactRadius * 2);
area.width(impactRadius * 2);
// point is center
const QPointF centerPoint(wirkmittel.auftreffpunkt.koordinate.longitude * (M_PI / 180.0), wirkmittel.auftreffpunkt.koordinate.latitude * (M_PI / 180.0));
QRectF rect(QPointF(), QSizeF(area.height(), area.width()));
rect.moveCenter(QPointF());
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
const QPointF bl = QTransform().rotate(-area.rotation()).map(rect.topLeft());
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerPoint);
DEBUG_LOG(m_logger) << "centerUtm="
@@ -580,10 +608,10 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
{
DEBUG_LOG(m_logger) << " Need to generate area AREA";
// Area even though thez might all target the same point
geometry = AreaEffectSimulation::AesExplosionGeometry::EgArea;
// Now we take the charge data from the first effector
// we need the impactradius to create an area
const auto &chargeData = data.feuerkommando.wirkmittel.at(0).geschoss;
const int chargeTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getChargeTypeIdFromAdlerCharges(chargeData.geschosssorte);
const int igniterTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterTypeIdFromAdlerIgniter(chargeData.zuender);
@@ -592,14 +620,14 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
DEBUG_LOG(m_logger) << "Impact Radius for " << chargeTypeId << " " << igniterTypeId << " " << igniterTypeId << " is " << impactRadius;
// Now that we know the impact radius we create a boundingbox around all impact areas
QList<AreaEffectSimulation::Area> areas;
QList<QRectF> rects;
int zone = 0;
QVector<QPointF> vecPoints;
for (const WirkmittelFeuerkommando &effector : std::as_const(data.feuerkommando.wirkmittel))
for (const WirkmittelFeuerkommando &effector : qAsConst(data.feuerkommando.wirkmittel))
{
// get the target point for the effector
const QPointF centerPoint(effector.auftreffpunkt.koordinate.longitude * (M_PI / 180.0), effector.auftreffpunkt.koordinate.latitude * (M_PI / 180.0));
AreaEffectSimulation::Area areaEffector;
@@ -608,7 +636,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
QRectF rect(QPointF(), QSizeF(areaEffector.height(), areaEffector.width()));
rect.moveCenter(QPointF());
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
const QPointF bl = QTransform().rotate(90).map(rect.topLeft());
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerPoint);
@@ -617,7 +645,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
areaEffector.cornerBottomLeft(Core::GeoPosition2D(bottomLeft.x(), bottomLeft.y()));
rect.moveCenter(QPointF(centerUtm.x, centerUtm.y));
// now add Area to list
areas.append(areaEffector);
rects.append(rect);
vecPoints.append(rect.topLeft());
@@ -626,29 +654,29 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
vecPoints.append(rect.bottomRight());
}
// Finally use the rerturning rect as Area
area = createMinimumRect(zone, vecPoints);
DEBUG_LOG(m_logger) << "FINAL AREA " << area.cornerBottomLeft() << " " << area.height() << " " << area.width();
}
else if (data.feuerkommando.wirkmittelRakete.size() > 0)
{
DEBUG_LOG(m_logger) << " Need to generate area AREA";
// Area even though thez might all target the same point
geometry = AreaEffectSimulation::AesExplosionGeometry::EgArea;
// Now we take the charge data from the first effector
// we need the impactradius to create an area
const auto &chargeData = data.feuerkommando.wirkmittelRakete.at(0).geschoss;
const int chargeTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getChargeTypeIdFromAdlerCharges(chargeData.geschosssorte);
const int igniterTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterTypeIdFromAdlerIgniter(chargeData.zuender);
const int igniterFunctionId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterFunctionIdFromAdlerIgniterFunction(chargeData.zuenderFunktion);
const int impactRadius = m_mainDispatcher->pdmHandler()->getDtc()->impactRadius(chargeTypeId, igniterTypeId, igniterFunctionId);
// Now that we know the impact radius we create a boundingbox around all impact areas
int zone = 0;
QVector<QPointF> vecPoints;
for (const WirkmittelFeuerkommandoRakete &effector : std::as_const(data.feuerkommando.wirkmittelRakete))
for (const WirkmittelFeuerkommandoRakete &effector : qAsConst(data.feuerkommando.wirkmittelRakete))
{
for (const Treffpunkt &treffpunkt : std::as_const(effector.treffpunkte))
for (const Treffpunkt &treffpunkt : qAsConst(effector.treffpunkte))
{
const QPointF centerPoint(treffpunkt.koordinate.koordinate.longitude * (M_PI / 180.0), treffpunkt.koordinate.koordinate.latitude * (M_PI / 180.0));
@@ -658,7 +686,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
QRectF rect(QPointF(), QSizeF(areaEffector.height(), areaEffector.width()));
rect.moveCenter(QPointF());
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
const QPointF bl = QTransform().rotate(90).map(rect.topLeft());
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerPoint);
@@ -667,7 +695,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
areaEffector.cornerBottomLeft(Core::GeoPosition2D(bottomLeft.x(), bottomLeft.y()));
rect.moveCenter(QPointF(centerUtm.x, centerUtm.y));
// now add Area to list
vecPoints.append(rect.topLeft());
vecPoints.append(rect.topRight());
vecPoints.append(rect.bottomLeft());
@@ -675,7 +703,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
}
}
// Finally use the returning rect as Area
area = createMinimumRect(zone, vecPoints);
DEBUG_LOG(m_logger) << "FINAL AREA " << area.cornerBottomLeft() << " " << area.height() << " " << area.width();
}
@@ -684,7 +712,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
AreaEffectSimulation::EffectClusterModifyRequest req;
for (const AreaEffectSimulation::EffectCluster &clus : m_dtcAes->effectClusters())
{
// ERROR_LOG(m_logger) << "Look for if: " << clus.element() << " is data: " << data.feuerkampfReferenz;
if (clus.partyId() != part.partyId())
continue;
if (clus.isDeleted())
@@ -704,19 +732,19 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
obj[QStringLiteral("useReceived")] = true;
obj[QStringLiteral("effectSource")] = ProjectDataManagement::EffectSource::ES_ADLER;
obj[AdlerJson::JsonKeys::ABFEUERUNGSART()] = static_cast<int>(data.abfeuerungsArt);
// TargetData is often only optional
obj[AdlerJson::JsonKeys::TYP()] = m_mainDispatcher->pdmHandler()->getDtc()->getTargetTypeIdFromAdlerTargetType(data.zieldaten.typ);
obj[AdlerJson::JsonKeys::SUB_TYP()] = m_mainDispatcher->pdmHandler()->getDtc()->getTargetSubTypeIdFromAdlerTargetSubType(data.zieldaten.subTyp);
obj[AdlerJson::JsonKeys::ELEMENTE()] = data.zieldaten.elemente;
obj[AdlerJson::JsonKeys::NUMMER()] = data.zieldaten.nummer;
obj[QStringLiteral("partyId")] = part.partyId();
// fill EffectorData
std::vector<AreaEffectSimulation::EffectorData> effectorVec;
if (data.feuerkommando.wirkmittel.size() > 0)
{
for (const auto &effectorAdler : std::as_const(data.feuerkommando.wirkmittel))
for (const auto &effectorAdler : qAsConst(data.feuerkommando.wirkmittel))
{
AreaEffectSimulation::EffectorData effectorData;
effectorData.angleOfFall() = 0;
@@ -750,10 +778,10 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
const double useNewGrad = -90 + std::atan2(coordDiff.y, coordDiff.x) * (180.0 / M_PI);
DEBUG_LOG(m_logger) << "Atan2 Deg to: last point " << std::atan2(coordDiff.y, coordDiff.x) * (180.0 / M_PI);
for (const auto &flightPointAdler : std::as_const(effectorAdler.flugbahnpunkt))
for (const auto &flightPointAdler : qAsConst(effectorAdler.flugbahnpunkt))
{
Core::RelativePositionRelativeTime flightPoint;
flightPoint.offsetX() = 0;
flightPoint.offsetX() = 0; // flightPointAdler.zRelativ;
flightPoint.offsetY() = flightPointAdler.xRelativ;
flightPoint.offsetZ() = flightPointAdler.yRelativ;
flightPoint.relativeTime() = flightPointAdler.zeit * 1000;
@@ -773,7 +801,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
}
effectorData.trajectoryRelative() = flightCurve;
effectorData.flightTime() = effectorAdler.flugzeit * 1000;
effectorData.chargeStruct().igniterTypeId() = 1;
effectorData.chargeStruct().igniterTypeId() = 1; // effectorAdler.geschoss.zuender; //XKCD
effectorData.interval() = effectorAdler.geschoss.intervall;
Core::GeoPosition mhpPos;
mhpPos.lat() = effectorAdler.auftreffpunkt.koordinate.latitude * (M_PI / 180.0);
@@ -791,14 +819,14 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
effectorData.peakHeight() = effectorAdler.gipfelhoehe;
effectorVec.push_back(effectorData);
// We will override with the last one but okay should be suffice
obj[AdlerJson::JsonKeys::LADUNG()] = m_mainDispatcher->pdmHandler()->getDtc()->getLoadTypeIdFromAdlerLoad(effectorAdler.geschoss.ladung);
obj[AdlerJson::JsonKeys::LADUNGSNUMMER()] = effectorAdler.geschoss.ladungsNummer;
}
}
else if (data.feuerkommando.wirkmittelRakete.size() > 0)
{
for (const auto &effectorAdler : std::as_const(data.feuerkommando.wirkmittelRakete))
for (const auto &effectorAdler : qAsConst(data.feuerkommando.wirkmittelRakete))
{
AreaEffectSimulation::EffectorData effectorData;
@@ -820,10 +848,10 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
effectorData.chargeStruct().igniterTypeId() = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterTypeIdFromAdlerIgniter(effectorAdler.geschoss.zuender);
effectorData.interval() = effectorAdler.geschoss.intervall;
// create centroid
Core::GeoPosition mhpPos;
QPointF centroid;
for (const auto &treffpunkt : std::as_const(effectorAdler.treffpunkte))
for (const auto &treffpunkt : qAsConst(effectorAdler.treffpunkte))
centroid += {treffpunkt.koordinate.koordinate.latitude, treffpunkt.koordinate.koordinate.longitude};
centroid /= effectorAdler.treffpunkte.size();
@@ -843,7 +871,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
AdlerJson::wirkmittelFeuerkommandoRaketeToJson(properties, effectorAdler);
effectorData.properties() = QJsonDocument(properties).toJson(QJsonDocument::Compact).toStdString();
// trajectory without time start ------------------------------------------
const ParticipantManagement::ParticipantPosition &participantPosition = m_dtcPpm->participantPosition(effectorData.participantId());
const LtsUtils::QPoint3D trajectoryStart(participantPosition.pos().lon(), participantPosition.pos().lat(), participantPosition.pos().z());
@@ -853,15 +881,15 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
if (!AreaEffectSimulation::TrajectoryGenerator::getInstance()->generateTrajectoryBiedziak(trajectoryStart, trajectoryEnd, 45.0, trajectoryWithoutTime))
ERROR_LOG(m_logger) << "Could not create trajectory for Feuerkommando " << data.feuerkommando.verein;
// trajectory without time end -------------------------------------------
// trajectory with time start -------------------------------------------
effectorData.flightTime() = effectorAdler.flugzeit * 1000;
const double flightTimeStep = effectorData.flightTime() / static_cast<double>(trajectoryWithoutTime.size());
double currentFlightTime = 0.0;
const LtsCoordTrans::UtmCoordinate utmStartPos = LtsCoordTrans::UtmCoordinate::fromGeographic(participantPosition.pos().lon(), participantPosition.pos().lat());
for (const Core::GeoPosition &pos : std::as_const(trajectoryWithoutTime))
for (const Core::GeoPosition &pos : qAsConst(trajectoryWithoutTime))
{
const LtsCoordTrans::UtmCoordinate utmCurrentPos = LtsCoordTrans::UtmCoordinate::fromGeographic(pos.lon(), pos.lat());
@@ -870,7 +898,7 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
currentFlightTime += flightTimeStep;
}
// trajectory with time end --------------------------------------------
obj[AdlerJson::JsonKeys::LADUNG()] = m_mainDispatcher->pdmHandler()->getDtc()->getLoadTypeIdFromAdlerLoad(effectorAdler.geschoss.ladung);
obj[AdlerJson::JsonKeys::LADUNGSNUMMER()] = effectorAdler.geschoss.ladungsNummer;
@@ -887,14 +915,14 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
req.effectMarkingInTa(true);
req.partyId(part.partyId());
req.explosionGeometry(geometry);
req.firePurposeId(static_cast<int>(data.forderung.underlying()));
req.firePurposeId(static_cast<int>(data.forderung.underlying())); // TODO Mapping
req.effectSourceId(ProjectDataManagement::EffectSource::ES_ADLER);
req.name(data.feuerkampfReferenz.toStdString());
req.element(data.feuerkampfReferenz.toStdString());
req.effectDuration(data.dauer * 1000);
req.participantId(part.id());
req.externalId(7);
req.tla(data.zieldaten.zog);
req.tla(data.zieldaten.zog); // defaults to 0 if not set
if (data.feuerkommando.wirkmittel.empty())
DEBUG_LOG(m_logger) << "flugzeit :"
@@ -907,9 +935,9 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
std::vector<ProjectDataManagement::L27_EffectorId> refWirkmittel;
refWirkmittel.reserve(data.feuerkommando.wirkmittel.size());
for (const auto &wm : std::as_const(data.feuerkommando.wirkmittel))
for (const auto &wm : qAsConst(data.feuerkommando.wirkmittel))
{
ammoCounter += wm.geschoss.anzahl;
ammoCounter += wm.geschoss.anzahl; // Counts every Ammo that will be shooted
DEBUG_LOG(m_logger) << "INSERT KENNER INTO MAP: " << data.feuerkampfReferenz << "---------" << wm.kenner;
refWirkmittel.push_back(wm.kenner);
}
@@ -923,18 +951,18 @@ void AesHandler::handleEffectCluster(const WirkfeldplanungData &data, int wsId)
QList<qint32> AesHandler::findAllPendingEffectCluster(int partyId)
{
// QList<AreaEffectSimulation::EffectCluster> ret;
QList<qint32> ret;
const auto &effectClusters = m_dtcAes->effectClusters();
for (const auto &effectCluster : std::as_const(effectClusters))
for (const auto &effectCluster : qAsConst(effectClusters))
{
if (effectCluster.partyId() != partyId)
continue;
if (effectCluster.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsReceived
&& effectCluster.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsCreated)
continue;
if (effectCluster.effectSourceId() != ProjectDataManagement::EffectSource_def::ES_ADLER
)
if (effectCluster.effectSourceId() != ProjectDataManagement::EffectSource_def::ES_ADLER /*&&
effectCluster.effectSourceId() != ProjectDataManagement::EffectSource_def::ES_ADLER_STANDALONE*/)
continue;
ret.append(effectCluster.id());
@@ -942,8 +970,20 @@ QList<qint32> AesHandler::findAllPendingEffectCluster(int partyId)
return ret;
}
/**
* * handleAmmoCount handhabt die Munitionszählung (AmmoCount).
* TODO: Behebung des Persistenz-Problems:
* Aktuell gehen die Map-Daten bei einem Service-Restart verloren.
* ! 'm_feuerkampReferenzAmmoCounter' ist nach Restart leer.
* Dies kann zu Inkonsistenzen führen, falls die SAA eine VM (Vollzugsmeldung) erwartet,
* der Zählerstand aber zurückgesetzt wurde.
* ? Refactoring-Option:
* Sobald der 'EffectCluster' die reale Munition der Adler-Bekämpfungspläne führt,
* sollte diese Map durch den 'EffectCluster'-Counter ersetzt werden, um eine
* "Single Source of Truth" zu gewährleisten.
*
* STAND (16.04.2026)
*/
void AesHandler::handleAmmoCount(const Feuerkampfdaten &data, int saaPartyId, bool sendExVollzug)
{
const QString refTrimmed = data.feuerkampfReferenz.trimmed();
@@ -953,7 +993,7 @@ void AesHandler::handleAmmoCount(const Feuerkampfdaten &data, int saaPartyId, bo
const auto &aesEffetClusters = m_dtcAes->effectClusters();
for (const auto &aes : std::as_const(aesEffetClusters))
for (const auto &aes : qAsConst(aesEffetClusters))
{
if (aes.isDeleted() || aes.partyId() != saaPartyId || aes.element() != refTrimmed.toStdString())
{
@@ -984,7 +1024,7 @@ void AesHandler::handleAmmoCount(const Feuerkampfdaten &data, int saaPartyId, bo
{
auto refKenners = m_feuerkampfReferenzWirkmittel.value(refTrimmed);
for (const auto &kenner : std::as_const(refKenners))
for (const auto &kenner : qAsConst(refKenners))
{
ExVollzugsmeldung exvm;
exvm.feuerkampfdaten.feuerkampfReferenz = data.feuerkampfReferenz;
@@ -1000,7 +1040,7 @@ void AesHandler::handleAmmoCount(const Feuerkampfdaten &data, int saaPartyId, bo
req.id(aes.id());
m_ftcAes->effectClusterRemoveRequest(req);
// After remove effectCluster remove FeuerkampfReferenz from Maps
m_feuerkampfReferenzUsedFeuerHalt.remove(refTrimmed);
m_feuerkampfReferenzWirkmittel.remove(refTrimmed);
m_feuerkampReferenzAmmoCounter.remove(refTrimmed);
@@ -1016,7 +1056,7 @@ void AesHandler::removeAllPendingEffectCluster(int partyId)
INFO_LOG(m_logger) << "Pending effect cluster size: " << effectClusterIds.size();
Core::RemoveElementRequest req;
for (const qint32 id : std::as_const(effectClusterIds))
for (const qint32 id : qAsConst(effectClusterIds))
{
req.id(id);
INFO_LOG(m_logger) << "Remove effect cluster id: " << id;
@@ -1076,10 +1116,10 @@ void AesHandler::onExplosionSingleChanged(const AreaEffectSimulation::ExplosionS
QJsonObject jsonData;
jsonData[QStringLiteral("header")] = headerJson;
jsonData[QStringLiteral("impactPosition")] = impactPosition;
jsonData[QStringLiteral("impactPosition")] = impactPosition; // detonationLocation
if (data.participantId().is_set())
{
jsonData[QStringLiteral("participantId")] = data.participantId().get();
jsonData[QStringLiteral("participantId")] = data.participantId().get(); // firingObjectIdentifier
}
telegram[QStringLiteral("data")] = jsonData;
@@ -1121,20 +1161,20 @@ void AesHandler::onEffectClusterChanged(const AreaEffectSimulation::EffectCluste
{
case AreaEffectSimulation::AesEffectState::EsCreated:
{
// Auftragsannahme (manuelle Quittung)
// Could be send via notification managment??
if (m_aesIdToFeuerkampfReferenz.contains(data.id()))
{
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(data.properties())).object();
const QString reference = probObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString();
ExMqManuelleQuittung exmq;
// exmq.mqManuelleQuittungDaten.feuerkampfreferenz = QString::fromStdString(data.element());
exmq.mqManuelleQuittungDaten.feuerkampfreferenz = reference;
m_mainDispatcher->jsonDispatcher()->sendExMqManuelleQuittung(exmq, data.partyId().get());
// After that we'll remove it form the list to not send an MQ again!
m_aesIdToFeuerkampfReferenz.remove(data.id());
DEBUG_LOG(m_logger) << "SEND MQ for " << data.name() << " " << reference;
break;
@@ -1142,11 +1182,11 @@ void AesHandler::onEffectClusterChanged(const AreaEffectSimulation::EffectCluste
}
case AreaEffectSimulation::AesEffectState::EsReceived:
case AreaEffectSimulation::AesEffectState::EsPrepared:
case AreaEffectSimulation::AesEffectState::EsFired:
case AreaEffectSimulation::AesEffectState::EsExpired:
case AreaEffectSimulation::AesEffectState::EsFired: // EsFired! will be handled by fired topic
case AreaEffectSimulation::AesEffectState::EsExpired: // EX Expired is not for ADLER it just need the ConfirmationReport
default:
return;
}
}
}
} // namespace AdlerJson