/** * @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 #include "DDS/Core/function_reply_object.h" #include "aes.hpp" #include "json_dispatcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace AdlerJson { AesHandler::AesHandler(std::shared_ptr ftsTab, QObject *parent) : QObject(parent) , m_requestHelper(new LtsUtils::RequestHelper(this)) , m_logger(getLogger("AesHandler")) { Q_UNUSED(ftsTab) } bool AesHandler::init(MainDispatcher *mainDispatcher) { m_mainDispatcher = mainDispatcher; m_ftcAes = AreaEffectSimulation::Interface::Ftc::create(mainDispatcher->ddsHandler()->ddsConnector()); m_dtcAes = AreaEffectSimulation::Interface::Dtc::create(mainDispatcher->ddsHandler()->ddsConnector()); m_dtcPpm = ParticipantManagement::Interface::Dtc::create(mainDispatcher->ddsHandler()->ddsConnector()); m_ddsHandler = mainDispatcher->ddsHandler(); if (!m_ftcAes) return false; 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 ------- "; connect(m_dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::explosionSingleChanged, this, &AesHandler::onExplosionSingleChanged); connect(m_dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::effectClusterChanged, this, &AesHandler::onEffectClusterChanged); return true; } void AesHandler::onOffRouteMineModifyReply(const Core::FunctionReplyObject &replyObject) { DEBUG_LOG(m_logger) << "onOffRouteMineModifyReply" << ", replyObject.id :" << replyObject.id() << ", str :" << replyObject.str() << ", tanslated :" << replyObject.translated(); }; void AesHandler::onEffectClusterModifyReply(const Core::FunctionReplyObject &replyObject) { DEBUG_LOG(m_logger) << "onEffectClusterModifyReply" << ", replyObject.id :" << replyObject.id() << ", str :" << replyObject.str() << ", tanslated :" << replyObject.translated() << ", additinal Info :" << replyObject.additionalInfo(); m_requestHelper->requestReply(replyObject); DEBUG_LOG(m_logger) << "Got Reply"; 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; } QJsonObject propertiesObject = QJsonDocument::fromJson(QByteArray::fromStdString(replyObject.additionalInfo())).object(); ERROR_LOG(m_logger) << "Try send notification0"; m_aesId = replyObject.id(); ProjectDataManagement::EffectSource effectSource = static_cast(propertiesObject.value(QStringLiteral("effectSource")).toInt()); ERROR_LOG(m_logger) << "Try send notification1 " << effectSource; if (effectSource != ProjectDataManagement::EffectSource::ES_ADLER) return; QString feuerkampfReferenz = propertiesObject.value(QStringLiteral("feuerkampfReferenz")).toString(); int partyId = propertiesObject.value(QStringLiteral("partyId")).toInt(); 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
[Open](//ExplosionGui/Adler/%2)")); QStringList argsList; argsList.append(m_wirkfeldPlanungsData.feuerkommando.verein); argsList.append(QString::number(replyObject.id())); std::vector args; args.reserve(argsList.size()); for (const auto &str : std::as_const(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); if (saa) { 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 recipentVec; // Honestly... I don't know if this is the correct Right? but will do for now const QVector recipientIds = m_mainDispatcher->ddsHandler()->dtcOpm()->getNotificationRecipents(m_mainDispatcher->exerciseId(), partyId, OperatorRights::AdlerExplosionClusterEdit, static_cast(req.notification.notificationType)); std::vector recipientIdsDds; recipientIdsDds.reserve(recipientIds.size()); for (const auto &recipientId : std::as_const(recipientIds)) { NotificationManagement::Address addr; addr.addressId = recipientId; addr.isRead = false; const OperatorManagement::User user = m_mainDispatcher->ddsHandler()->dtcOpm()->user(recipientId); addr.name = user.name; addr.addressType = NotificationManagement::AddressType::Operator; recipentVec.emplace_back(addr); } req.notification.recipientIds = recipentVec; if (req.notification.recipientIds.size() > 0) { m_mainDispatcher->ddsHandler()->ftcNom()->createNotificationRequest(req, this); } 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) { if (replyObject.error()) ERROR_LOG(m_logger) << "Got onCreateNotificationReply ERROR: " << replyObject.translated(); } bool AesHandler::sendAesModifyRequest(const QJsonObject &data) { int equipmentNumber = data[QStringLiteral("header")].toObject()[QStringLiteral("equipmentNumber")].toInt(); AreaEffectSimulation::AirSupportImpactModifyRequest request; request.chargeTypeId = data[QStringLiteral("chargeTypeId")].toInt(); request.externalId = data[QStringLiteral("eventId")].toInt(); request.name = data[QStringLiteral("name")].toString().toStdString(); request.impactPosition = Core::GeoPosition2D(data[QStringLiteral("impactPosition")].toObject()[QStringLiteral("lon")].toDouble(), data[QStringLiteral("impactPosition")].toObject()[QStringLiteral("lat")].toDouble()); request.flightTime = 0; qint64 requestedTime = static_cast(data[QStringLiteral("header")].toObject()[QStringLiteral("timeStamp")].toDouble()); if (requestedTime < m_mainDispatcher->ddsHandler()->partitionTime()) requestedTime = m_mainDispatcher->ddsHandler()->partitionTime() + 500; request.effectStart = requestedTime; QJsonDocument doc(data); if (equipmentNumber) request.participantId = m_dtcPpm->participantIdByEquipmentNumber(equipmentNumber); m_requestHelper->startSingleRequest([this, request]() { return m_ftcAes->airSupportImpactModifyRequest(request, this); }); return true; } bool AesHandler::sendExplosionArea(const QJsonObject &data) { Q_UNUSED(data) AreaEffectSimulation::OffRouteMine orm; orm.id = 0; orm.name = QString(QStringLiteral("test_offRouteMine_02")).toStdString(); orm.chargeStruct.chargeTypeId = 254; orm.effectDuration = 6000; orm.partyId = 2; orm.impactRadius = 10; orm.area.cornerBottomLeft = Core::GeoPosition2D(0, 0); orm.area.height = 50; orm.area.width = 1; orm.area.rotation = 85; orm.effectStart = (time(nullptr) + 10) * 1000; AreaEffectSimulation::OffRouteMineModifyRequest req(orm.id, orm.name, orm.effectDuration, orm.chargeStruct.chargeTypeId, orm.area, orm.impactRadius, orm.effectStart, orm.partyId); m_ftcAes->offRouteMineModifyRequest(req, this); return true; } void AesHandler::onAirSupportImpactModifyReply(const Core::FunctionReplyObject &reply) { m_requestHelper->requestReply(reply); } void AesHandler::handleCreateSingleExplosion(const TrainingAreaBase::TetraIcd::CreateSingleExplosion &data) { auto strFromObj = QJsonDocument(data.toJson()).toJson(QJsonDocument::Compact); m_singleExplosions.insert(data.aesDdsId, AreaEffectSimulation::AesEffectState::EsCreated); DEBUG_LOG(m_logger) << "TEST SINGLE EXPLOSION CREATED " << strFromObj; } void AesHandler::handleEXFire(const Feuerkampfdaten &data, int wsId) { if (!m_mainDispatcher->jsonDispatcher()->getSAAByWs(wsId)) return; auto saa = m_mainDispatcher->jsonDispatcher()->getSAAByWs(wsId); int particpantId = m_dtcPpm->getParticipantByInternalName(saa->getPartyId(), data.verein, waffensystemkennerMap.key(data.kenner)).id; TrainingAreaBase::EX_Fire outData; outData.equipmentNumber = particpantId; outData.reference = data.feuerkampfReferenz.toStdString(); outData.identifier = data.verein.toStdString(); outData.effectorId = data.kenner; // static_cast((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(), dds::core::optional(), dds::core::optional()) , this); */ } void AesHandler::handleEXDontFire(const Feuerkampfdaten &data, int wsId) { if (!m_mainDispatcher->jsonDispatcher()->getSAAByWs(wsId)) return; const auto saa = m_mainDispatcher->jsonDispatcher()->getSAAByWs(wsId); DEBUG_LOG(m_logger) << "[EX-FQ(nFN)] data from SSA " << wsId << " : " << "verein: " << data.verein << " " << "kenner: " << data.kenner << " " << "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; } void AesHandler::onTrajectoryAdlerModifyReply(const Core::FunctionReplyObject &reply) { DEBUG_LOG(m_logger) << "onTrajectoryAdlerModifyReply" << ", aesId :" << reply.id() << ", str :" << reply.str() << ", m_wirkmittelIndex :" << m_wirkmittelIndex << ", m_data.feuerkommando.wirkmittel.size() :" << m_wirkfeldPlanungsData.feuerkommando.wirkmittel.size(); if (reply.error()) 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) { m_wirkmittelIndex++; DEBUG_LOG(m_logger) << "sendTrajectoryAdlerModifyRequest" << ", m_participantId :" << m_participantId << ", m_wirkmittelIndex :" << m_wirkmittelIndex << ", m_aesId :" << m_aesId; sendTrajectoryAdlerModifyRequest(m_wirkfeldPlanungsData, m_participantId, m_wirkmittelIndex, m_aesId); } } void AesHandler::sendTrajectoryAdlerModifyRequest(const WirkfeldplanungData &data, int participantId, int wirkmittelIndex, int aesId) { DEBUG_LOG(m_logger) << "sendTrajectoryAdlerModifyRequest" << ", participantId :" << participantId << ", wirkmittelIndex :" << wirkmittelIndex << ", aesId:" << aesId; std::vector geometryRef; geometryRef.clear(); geometryRef.reserve(data.feuerkommando.wirkmittel.at(wirkmittelIndex).flugbahnpunkt.size()); QVector subordinates = m_dtcPpm->getSubordinatesByParticipant(participantId); QVector subordinatePositions; for (int subordinate : std::as_const(subordinates)) { DEBUG_LOG(m_logger) << "subordinates(" << participantId << ") :" << subordinate; subordinatePositions.append(m_dtcPpm->participantPosition(subordinate)); } AreaEffectSimulation::TrajectoryAdlerModifyRequest *newRequest = new AreaEffectSimulation::TrajectoryAdlerModifyRequest(); newRequest->aesItemId = aesId; newRequest->explosionIndex = wirkmittelIndex; int zone = -1; Core::GeoPosition positionLaunch = subordinatePositions.at(wirkmittelIndex).pos; auto utmLaunch = LtsCoordTrans::UtmCoordinate::fromGeographic(positionLaunch.lon, positionLaunch.lat, zone); double grad = (data.zieldaten.zielachse / 6400.) * 360.0; for (Flugbahnpunkt pos : std::as_const(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: " <impactPosition = impactPosition; newRequest->geometry = geometryRef; DEBUG_LOG(m_logger) << "geometryRef.size=" << geometryRef.size() << ", newRequest->geometry().size() :" << newRequest->geometry.size(); if (newRequest->aesItemId <= 0) { ERROR_LOG(m_logger) << "newRequest->aesItemId() <=0" << ", aesItemId :" << newRequest->aesItemId; return; } newRequest->peakAltitude = data.feuerkommando.wirkmittel.at(wirkmittelIndex).gipfelhoehe; newRequest->mhpScatterLenght = data.feuerkommando.wirkmittel.at(wirkmittelIndex).streuung.laengsstreuung; newRequest->mhpScatterWidth = data.feuerkommando.wirkmittel.at(wirkmittelIndex).streuung.seitenstreuung; newRequest->mhpScatterLengthDetonationHeight = data.feuerkommando.wirkmittel.at(wirkmittelIndex).streuung.laengsstreuungH; newRequest->mhpScatterWidthDetonationHeight = data.feuerkommando.wirkmittel.at(wirkmittelIndex).streuung.seitenstreuungH; m_ftcAes->trajectoryAdlerModifyRequest(*newRequest, this); } 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; const bool createArea = data.zieldaten.breiteRadius == 0 && data.zieldaten.tiefe == 0; ERROR_LOG(m_logger) << "create AREA??? " << createArea << " breite " << data.zieldaten.breiteRadius << " tiefe " << data.zieldaten.tiefe; 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); 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); DEBUG_LOG(m_logger) << "Impact Radius for " << chargeTypeId << " " << igniterTypeId << " " << igniterTypeId << " is " << impactRadius; area.height = impactRadius * 2; area.width = impactRadius * 2; } 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) // we need the rect or the line { // maye wrong? // not only maybe.. we need to swap .. yes.. area.height = data.zieldaten.breiteRadius; 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; // 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=" << ", x :" << centerUtm.x << ", y :" << centerUtm.y << ", zone :" << centerUtm.zone; const QPointF &bottomLeft = centerUtm.geographic(bl); DEBUG_LOG(m_logger) << "bl=" << ", x :" << bl.x() << ", y :" << bl.y(); DEBUG_LOG(m_logger) << "bottomLeft=" << ", x :" << bottomLeft.x() << ", y :" << bottomLeft.y(); area.cornerBottomLeft = Core::GeoPosition2D(bottomLeft.x(), bottomLeft.y()); DEBUG_LOG(m_logger) << "area.cornerBottomLeft=" << ", lon :" << area.cornerBottomLeft.lon << ", lat :" << area.cornerBottomLeft.lat; const LtsCoordTrans::UtmCoordinate cornerBottomLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(area.cornerBottomLeft.lon, area.cornerBottomLeft.lat); DEBUG_LOG(m_logger) << "cornerBottomLeftUtm=" << ", x :" << cornerBottomLeftUtm.x << ", y :" << cornerBottomLeftUtm.y << ", zone :" << cornerBottomLeftUtm.zone; } else { DEBUG_LOG(m_logger) << " Need to generate area"; if (data.feuerkommando.wirkmittel.size() == 1) { 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; 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); DEBUG_LOG(m_logger) << "Impact Radius for " << chargeTypeId << " " << igniterTypeId << " " << igniterTypeId << " is " << impactRadius; 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=" << ", x :" << centerUtm.x << ", y :" << centerUtm.y << ", zone :" << centerUtm.zone; const QPointF bottomLeft = centerUtm.geographic(bl); DEBUG_LOG(m_logger) << "bl=" << ", x :" << bl.x() << ", y :" << bl.y(); DEBUG_LOG(m_logger) << "bottomLeft=" << ", x :" << bottomLeft.x() << ", y :" << bottomLeft.y(); area.cornerBottomLeft = Core::GeoPosition2D(bottomLeft.x(), bottomLeft.y()); DEBUG_LOG(m_logger) << "area.cornerBottomLeft=" << ", lon :" << area.cornerBottomLeft.lon << ", lat :" << area.cornerBottomLeft.lat; const LtsCoordTrans::UtmCoordinate cornerBottomLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(area.cornerBottomLeft.lon, area.cornerBottomLeft.lat); ERROR_LOG(m_logger) << "cornerBottomLeftUtm=" << ", x :" << cornerBottomLeftUtm.x << ", y :" << cornerBottomLeftUtm.y << ", zone :" << cornerBottomLeftUtm.zone; } else if (data.feuerkommando.wirkmittel.size() > 1) { 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); const int igniterFunctionId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterFunctionIdFromAdlerIgniterFunction(chargeData.zuenderFunktion); const int impactRadius = m_mainDispatcher->pdmHandler()->getDtc()->impactRadius(chargeTypeId, igniterTypeId, igniterFunctionId); 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 areas; QList rects; int zone = 0; QVector vecPoints; for (const WirkmittelFeuerkommando &effector : std::as_const(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; areaEffector.height = impactRadius * 2; areaEffector.width = impactRadius * 2; 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); zone = centerUtm.zone; const QPointF bottomLeft = centerUtm.geographic(bl); 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()); vecPoints.append(rect.topRight()); vecPoints.append(rect.bottomLeft()); 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 vecPoints; for (const WirkmittelFeuerkommandoRakete &effector : std::as_const(data.feuerkommando.wirkmittelRakete)) { for (const Treffpunkt &treffpunkt : std::as_const(effector.treffpunkte)) { const QPointF centerPoint(treffpunkt.koordinate.koordinate.longitude * (M_PI / 180.0), treffpunkt.koordinate.koordinate.latitude * (M_PI / 180.0)); AreaEffectSimulation::Area areaEffector; areaEffector.height = impactRadius * 2; areaEffector.width = impactRadius * 2; 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); zone = centerUtm.zone; const QPointF bottomLeft = centerUtm.geographic(bl); 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()); vecPoints.append(rect.bottomRight()); } } // Finally use the returning rect as Area area = createMinimumRect(zone, vecPoints); DEBUG_LOG(m_logger) << "FINAL AREA " << area.cornerBottomLeft << " " << area.height << " " << area.width; } } 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) continue; if (clus.effectStateId != AreaEffectSimulation::AesEffectState::EsPrepared && clus.effectStateId != AreaEffectSimulation::AesEffectState::EsCreated && clus.effectStateId != AreaEffectSimulation::AesEffectState::EsReceived) continue; if (clus.element != data.feuerkampfReferenz.trimmed().toStdString()) continue; req.id = clus.id; } QJsonObject obj; obj[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()] = data.feuerkampfReferenz; obj[QStringLiteral("useReceived")] = true; obj[QStringLiteral("effectSource")] = static_cast(ProjectDataManagement::EffectSource::ES_ADLER); obj[AdlerJson::JsonKeys::ABFEUERUNGSART()] = static_cast(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 effectorVec; if (data.feuerkommando.wirkmittel.size() > 0) { for (const auto &effectorAdler : std::as_const(data.feuerkommando.wirkmittel)) { AreaEffectSimulation::EffectorData effectorData; effectorData.angleOfFall = 0; effectorData.chargeAmount = effectorAdler.geschoss.anzahl; effectorData.chargeStruct.chargeTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getChargeTypeIdFromAdlerCharges(effectorAdler.geschoss.geschosssorte); effectorData.chargeStruct.igniterTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterTypeIdFromAdlerIgniter(effectorAdler.geschoss.zuender); effectorData.chargeStruct.igniterFunctionId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterFunctionIdFromAdlerIgniterFunction(effectorAdler.geschoss.zuenderFunktion); DEBUG_LOG(m_logger) << "FOUND chargeTypeId " << effectorData.chargeStruct.chargeTypeId << " for " << effectorAdler.geschoss.geschosssorte; DEBUG_LOG(m_logger) << "igniterTypeId: " << effectorData.chargeStruct.igniterTypeId; DEBUG_LOG(m_logger) << "igniterFunctionId: " << effectorData.chargeStruct.igniterFunctionId; effectorData.participantId = m_dtcPpm->getParticipantByInternalName(part.partyId, QString::fromStdString(part.name), waffensystemkennerMap.key(effectorAdler.kenner)).id; std::vector flightCurve; flightCurve.reserve(effectorAdler.flugbahnpunkt.size()); const auto &startPos = m_dtcPpm->participantPosition(effectorData.participantId); const auto coordStart = LtsCoordTrans::UtmCoordinate::fromGeographic(startPos.pos.lon, startPos.pos.lat); DEBUG_LOG(m_logger) << "Start point: " << coordStart; const auto coordEnd = LtsCoordTrans::UtmCoordinate::fromGeographic(effectorAdler.auftreffpunkt.koordinate.longitude * (M_PI / 180.0), effectorAdler.auftreffpunkt.koordinate.latitude * (M_PI / 180.0)); DEBUG_LOG(m_logger) << "End point: " << coordEnd; LtsCoordTrans::UtmCoordinate coordDiff = coordEnd; coordDiff.x = coordDiff.x - coordStart.x; coordDiff.y = coordDiff.y - coordStart.y; DEBUG_LOG(m_logger) << "Last point: " << coordDiff.x << " " << coordDiff.y; DEBUG_LOG(m_logger) << "Last point ATAN: " << std::atan2(coordDiff.y, coordDiff.x); 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)) { Core::RelativePositionRelativeTime flightPoint; flightPoint.offsetX = 0; // flightPointAdler.zRelativ; flightPoint.offsetY = flightPointAdler.xRelativ; flightPoint.offsetZ = flightPointAdler.yRelativ; flightPoint.relativeTime = flightPointAdler.zeit * 1000; QTransform matrix; matrix.rotate(useNewGrad); const QPointF point(flightPoint.offsetX, flightPoint.offsetY); TRACE_LOG(m_logger) << " POS bevore rot: " << point; const auto rotatePoint = matrix.map(point); TRACE_LOG(m_logger) << " POS after rot: " << rotatePoint; flightPoint.offsetX = rotatePoint.x(); flightPoint.offsetY = rotatePoint.y(); flightCurve.push_back(flightPoint); } effectorData.trajectoryRelative = flightCurve; effectorData.flightTime = effectorAdler.flugzeit * 1000; 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); mhpPos.lon = effectorAdler.auftreffpunkt.koordinate.longitude * (M_PI / 180.0); mhpPos.z = effectorAdler.auftreffpunkt.hoehe; effectorData.mhpPosition = mhpPos; effectorData.mhpRotation = 90.0 - (effectorAdler.schussrichtung / 6400.) * 360; effectorData.mhpScatterLenght = effectorAdler.streuung.laengsstreuung; effectorData.mhpScatterLengthDetonationHeight = effectorAdler.streuung.laengsstreuungH; effectorData.mhpScatterWidth = effectorAdler.streuung.seitenstreuung; effectorData.mhpScatterWidthDetonationHeight = effectorAdler.streuung.seitenstreuungH; DEBUG_LOG(m_logger) << effectorAdler.kenner; DEBUG_LOG(m_logger) << "Looking for participantID with stats = party: " << part.partyId << " unitname: " << part.name << " effectorname: " << waffensystemkennerMap.key(effectorAdler.kenner); 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)) { AreaEffectSimulation::EffectorData effectorData; effectorData.chargeAmount = effectorAdler.geschoss.anzahl; effectorData.angleOfFall = effectorAdler.fallwinkel; effectorData.chargeStruct.chargeTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getChargeTypeIdFromAdlerCharges(effectorAdler.geschoss.geschosssorte); effectorData.chargeStruct.igniterTypeId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterTypeIdFromAdlerIgniter(effectorAdler.geschoss.zuender); effectorData.chargeStruct.igniterFunctionId = m_mainDispatcher->pdmHandler()->getDtc()->getIgniterFunctionIdFromAdlerIgniterFunction(effectorAdler.geschoss.zuenderFunktion); DEBUG_LOG(m_logger) << "FOUND chargeTypeId " << effectorData.chargeStruct.chargeTypeId << " for " << effectorAdler.geschoss.geschosssorte; DEBUG_LOG(m_logger) << "igniterTypeId: " << effectorData.chargeStruct.igniterTypeId; DEBUG_LOG(m_logger) << "igniterFunctionId: " << effectorData.chargeStruct.igniterFunctionId; effectorData.participantId = m_dtcPpm->getParticipantByInternalName(part.partyId, QString::fromStdString(part.name), waffensystemkennerMap.key(effectorAdler.kenner)).id; if (effectorData.participantId == 0) WARNING_LOG(m_logger) << "Could not find participant for partiyId " << part.partyId << ", name " << part.name << ", kenner " << effectorAdler.kenner; 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)) centroid += {treffpunkt.koordinate.koordinate.latitude, treffpunkt.koordinate.koordinate.longitude}; centroid /= effectorAdler.treffpunkte.size(); mhpPos.lat = centroid.x() * (M_PI / 180.0); mhpPos.lon = centroid.y() * (M_PI / 180.0); mhpPos.z = effectorAdler.treffpunkte.at(0).koordinate.hoehe; effectorData.mhpPosition = mhpPos; effectorData.mhpRotation = 90.0 - (effectorAdler.schussrichtung / 6400.) * 360; effectorData.mhpScatterLenght = 0.0; effectorData.mhpScatterLengthDetonationHeight = 0.0; effectorData.mhpScatterWidth = 0.0; effectorData.mhpScatterWidthDetonationHeight = 0.0; effectorData.peakHeight = effectorAdler.gipfelhoehe; QJsonObject properties; 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); const LtsUtils::QPoint3D trajectoryEnd(effectorData.mhpPosition.lon, effectorData.mhpPosition.lat, effectorData.mhpPosition.z); AreaEffectSimulation::TrajectoryGeoPosition trajectoryWithoutTime; 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(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)) { const LtsCoordTrans::UtmCoordinate utmCurrentPos = LtsCoordTrans::UtmCoordinate::fromGeographic(pos.lon, pos.lat); effectorData.trajectoryRelative.emplace_back(utmCurrentPos.x - utmStartPos.x, utmCurrentPos.y - utmStartPos.y, pos.z, currentFlightTime); 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; effectorVec.push_back(effectorData); } } req.effectors = effectorVec; QString propertiesStr = QString::fromStdString(QJsonDocument(obj).toJson(QJsonDocument::Compact).toStdString()); std::string properties = propertiesStr.toStdString(); req.properties = properties; req.area = area; req.effectMarkingInTa = true; req.partyId = part.partyId; req.explosionGeometry = geometry; req.firePurposeId = static_cast(data.forderung); // 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; // defaults to 0 if not set if (data.feuerkommando.wirkmittel.empty()) DEBUG_LOG(m_logger) << "flugzeit :" << " -"; else DEBUG_LOG(m_logger) << "flugzeit :" << data.feuerkommando.wirkmittel.at(0).flugzeit; DEBUG_LOG(m_logger) << "SEND " << data.feuerkampfReferenz << " "; int ammoCounter = 0; std::vector refWirkmittel; refWirkmittel.reserve(data.feuerkommando.wirkmittel.size()); for (const auto &wm : std::as_const(data.feuerkommando.wirkmittel)) { 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); } m_feuerkampReferenzAmmoCounter.insert(data.feuerkampfReferenz, ammoCounter); m_feuerkampfReferenzWirkmittel.insert(data.feuerkampfReferenz, refWirkmittel); m_feuerkampfReferenzUsedFeuerHalt.insert(data.feuerkampfReferenz, false); m_ftcAes->effectClusterModifyRequest(req, this); } QList AesHandler::findAllPendingEffectCluster(int partyId) { // QList ret; QList ret; const auto &effectClusters = m_dtcAes->effectClusters(); for (const auto &effectCluster : std::as_const(effectClusters)) { if (effectCluster.partyId != partyId) continue; if (effectCluster.effectStateId != AreaEffectSimulation::AesEffectState::EsReceived && effectCluster.effectStateId != AreaEffectSimulation::AesEffectState::EsCreated) continue; if (effectCluster.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER /*&& effectCluster.effectSourceId() != ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE*/) continue; ret.append(effectCluster.id); } 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(); if (!m_feuerkampReferenzAmmoCounter.contains(refTrimmed)) return; const auto &aesEffetClusters = m_dtcAes->effectClusters(); for (const auto &aes : std::as_const(aesEffetClusters)) { if (aes.isDeleted || aes.partyId != saaPartyId || aes.element != refTrimmed.toStdString()) { continue; } int refAmmoCounter = 0; refAmmoCounter = m_feuerkampReferenzAmmoCounter.value(refTrimmed, -1); bool outOfAmmo = false; const int newAmmoAmount = std::max(0, refAmmoCounter - 1); DEBUG_LOG(m_logger) << "Handle ammo count - ID: " << aes.id << " | Ref: " << refTrimmed << " | PartyID: " << saaPartyId << " | oldAmmoCount: " << refAmmoCounter << " | newAmmoCount: " << newAmmoAmount; if (newAmmoAmount <= 0) { m_feuerkampReferenzAmmoCounter.remove(refTrimmed); } else { m_feuerkampReferenzAmmoCounter[refTrimmed] = newAmmoAmount; } outOfAmmo = (newAmmoAmount == 0); if (outOfAmmo && sendExVollzug) { auto refKenners = m_feuerkampfReferenzWirkmittel.value(refTrimmed); for (const auto &kenner : std::as_const(refKenners)) { ExVollzugsmeldung exvm; exvm.feuerkampfdaten.feuerkampfReferenz = data.feuerkampfReferenz; exvm.feuerkampfdaten.kenner = kenner; exvm.feuerkampfdaten.verein = data.verein; DEBUG_LOG(m_logger) << "------------ SENDE ExVollzugsmeldung für ------------ " << kenner; m_mainDispatcher->jsonDispatcher()->sendExVollzugsmeldung(exvm, saaPartyId); } Core::RemoveElementRequest req; 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); } } } void AesHandler::removeAllPendingEffectCluster(int partyId) { const QList &effectClusterIds = findAllPendingEffectCluster(partyId); if (effectClusterIds.isEmpty()) return; INFO_LOG(m_logger) << "Pending effect cluster size: " << effectClusterIds.size(); Core::RemoveElementRequest req; for (const qint32 id : std::as_const(effectClusterIds)) { req.id = id; INFO_LOG(m_logger) << "Remove effect cluster id: " << id; qint32 ret = m_ftcAes->effectClusterRemoveRequest(req); INFO_LOG(m_logger) << "m_ftsAes->effectClusterRemoveRequest(req) --> " << ret; } } AreaEffectSimulation::Area AesHandler::createMinimumRect(int zone, const QVector &points) { double angle = 0.0; AreaEffectSimulation::Area ret; auto rec = AreaEffectSimulation::Polygons::getMinAreaRect(points, angle); LtsCoordTrans::UtmCoordinate coord(zone, rec.bottomLeft().x(), rec.bottomLeft().y()); ret.cornerBottomLeft = Core::GeoPosition2D(coord.geographic().x(), coord.geographic().y()); ret.rotation = angle; ret.height = abs(rec.height()); ret.width = abs(rec.width()); return ret; } void AesHandler::onExplosionSingleChanged(const AreaEffectSimulation::ExplosionSingle &data, DataModification modType) { if (modType == DataModification::eRemoved) { DEBUG_LOG(m_logger) << "----------------- remove area ------------- "; m_singleExplosions.remove(data.id); return; } if (modType == DataModification::eAdded) { DEBUG_LOG(m_logger) << "----------------- add area ------------- " << data.effectStateId; m_singleExplosions.insert(data.id, data.effectStateId); return; } if (data.effectStateId == AreaEffectSimulation::AesEffectState::EsActivated && m_singleExplosions.value(data.id, AreaEffectSimulation::AesEffectState::EsActivated) != AreaEffectSimulation::AesEffectState::EsActivated) { const auto state = data.effectStateId; DEBUG_LOG(m_logger) << "----------------- switch state ------------- " << state; m_singleExplosions.insert(data.id, state); QJsonObject telegram; telegram[QStringLiteral("typeId")] = 80082; telegram[QStringLiteral("type")] = QStringLiteral("UFomHlaExplosionSingleChanged"); QJsonObject headerJson; headerJson[QStringLiteral("aesId")] = data.id; headerJson[QStringLiteral("timeStamp")] = m_mainDispatcher->ddsHandler()->partitionTime(); QJsonObject impactPosition; impactPosition[QStringLiteral("lon")] = data.impactPosition.lon; impactPosition[QStringLiteral("lat")] = data.impactPosition.lat; QJsonObject jsonData; jsonData[QStringLiteral("header")] = headerJson; jsonData[QStringLiteral("impactPosition")] = impactPosition; // detonationLocation if (data.participantId.has_value()) { jsonData[QStringLiteral("participantId")] = data.participantId.get(); // firingObjectIdentifier } telegram[QStringLiteral("data")] = jsonData; emit explosionSingleChanged(telegram); } } void AesHandler::onEffectClusterChanged(const AreaEffectSimulation::EffectCluster &data, DataModification modType) { DEBUG_LOG(m_logger) << "Try auto handle effectCluster: " << data.name; if (modType == DataModification::eRemoved) return; DEBUG_LOG(m_logger) << "Try auto handle effectCluster: probs: " << data.properties; if (data.properties.empty()) return; if (!data.partyId.has_value()) return; DEBUG_LOG(m_logger) << "has PARTY " << data.properties; if (!data.participantId.has_value()) return; if (data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER) return; DEBUG_LOG(m_logger) << "has PARTICIPANTID " << data.properties; QJsonObject propertiesObject = QJsonDocument::fromJson(QByteArray::fromStdString(data.properties)).object(); if (!propertiesObject.value(QStringLiteral("useReceived")).toBool()) return; DEBUG_LOG(m_logger) << "has USE RECEIVED " << data.effectStateId; switch (data.effectStateId) { 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; } } case AreaEffectSimulation::AesEffectState::EsReceived: case AreaEffectSimulation::AesEffectState::EsPrepared: 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