/** * @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 "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 : 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); 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, req.notification().notificationType().underlying()); std::vector recipientIdsDds; recipientIdsDds.reserve(recipientIds.size()); for (const auto &recipientId : qAsConst(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_def::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_def::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.underlying(); // 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.underlying() << " " << "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 : qAsConst(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 : 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: " <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 : 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; 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 : qAsConst(data.feuerkommando.wirkmittelRakete)) { 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)); 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")] = 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 : qAsConst(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 : qAsConst(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 : qAsConst(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 : qAsConst(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 : qAsConst(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.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); // 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 : qAsConst(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 : 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 /*&& effectCluster.effectSourceId() != ProjectDataManagement::EffectSource_def::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 : qAsConst(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 : qAsConst(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 : qAsConst(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_def::EsActivated && m_singleExplosions.value(data.id(), AreaEffectSimulation::AesEffectState::EsActivated) != AreaEffectSimulation::AesEffectState_def::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().is_set()) { 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().is_set()) return; DEBUG_LOG(m_logger) << "has PARTY " << data.properties(); if (!data.participantId().is_set()) 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().underlying(); switch (data.effectStateId().underlying()) { 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