/** * @RDE_HEADER@ * Interface * $Id$ * $HeadURL$ * $Author$ */ #include "participant_to_map.h" #include "pdm_handler.h" #include #include #include #include #include #include #include #include #include // std::set #include #include #include #include //Qt #include #include namespace SituationMap { static QString participantToString(const ParticipantManagement::Participant &data) { QString str; str.append(QStringLiteral("participantId=%1").arg(data.id())); str.append(QStringLiteral(", symbol=%1").arg(QString::fromStdString(data.symbol()))); str.append(QStringLiteral(", typeId=%1").arg(data.typeId())); str.append(QStringLiteral(", designator=%1").arg(QString::fromStdString(data.designator()))); str.append(QStringLiteral(", tacticalIdentifier=%1").arg(QString::fromStdString(data.tacticalIdentifier()))); return str; } static bool hasGeoPositionNaN(const Core::GeoPosition &geoPos) { return std::isnan(geoPos.lat()) || std::isnan(geoPos.lon()) || std::isnan(geoPos.z()); } Participant2SituationMap::Participant2SituationMap(PdmHandler *pdmHandler, ObjectFactory &objFac, const QString &layername, const char *loggerName) : BaseClass(objFac, layername, loggerName) , FilterClass(loggerName, this) , m_pdmHandler(pdmHandler) , m_fireEventFlashTimer(new QTimer(this)) { m_fireEventFlashTimer->setInterval(1000); connect(m_fireEventFlashTimer, &QTimer::timeout, this, &Participant2SituationMap::onFireEventFlashTimer); } Participant2SituationMap::~Participant2SituationMap() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; // Update GUI (reset every ParticipantInfoDialog) if (m_movementTrackManager) m_movementTrackManager->emit_activeSimIdsChanged(QVector()); // empty TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } int32_t Participant2SituationMap::getSimId(const Sim2SituationMap::DDS_T &ddsData) const { return ddsData.id(); } bool Participant2SituationMap::init(LayerFactory *layerFactory, PluginProvider *pluginProvider) { if (!layerFactory || !pluginProvider || !pluginProvider->ppmDtc() || !pluginProvider->aesDtc()) return false; m_layerFactory = layerFactory; m_pluginProvider = pluginProvider; m_ppmDtc = m_pluginProvider->ppmDtc(); m_aesDtc = m_pluginProvider->aesDtc(); m_ppmParticipantInfoIfc = m_pluginProvider->ppmParticipantInfoIfc(); m_ocParticipantInfoIfc = m_pluginProvider->ocParticipantInfoIfc(); // FilterManagement FilterClass::init(m_pluginProvider); return true; } bool Participant2SituationMap::run() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; if (!m_ppmDtc) { ERROR_LOG(m_logger) << LOGFUNCTION << " no dtc"; return false; } this->stop(); if (!m_pluginProvider->isSessionValid()) { DEBUG_LOG(m_logger) << LOGFUNCTION << " no m_session anymore: return"; return false; } connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::typeChanged, this, &Participant2SituationMap::onTypesChanged, Qt::UniqueConnection); connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantChanged, this, &Participant2SituationMap::onParticipantChanged, Qt::UniqueConnection); connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantPositionChanged, this, &Participant2SituationMap::onPositionChanged, Qt::UniqueConnection); connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::unitPositionChanged, this, &Participant2SituationMap::onUnitPositionChanged, Qt::UniqueConnection); connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::dynamicParticipantDataChanged, this, &Participant2SituationMap::onDynamicParticipantDataChanged, Qt::UniqueConnection); connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::biteChanged, this, &Participant2SituationMap::onBiteChanged, Qt::UniqueConnection); // get: auto cbTypes = [this](const ParticipantManagement::Type & type) { onTypesChanged(type, DataModification::eAdded); }; m_ppmDtc->types(cbTypes); updateHierarchyLevels(); // nur lesen, kein Update (wird in den anderen Funktionen indirekt verwendet) m_ppmDtc->bites([this](const ParticipantManagement::Bite & data) { if (data.biteErrors().size() > 0) m_hasBiteError.insert(data.equipmentNumber()); }); // nur lesen, kein Update (wird in den anderen Funktionen indirekt verwendet) m_ppmDtc->dynamicParticipantDatas([this](const ParticipantManagement::DynamicParticipantData & data) { m_participantDynamicData.insert(data.participantId(), data); }); // Anlegen der Objekte m_ppmDtc->participants([this](const ParticipantManagement::Participant & data) { onParticipantChanged(data, DataModification::eAdded); }); // Aktualiserung der Position m_ppmDtc->participantPositions([this](const ParticipantManagement::ParticipantPosition & data) { onPositionChanged(data, DataModification::eAdded); }); m_ppmDtc->unitPositions([this](const ParticipantManagement::UnitPosition & data) { onUnitPositionChanged(data, DataModification::eAdded); }); // // Aes // connect(m_aesDtc.get(), &AreaEffectSimulation::Interface::Dtc::aesItemViewChanged, this, &Participant2SituationMap::onAesItemViewChanged, Qt::UniqueConnection); m_aesDtc->aesItemViews([this](const AreaEffectSimulation::AesItemView & data) { onAesItemViewChanged(data, DataModification::eAdded); }); // to handle race condition between DynamicParticipantData and DusMineState: connect(m_aesDtc.get(), &AreaEffectSimulation::Interface::Dtc::dusMineStateChanged, this, &Participant2SituationMap::onDusMineStateChanged, Qt::UniqueConnection); // // FireEventFlashing: // connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantGearEventChanged, this, &Participant2SituationMap::onParticipantGearEventChanged, Qt::UniqueConnection); m_ppmDtc->participantGearEvents([this](const ParticipantManagement::ParticipantGearEvent & data) { onParticipantGearEventChanged(data, DataModification::eAdded); }); // Timer: if (m_fireEventFlashEnable) m_fireEventFlashTimer->start(); connect(m_pluginProvider, &PluginProvider::partitionTimeChanged, this, &Participant2SituationMap::onPartitionTimeChanged); // At Snapshot partitionTimeChanged is signaled just once. // 'Simulate' that with m_notifiyTimer: if ((m_pluginProvider->appId() == LtsAppDescriptor::App::Snapshot) || ((m_pluginProvider->appId() == LtsAppDescriptor::App::Situation /*!!! Situation !!!*/ ) && m_pluginProvider->isReplayPartition()) || // snapshotTopic within script ((m_pluginProvider->appId() == LtsAppDescriptor::App::PlanEditor) && (m_pluginProvider->partitionId() == 0)) // PlanEditor @ partitionId==0 -> PE on Snapshot ) { if (!m_notifiyTimer) { m_notifiyTimer = new QTimer(this); m_notifiyTimer->setInterval(1000); connect(m_notifiyTimer, &QTimer::timeout, this, &Participant2SituationMap::notifyMapAboutModifedObjects); } m_notifiyTimer->start(); } // MovementTrack : if (m_movementTrackManager) { connect(m_movementTrackManager.data(), &MovementTrackManager::trackActivityChanged, this, &Participant2SituationMap::onMovementTrackActivityChanged); connect(m_movementTrackManager.data(), &MovementTrackManager::trackRemoved, this, &Participant2SituationMap::onMovementTrackRemoved); // transfer tracks to a) participants and b) update GUI: m_movementTrackManager->setCurrentTimeMsec(m_pluginProvider->partitionTime()); { const QVector &simIds = m_movementTrackManager->activeTrackIds(); for (int simId : simIds) onMovementTrackActivityChanged(simId, true); } { const QVector &simIds = m_movementTrackManager->deactiveTrackIds(); for (int simId : qAsConst(simIds)) onMovementTrackActivityChanged(simId, false); } } // to apply values from filter to MomentTrackManager (like maxViewAge): onSymbolAppearanceSettingsChanged(FilterManagement::SymbolAppearanceSettings::Part::All); // : MovementTrack TRACE_LOG(m_logger) << LOGFUNCTION << " END"; return true; } void Participant2SituationMap::stop() { // FlashTimer: if (m_fireEventFlashTimer) m_fireEventFlashTimer->stop(); if (m_notifiyTimer) m_notifiyTimer->stop(); if (m_aesDtc) m_aesDtc->disconnect(this); if (m_ppmDtc) m_ppmDtc->disconnect(this); if (m_pluginProvider) disconnect(m_pluginProvider, &PluginProvider::partitionTimeChanged, this, &Participant2SituationMap::onPartitionTimeChanged); if (m_movementTrackManager) m_movementTrackManager->disconnect(this); m_participants.clear(); m_participantPositions.clear(); m_participantDynamicData.clear(); m_participantGearEvents.clear(); m_lrfParticipantFakeGearEvents.clear(); m_ltdParticipantFakeGearEvents.clear(); m_unknownOriginatorsParticipant.clear(); m_participantWithMaybeMissingPartyId.clear(); m_unknownOriginatorsAes.clear(); m_aesItemView2PartyId.clear(); m_participantsWithProperties.clear(); } bool Participant2SituationMap::mapObjectsClicked(const QVector &uniqueMapIds) { if (!m_ppmParticipantInfoIfc && !m_ocParticipantInfoIfc) return false; const QVector &simIds = uniqueMapIds2SimIds(uniqueMapIds); if (simIds.isEmpty()) return true; if (m_ppmParticipantInfoIfc) m_ppmParticipantInfoIfc->showParticipantInfoWindow(simIds); //if (m_ocParticipantInfoIfc) // m_ocParticipantInfoIfc->showOcParticipantInfoWindow(simIds); return true; } bool Participant2SituationMap::unifiedMapObjectData(UniqueMapId uniqueMapId, UnifiedMapObjectData &umodOut) const { if (!m_ppmDtc) return false; int32_t simIdOut = -1; if (!this->uniqueMapId2SimId(uniqueMapId, simIdOut)) return false; auto it = m_participants.constFind(simIdOut); if (it == m_participants.constEnd()) return false; umodOut = UnifiedMapObjectData(); const ParticipantManagement::Participant &particpantData = it.value(); if (particpantData.equipmentNumber().is_set()) umodOut.m_equipmentNumber = particpantData.equipmentNumber().get(); umodOut.m_participantData.m_participantId = particpantData.id(); umodOut.m_participantData.m_typeId = particpantData.typeId(); umodOut.m_participantData.m_typeName = QString::fromStdString(m_ppmDtc->type(particpantData.typeId()).name()); const auto itDynData = m_participantDynamicData.find(particpantData.id()); if (itDynData != m_participantDynamicData.end()) { const auto &dynData = itDynData.value(); if (dynData.stateHistory().size() != 0) { const ParticipantManagement::Type type = m_ppmDtc->type(particpantData.typeId()); const ParticipantManagement::TypeClass typeClass = type.typeClass(); const ProjectDataManagement::VulnerabilityClass vc = typeClass == ParticipantManagement::TypeClass_def::Human ? ProjectDataManagement::VulnerabilityClass_def::SoldierInjuries : ProjectDataManagement::VulnerabilityClass_def::VehicleDamages; const ParticipantManagement::StateInformation &lastState = m_ppmDtc->youngestStateInformation(dynData.stateHistory()); const ProjectDataManagement::VulnerabilityState &vulState = lastState.vulnerabilityState(); umodOut.m_participantData.m_vulnerabilityState = vulState.underlying(); umodOut.m_participantData.m_vulnerabilityString = ProjectDataManagement::toString(vulState, vc); } } umodOut.m_partyId = particpantData.partyId(); umodOut.m_description1 = QString::fromStdString(particpantData.designator()); umodOut.m_description2 = QString::fromStdString(particpantData.tacticalIdentifier()); RefPointer mapObj = this->getMapObjectOfUniqueMapId(uniqueMapId); if (mapObj.valid()) { if (!mapObj->updateView()) ERROR_LOG(m_logger) << LOGFUNCTION << " unifiedMapObjectData: debugInfo='" << mapObj->debugInfo() << "'" << " updateView failed"; umodOut.m_symDataToRender = mapObj->symbolDataToRender(); } return true; } bool Participant2SituationMap::enableFireEventFlashing(bool enable) { TRACE_LOG(m_logger) << LOGFUNCTION << " enable: " << enable; if (m_fireEventFlashEnable == enable) return true; if (!m_fireEventFlashTimer) return false; m_fireEventFlashEnable = enable; if (m_fireEventFlashEnable) m_fireEventFlashTimer->start(); else { m_fireEventFlashTimer->stop(); // set forced Color to an invalid color: m_fireEventFlashTriState = 0; updateGearEvents(true); // perform updateView immediately be ensure correct display for pause, hardcopy etc. DEBUG_LOG(m_logger) << LOGFUNCTION << " updateGearEvents done"; } return true; } bool Participant2SituationMap::isFireEventFlashingEnabled() const { return m_fireEventFlashEnable; } bool Participant2SituationMap::isParticipantVisible(const int32_t participantId, bool *bParticipantExists) const { RefPointer mapObj = getMapObjectOfSimId(participantId); if (!mapObj.valid()) { if (bParticipantExists) *bParticipantExists = false; return false; } if (bParticipantExists) *bParticipantExists = true; return mapObj->visibility(); } void Participant2SituationMap::onPartitionTimeChanged(qint64 newPartitionTime) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, newPartitionTime=" << newPartitionTime << ", exerciseId = " << m_pluginProvider->exerciseId() << ", workspaceId = " << m_pluginProvider->workspaceId() << ", this=" << this; QElapsedTimer timer; if (m_logger.isTraceEnabled()) timer.start(); for (const InternalPosition &pos : qAsConst(m_participantPositions)) { if (LtsUtils::isPositionTimeOutdated(newPartitionTime, pos.sampleTime())) handleSymbolAppearanceSettingsParticipant(pos.participantId()); } // MovementTrack: QVector simIds; if (m_movementTrackManager) { m_movementTrackManager->setCurrentTimeMsec(newPartitionTime); simIds = m_movementTrackManager->activeTrackIds(); for (int32_t simId : qAsConst(simIds)) { RefPointer mapObj = this->getMapObjectOfSimId(simId); if (!mapObj.valid()) continue; PositionCont movementTrack = m_movementTrackManager->activeAndVisibleTrack(simId); if (movementTrack.isEmpty()) continue; // Do not continue here // if (movementTrack.last() == mapObj->origin()) // continue; // The participant has to receive the recalculated track. // (maybe reduced just by new partitionTime) // add current origin only if different: if (movementTrack.last() != mapObj->origin()) movementTrack.append(mapObj->origin()); mapObj->setMovementTrack(movementTrack); DEBUG_LOG(m_logger) << LOGFUNCTION << " movementTrack debugInfo='" << mapObj->debugInfo() << "'" << " update ..." << " movementTrack.size()=" << movementTrack.size(); addToUpdateNotify(mapObj); } // for } // if (m_movementTrackManager) notifyMapAboutModifedObjects(); TRACE_LOG(m_logger) << LOGFUNCTION << " END, newPartitionTime=" << newPartitionTime << ", took [ms] " << timer.elapsed() << ", exerciseId = " << m_pluginProvider->exerciseId() << ", workspaceId = " << m_pluginProvider->workspaceId() << ", ids.count()" << simIds.count() << " - finished"; } void Participant2SituationMap::onFireEventFlashTimer() { // 0, 1, 2 ++m_fireEventFlashTriState; if (m_fireEventFlashTriState >= 3) m_fireEventFlashTriState = 0; this->updateGearEvents(); } void Participant2SituationMap::onTypesChanged(const ParticipantManagement::Type &data, DataModification modType) { if (modType == DataModification::eRemoved) { m_types.remove(data.id()); return; } m_types[data.id()] = data; updateHierarchyLevels(); } void Participant2SituationMap::updateHierarchyLevels() { // // ensure that the required layer exists (depending on the possible types/hierachyLevel) // if (m_layerFactory) { std::set hLevels; // std::set is ordered, QSet not!!!!!!!!!! for (const ParticipantManagement::Type &type : qAsConst(m_types)) hLevels.insert(type.hierarchyLevel()); RenderingMode renderingMode = m_layerFactory->participantLayerRenderingMode(); SurfacePlacement surfacePlacement = m_layerFactory->participantLayerSurfacePlacement(); for (const qint32 hLevel : hLevels) { const QString &layername = this->layernameWithHierachy(hLevel); if (!m_layerFactory->hasOperationalLayer(layername)) { DEBUG_LOG(m_logger) << LOGFUNCTION << " create logicalLayer: '" << layername << "'" << " rm = " << renderingMode << " sp = " << surfacePlacement; if (!m_layerFactory->addOperationalLayer(layername, tr("Participant (Level %1)").arg(hLevel), renderingMode, surfacePlacement)) // dt. Teilnehmer (Ebene 1) - compare map_initializer.cpp DEBUG_LOG(m_logger) << LOGFUNCTION << " failure on creating logicalLayer: '" << layername << "'"; } else DEBUG_LOG(m_logger) << LOGFUNCTION << " already exists, logicalLayer: '" << layername << "'"; } // for } // if (m_layerFactory) } template void updateContainer(QHash &cont, const T &data, int id, DataModification modType) { switch (modType) { case DataModification::eAdded: case DataModification::eModified: cont[id] = data; break; case DataModification::eRemoved: cont.remove(id); break; } } void Participant2SituationMap::onParticipantChanged(const ParticipantManagement::Participant &data, DataModification modType) { DEBUG_LOG(m_logger) << "onParticipantChanged: " << data; updateContainer(m_participants, data, data.id(), modType); checkForUnknownOriginatorsParticipant(data.id()); if (handleChanged(data, modType) && modType != DataModification::eRemoved) updateParticipantPosition(data.id()); // make sure to set the current position // purpose is to get the value the porperty(is artillery or not) of the participant data. feuereinheit const ParticipantManagement::ParticipantData &ppData = m_ppmDtc->convertParticipantToParticipantData(data); const ParticipantManagement::Type &ppType = m_ppmDtc->type(ppData.typeId()); const QJsonObject typeProperties(QJsonDocument::fromJson(QByteArray::fromStdString(ppType.properties())).object()); updateContainer(m_participantsWithProperties, typeProperties[QStringLiteral("isArtillery")].toBool(false), data.id(), modType); } void Participant2SituationMap::onPositionChanged(const ParticipantManagement::ParticipantPosition &data, DataModification modType) { TRACE_LOG(m_logger) << "onPositionChanged: " << data; if(strcmp(PROJECTBUILD, "commaster") == 0 || strcmp(PROJECTBUILD, "demotablet") == 0) { //NOTE: Maybe faster if m_participants is used instead of dtc, but unsure if m_participants got all the participants at the right time if(m_ppmDtc != nullptr) { auto participant = m_ppmDtc.get()->participant(data.participantId()); if(participant.equipmentNumber().is_set()) { if(participant.equipmentNumber().get() == 1) { DEBUG_LOG(m_logger) << "ownPositionChanged2!!!"; const Point3D newPos(data.pos().lon(), data.pos().lat(), data.pos().z()); emit participantOwnPositionChanged(newPos, data.sampleTime()); return; } //NOTE: Check if upper hierarchy icon anf if true, ignore //FIXME: Maybe not necessary if participant with EqNumber 1 is always in the highest hierarchy. // if(participant.superiorId() != 0) // { // if(m_ppmDtc.get()->participant(participant.superiorId()).equipmentNumber().get()) // if(m_ppmDtc.get()->participant(participant.superiorId()).equipmentNumber().get() == 1) // return; //} } } } InternalPosition pos; pos.participantId(data.participantId()); pos.positionLeader(data.pos()); pos.sampleTimeLeader(data.sampleTime()); pos.reportingSource = data.reportingSource(); pos.isUnit = false; if (data.mountedOn().is_set() && data.mountedOn().get() > 0) pos.bIsMountedOn = true; if (data.mountedParticipants().is_set() && data.mountedParticipants().get().size() > 0) pos.bHasMountedParticipants = true; updateContainer(m_participantPositions, std::move(pos), data.participantId(), modType); updateParticipantPosition(data.participantId()); } void Participant2SituationMap::onUnitPositionChanged(const ParticipantManagement::UnitPosition &data, DataModification modType) { DEBUG_LOG(m_logger) << "onUnitPositionChanged: " << data; #ifdef GUEZ_MODE if ( (m_pluginProvider->appId() == LtsAppDescriptor::App::OC) || (LtsSessionData::LtsSessionData::getInstance()->isTablet()) ) { DEBUG_LOG(m_logger) << "OcMode: no Unit Pos Update" << data; return; } #endif InternalPosition pos(data, m_bShowCenterPositions); updateContainer(m_participantPositions, pos, data.participantId(), modType); updateParticipantPosition(pos); } void Participant2SituationMap::updateParticipantPosition(int participantId, bool bHandleSymbolAppearanceSettingsParticipant) { const auto itPos = m_participantPositions.constFind(participantId); if (itPos == m_participantPositions.constEnd()) return; updateParticipantPosition(itPos.value(), bHandleSymbolAppearanceSettingsParticipant); } void Participant2SituationMap::updateParticipantPosition(const InternalPosition &pos, bool bHandleSymbolAppearanceSettingsParticipant) { // Assume, that participant was already created: const auto it = m_simId2UniqueMapId.constFind(pos.participantId()); if (it == m_simId2UniqueMapId.constEnd()) return; const auto participantId = pos.participantId(); const UniqueMapId uniqueMapId = it.value(); RefPointer mapObj = m_objFac.get(uniqueMapId); if (!mapObj) { ERROR_LOG(m_logger) << LOGFUNCTION << " not found in objFac" << " simId = " << participantId << " uniqueMapId = " << uniqueMapId; } if (hasGeoPositionNaN(pos.position())) { DEBUG_LOG(m_logger) << "InternalPosition of participantId " << participantId << " has nan!" << " pos.positionCenter() = " << pos.positionCenter() << " pos.positionLeader() = " << pos.positionLeader(); if (mapObj->visibility()) { mapObj->setVisibility(false); // shortcut addToUpdateNotify(mapObj); } return; } // IPS-Range / Location 'Tunnel' -> PositionReportingSource == QString str_X_altitudeDepth; if (pos.reportingSource == Core::PositionReportingSource::OpsTunnel) str_X_altitudeDepth = QStringLiteral("Tnnl"); if (mapObj->textModifier(Participant::TextModifier::XY_AltitudeLocation) != str_X_altitudeDepth) { mapObj->setTextModifier(Participant::TextModifier::XY_AltitudeLocation, str_X_altitudeDepth); addToUpdateNotify(mapObj); } // has mounted participants? -> EF == '+' QString strEF; if (pos.bHasMountedParticipants) strEF = QStringLiteral("+"); if (mapObj->textModifier(Participant::TextModifier::EF_ReinforcedReduced) != strEF) { mapObj->setTextModifier(Participant::TextModifier::EF_ReinforcedReduced, strEF); addToUpdateNotify(mapObj); } // is mounted on another? suppress if (pos.bIsMountedOn) { DEBUG_LOG(m_logger) << "participantId " << participantId << " is mounted on"; if (mapObj->visibility()) { mapObj->setVisibility(false); // shortcut addToUpdateNotify(mapObj); } // no return -> movementTrack will be updated: return; // return -> postionReport is not added to movementTrack (identical to kala code) } const Core::GeoPosition &geoPos = pos.position(); const Point3D newPos(geoPos.lon(), geoPos.lat(), geoPos.z()); // Position & Height: bool positionChanged = mapObj->setOrigin(newPos); if (positionChanged) addToUpdateNotify(mapObj); // MovementTrack: if (m_movementTrackManager) { bool isTrackModified = m_movementTrackManager->addPosition(participantId, newPos, pos.sampleTime()); // pos will be added if track exists if (isTrackModified) { const PositionCont &movementTrack = m_movementTrackManager->activeAndVisibleTrack(participantId); mapObj->setMovementTrack(movementTrack); } } // if (m_movementTrackManager if (bHandleSymbolAppearanceSettingsParticipant) handleSymbolAppearanceSettingsParticipant(mapObj, m_participants.value(participantId)); if (positionChanged) emit participantPositionChanged(participantId, newPos); } void Participant2SituationMap::onDynamicParticipantDataChanged(const ParticipantManagement::DynamicParticipantData &data, DataModification modType) { DEBUG_LOG(m_logger) << "onDynamicParticipantDataChanged: " << data; const int32_t simId = data.participantId(); updateContainer(m_participantDynamicData, data, simId, modType); // Assume, that participant was already created: auto it = m_simId2UniqueMapId.constFind(simId); if (it == m_simId2UniqueMapId.constEnd()) { DEBUG_LOG(m_logger) << LOGFUNCTION << " simId not found" << " simId = " << simId ; return; } // retrieve mapObj const UniqueMapId uniqueMapId = it.value(); RefPointer mapObj = m_objFac.get(uniqueMapId); if (!mapObj) { ERROR_LOG(m_logger) << LOGFUNCTION << " not found in objFac" << " simId = " << simId << " uniqueMapId = " << uniqueMapId ; return; } checkForUnknownOriginatorsParticipant(data.participantId()); // update dynamic data (State etc) updateDynamicParticipantData(mapObj, data); // refresh MapObjectSelectionWidget or similar: emit mapObjectChanged(uniqueMapId); } void Participant2SituationMap::onBiteChanged(const ParticipantManagement::Bite &data, DataModification modType) { switch (modType) { case DataModification::eAdded: case DataModification::eModified: if (data.biteErrors().size() > 0) m_hasBiteError.insert(data.participantId()); else m_hasBiteError.remove(data.participantId()); break; case DataModification::eRemoved: m_hasBiteError.remove(data.participantId()); break; } handleSymbolAppearanceSettingsParticipant(data.participantId()); } void Participant2SituationMap::onParticipantGearEventChanged(const ParticipantManagement::ParticipantGearEvent &data, DataModification modType) { DEBUG_LOG(m_logger) << LOGFUNCTION << " ParticipantGearEvent = " << " participantId= " << data.participantId() << " sampleTime= " << data.sampleTime(); // TODO: do we have to consider LRF and LTD charge types // Assumption: no (Norbert Thoden) switch (modType) { case DataModification::eAdded: case DataModification::eModified: { m_participantGearEvents[data.participantId()] = data.sampleTime(); } break; // Is it correct to remove them? // More or less irrelevant, since the topic is removed very late... case DataModification::eRemoved: { const int participantId = data.participantId(); RefPointer mapObj = this->getMapObjectOfSimId(participantId); if (mapObj.valid()) mapObj->forcePartyColor(QColor()); // set invalid color to let the partyId take effect m_participantGearEvents.remove(participantId); } break; } } void Participant2SituationMap::onParticipantFakeGearEventChanged(int participantId, qint64 sampleTime, SpecialChargeType specialChargeType) { DEBUG_LOG(m_logger) << LOGFUNCTION << " ParticipantGearEvent = " << ", participantId= " << participantId << ", sampleTime= " << sampleTime << ", specialChargeType= " << specialChargeType; // Note: // FakeEvent will not be removed from m_participant2SituationMap // if there are two fake events: 1st at 10sec, 2nd at 20sec. // the removeElement of the first must not remove the originatorParticipantId // at m_participant2SituationMap // Norbert Thoden, 2023-06-22 switch (specialChargeType) { case SpecialChargeType::LRF: m_lrfParticipantFakeGearEvents[participantId] = sampleTime; break; case SpecialChargeType::LTD: m_ltdParticipantFakeGearEvents[participantId] = sampleTime; break; case SpecialChargeType::NoneSpecial: // empty break; } // switch } void Participant2SituationMap::onAesItemViewChanged(const AreaEffectSimulation::AesItemView &data, DataModification modType) { // GUEZE-2717 3D Wirksim - EXC- Symbole Darstellung Karte // SubOrdinate (nachrangige) AesItemViews are maybe removed from DDS. // Like AesItemView with AtExplosionClusterGunToTargetLine. // GunToTargetLine is replaced be real trajectory. // So there is no more need for that topic -> AES is removing them from DDS. static QVector subOrdinateAesTypeIds = {AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions, AreaEffectSimulation::AesType_def::AtExplosionClusterSingleTrajectory, AreaEffectSimulation::AesType_def::AtExplosionClusterGunToTargetLine}; if (subOrdinateAesTypeIds.contains(data.aesTypeId().underlying())) return; // nur für die vorrangingen AesTypeIds: switch (modType) { case DataModification::eAdded: case DataModification::eModified: if (data.partyId().is_set()) m_aesItemView2PartyId[data.aesItemId()] = data.partyId().get(); else m_aesItemView2PartyId.remove(data.aesItemId()); break; case DataModification::eRemoved: m_aesItemView2PartyId.remove(data.aesItemId()); break; } if (modType == DataModification::eRemoved) return; checkForUnknownOriginatorsAes(data.aesItemId()); // AesItemView::aesItemId() is entityId } void Participant2SituationMap::onDusMineStateChanged(const AreaEffectSimulation::DusMineState &/*data*/, DataModification /*modType*/) { auto copy = m_participantDynamicData; for (const ParticipantManagement::DynamicParticipantData &dynPartData : qAsConst(copy)) { if (!m_participantWithMaybeMissingPartyId.contains(dynPartData.participantId())) continue; onDynamicParticipantDataChanged(dynPartData, DataModification::eModified); } } void Participant2SituationMap::onMovementTrackActivityChanged(int trackId, bool active) { TRACE_LOG(m_logger) << LOGFUNCTION << " START:" << " trackId=" << trackId << ", active=" << active; if (!m_movementTrackManager) return; if (!m_pluginProvider->isSessionValid()) { DEBUG_LOG(m_logger) << LOGFUNCTION << " no m_session anymore: return"; return; } // simId == trackId; RefPointer mapObj = this->getMapObjectOfSimId(trackId); if (!mapObj.valid()) return; PositionCont movementTrack; if (active) { movementTrack = m_movementTrackManager->activeAndVisibleTrack(trackId); if (!movementTrack.isEmpty()) { if (movementTrack.last() != mapObj->origin()) movementTrack.append(mapObj->origin()); } else { // Track is very new, so set current origin as first position: m_movementTrackManager->addPosition(trackId, mapObj->origin(), m_pluginProvider->partitionTime()); } } mapObj->setMovementTrack(movementTrack); mapObj->setMovementTrackVisibility(active); mapObj->setMovementTrackEnabled(active); DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << mapObj->debugInfo() << "'" << " simId/trackId=" << trackId; addToUpdateNotify(mapObj); // // update GUI: // const QVector &activeTrackIds = m_movementTrackManager->activeTrackIds(); // == active SimIds m_movementTrackManager->emit_activeSimIdsChanged(activeTrackIds); } void Participant2SituationMap::onMovementTrackRemoved(int trackId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << "simId/trackId=" << trackId; if (!m_movementTrackManager) return; // simId == trackId; RefPointer mapObj = this->getMapObjectOfSimId(trackId); if (!mapObj.valid()) return; mapObj->setMovementTrack(PositionCont()); mapObj->setMovementTrackVisibility(false); mapObj->setMovementTrackEnabled(false); DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << mapObj->debugInfo() << "'" << " simId/trackId=" << trackId; if (!mapObj->updateView()) ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << mapObj->debugInfo() << "'" << " updateView failed"; } int Participant2SituationMap::participantHierachyLevel(const ParticipantManagement::Participant &data) const { const qint32 typeId = data.typeId(); auto it = m_types.find(typeId); if (it == m_types.end()) return 0; return it.value().hierarchyLevel(); } QString Participant2SituationMap::layernameWithHierachy(qint32 hierachyLevel) const { if (hierachyLevel <= 0) return m_layername; return m_layername + QLatin1Char('_') + QString::number(hierachyLevel); } QString Participant2SituationMap::layernameWithHierachy(const Sim2SituationMap::DDS_T &ddsData) const { const qint32 hLevel = participantHierachyLevel(ddsData); return layernameWithHierachy(hLevel); } bool Participant2SituationMap::isElapsed(const ParticipantManagement::Participant &data) const { bool isOwnPosition = false; if(strcmp(PROJECTBUILD, "commaster") == 0 || strcmp(PROJECTBUILD, "demotablet") == 0) { if(data.equipmentNumber().is_set()) if(data.equipmentNumber().get() == 1) isOwnPosition = true; } //NOTE: If the position is the ComMaster-Tablet Own Position -> Not show the Symbol on Map, instead a BlueDot will be shown return data.isDeleted() || isOwnPosition; } void Participant2SituationMap::checkForUnknownOriginatorsParticipant(int possibleOriginatorParticipantId) { // m_unknownOriginatorsParticipant -> if (m_unknownOriginatorsParticipant.isEmpty()) return; auto cb = [this, possibleOriginatorParticipantId](QPair p) -> bool { if (p.first != possibleOriginatorParticipantId) return false; auto it = m_participantDynamicData.constFind(p.second); if (it != m_participantDynamicData.constEnd()) onDynamicParticipantDataChanged(it.value(), DataModification::eModified); return true; }; auto copy = m_unknownOriginatorsParticipant; copy.erase(std::remove_if(copy.begin(), copy.end(), cb), copy.end()); std::swap(m_unknownOriginatorsParticipant, copy); } void Participant2SituationMap::checkForUnknownOriginatorsAes(int possibleOriginatorEffectSimId) { // m_unknownOriginatorsAes -> if (m_unknownOriginatorsAes.isEmpty()) return; auto cb = [this, possibleOriginatorEffectSimId](QPair p) -> bool { if (p.first != possibleOriginatorEffectSimId) return false; auto it = m_participantDynamicData.constFind(p.second); if (it != m_participantDynamicData.constEnd()) onDynamicParticipantDataChanged(it.value(), DataModification::eModified); return true; }; auto copy = m_unknownOriginatorsAes; copy.erase(std::remove_if(copy.begin(), copy.end(), cb), copy.end()); std::swap(m_unknownOriginatorsAes, copy); } bool Participant2SituationMap::updateDynamicParticipantData(RefPointer mapObj, const ParticipantManagement::DynamicParticipantData &data) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", data.participantId() " << data.participantId(); if (!mapObj) return false; // state/injury and it's injuryPartyId SituationMap::Injury injury = SituationMap::Injury::NoInjury; int32_t injuryPartyId = -1; // state/injury: const ParticipantManagement::StateInformation &lastState = m_ppmDtc->youngestStateInformation(data.stateHistory()); if (data.stateHistory().size() != 0) { const ProjectDataManagement::VulnerabilityState &vulState = lastState.vulnerabilityState(); switch (vulState.underlying()) // underlying -> inner_enum { case ProjectDataManagement::VulnerabilityState::CombatReady: case ProjectDataManagement::VulnerabilityState::TemporarySeverance: case ProjectDataManagement::VulnerabilityState::NearMiss: case ProjectDataManagement::VulnerabilityState::HitWithNoEffect: case ProjectDataManagement::VulnerabilityState::Reserved: case ProjectDataManagement::VulnerabilityState::NoChange: //should never happen injury = SituationMap::Injury::NoInjury; break; case ProjectDataManagement::VulnerabilityState::LightDamage: case ProjectDataManagement::VulnerabilityState::MediumDamage: case ProjectDataManagement::VulnerabilityState::HeavyDamage: case ProjectDataManagement::VulnerabilityState::NotReady: injury = SituationMap::Injury::Hit; break; case ProjectDataManagement::VulnerabilityState::MobilityKill: case ProjectDataManagement::VulnerabilityState::VisibleMobilityKill: injury = SituationMap::Injury::MobilityKill; break; case ProjectDataManagement::VulnerabilityState::WeaponSystemDamage: case ProjectDataManagement::VulnerabilityState::VisibleWeaponSystemDamage: injury = SituationMap::Injury::WeaponKill; break; case ProjectDataManagement::VulnerabilityState::CatastrophicKill: injury = SituationMap::Injury::TotalKill; break; case ProjectDataManagement::VulnerabilityState::VulnerabilityBitsChanged: case ProjectDataManagement::VulnerabilityState::NContamination: case ProjectDataManagement::VulnerabilityState::BContamination: case ProjectDataManagement::VulnerabilityState::CContamination: // those four should no happen here injury = SituationMap::Injury::Hit; break; } // switch // injuryPartyId: switch (lastState.originatorType().underlying()) { case ProjectDataManagement::StateChangeOriginator::Participant: { const int32_t originParticipantId = lastState.originatorId(); if (originParticipantId != 0) { auto it = m_participants.constFind(originParticipantId); if (it == m_participants.constEnd()) { const ParticipantManagement::Participant &participantOrigin = m_ppmDtc->participant(originParticipantId); if (participantOrigin.id() > 0) // It's a participant { m_participants.insert(participantOrigin.id(), participantOrigin); injuryPartyId = participantOrigin.partyId(); const ParticipantManagement::ParticipantData &ppData = m_ppmDtc->convertParticipantToParticipantData(participantOrigin); const ParticipantManagement::Type &ppType = m_ppmDtc->type(ppData.typeId()); const QJsonObject typeProperties(QJsonDocument::fromJson(QByteArray::fromStdString(ppType.properties())).object()); m_participantsWithProperties.insert(participantOrigin.id(), typeProperties[QStringLiteral("isArtillery")].toBool(false)); } else if (originParticipantId != data.participantId()) // sanity m_unknownOriginatorsParticipant.push_back(qMakePair(originParticipantId, data.participantId())); // originator is not know yet, store for delayed processing } else injuryPartyId = it.value().partyId(); } break; } case ProjectDataManagement::StateChangeOriginator::Party: { const ParticipantManagement::Party &partyOrigin = m_ppmDtc->party(lastState.originatorId()); if(partyOrigin.id()) injuryPartyId = partyOrigin.id(); break; } case ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation: case ProjectDataManagement::StateChangeOriginator::FunctionalArea: { const int32_t originEffectSimulationId = lastState.originatorId(); if (originEffectSimulationId != 0) { // 1. try own hash (m_aesItemView2PartyId): auto itAesItemView = m_aesItemView2PartyId.constFind(originEffectSimulationId); if (itAesItemView == m_aesItemView2PartyId.constEnd()) { DEBUG_LOG(m_logger) << LOGFUNCTION << " aesItemViewFromEntityId failed 1, originEffectSimulationId = " << originEffectSimulationId << ", data.participantId() " << data.participantId(); #ifdef EXPENSIVE_FALLBACK // August 2024, N. Thoden // from my point of view, in this context, aesItemFromId() is expensive. // It considers all different aesType (nContamination, bcContamination, explosionArea ... // Additionally: enable..., waitForHistoricalData, and finally works on a QMap // 2. try more expensive way: // (more or less a fallback) AreaEffectSimulation::AesItem aesItem; m_aesDtc->aesItemFromId(originEffectSimulationId, aesItem); if (aesItem.partyId().is_set()) { injuryPartyId = aesItem.partyId().get(); #ifdef LTS_DEBUG ERROR_LOG(m_logger) << LOGFUNCTION << " m_aesDtc->aesItemFromId , injuryPartyId = " << injuryPartyId << ", data.participantId() " << data.participantId() << " - modifiy the code to avoid using this fallback"; // Q_ASSERT_X(false, "Participant2SituationMap::updateDynamicParticipantData", "Using m_aesItemView2PartyId failed, but aesItemFromId works. Please reviewed."); #else DEBUG_LOG(m_logger) << LOGFUNCTION << " m_aesDtc->aesItemFromId , injuryPartyId = " << injuryPartyId << ", data.participantId() " << data.participantId(); #endif // LTS_DEBUG } else #endif // EXPENSIVE_FALLBACK { DEBUG_LOG(m_logger) << LOGFUNCTION << " aesItemViewFromEntityId failed 2, originEffectSimulationId = " << originEffectSimulationId << ", data.participantId() " << data.participantId(); m_unknownOriginatorsAes.push_back(qMakePair(originEffectSimulationId, data.participantId())); // originator is not know yet, store for delayed processing } } else injuryPartyId = itAesItemView.value(); } } break; case ProjectDataManagement::StateChangeOriginator::ImprovisedExplosiveDevice: { auto guezIedDtc = m_pluginProvider->gimDtc(); if (guezIedDtc) { const GuezIedManagement::Ied &gIed = guezIedDtc->iedForEquipmentNumber(lastState.originatorId()); if (gIed.id() > 0) injuryPartyId = gIed.partyId(); } } break; case ProjectDataManagement::StateChangeOriginator::DusMine: injuryPartyId = m_aesDtc->partyForDusMine(lastState.originatorId()); if (injuryPartyId == -1) m_participantWithMaybeMissingPartyId.insert(data.participantId()); else m_participantWithMaybeMissingPartyId.remove(data.participantId()); break; case ProjectDataManagement::StateChangeOriginator::Unknown: case ProjectDataManagement::StateChangeOriginator::ObserverController: case ProjectDataManagement::StateChangeOriginator::ExerciseControl: case ProjectDataManagement::StateChangeOriginator::Initialization: { injuryPartyId = 1; // 1 == OC } break; case ProjectDataManagement::StateChangeOriginator::WoundTreatmentDevice: case ProjectDataManagement::StateChangeOriginator::MaintainanceDevice: case ProjectDataManagement::StateChangeOriginator::MedicalTimeout: case ProjectDataManagement::StateChangeOriginator::Sanitation: DEBUG_LOG(m_logger) << LOGFUNCTION << " originator state change for " << lastState.originatorType() << " not (yet) implemented" << ", participantId = " << data.participantId() << ", sampleTime=" << data.sampleTime() << ", eqNumber=" << m_participants.value(data.participantId()).equipmentNumber() << ", mapObj->debugInfo() = " << mapObj->debugInfo(); break; } } else injury = SituationMap::Injury::NoInjury; DEBUG_LOG(m_logger) << LOGFUNCTION << " injuryPartyId = " << injuryPartyId << ", data.participantId() " << data.participantId(); // Maybe ParticipantPositions were received BEFORE DynamicParticipantData (like at Snapshot) // So make sure to set the current position updateParticipantPosition(data.participantId(), false); // Hitline : if ((injury != Injury::NoInjury) && (injuryPartyId >= 0) && (lastState.originatorType() == ProjectDataManagement::StateChangeOriginator::Participant)) { DEBUG_LOG(m_logger) << "Hit Event: " << " stateTime=" << lastState.stateTime() << " injury=" << injury << " injuryPartyId=" << injuryPartyId << " originatorType=" << lastState.originatorType() << " originatorChargeTypeId=" << lastState.originatorChargeTypeId(); // // assume that 'position' is published BEFORE 'state'!!! // // retrieve very new data here (and not within the slot), to ensure consistent data: HitlineInfo hlInfo; hlInfo.participantId = data.participantId(); hlInfo.participantPosition = m_ppmDtc->participantPosition(data.participantId()); hlInfo.participantState = lastState; hlInfo.originatorPosition = m_ppmDtc->participantPosition(lastState.originatorId()); hlInfo.injuryPartyId = injuryPartyId; emit hitlineOccured(hlInfo); } // : Hitline const bool bInjuryChanged = mapObj->setInjury(injury, injuryPartyId); // Additional symbol for nbc protection without filter setting? const bool bAddSymbolChanged = mapObj->setAdditionalSymbol(SituationMap::AdditionalSymbolType::NbcProtection, data.hasNbcProtectionActive() || data.hasNbcProtectionConfigured()); // Gear Ranges : // Ammo can be stored at turret and/or tank // data.gear contains that // [NLDEVCTRL-926] avoid duplicates: QVector> validGearCharges; validGearCharges.reserve(data.gear().size()); for (auto const &gear : data.gear()) { QPair candidate(gear.gearTypeId(), gear.chargeTypeId()); if (!validGearCharges.contains(candidate)) validGearCharges.append(candidate); } // for // now duplicates should be removed QVector gearRanges; gearRanges.reserve(data.gear().size()); for (const QPair &gearCharge : qAsConst(validGearCharges)) { const int range = m_pdmHandler->maxRangeFromGearTypeIdAndChargeTypeId(gearCharge.first, gearCharge.second); if (range > 0) gearRanges.append(range); } // for const bool bRangesChanged = mapObj->setRanges(gearRanges); // : Gear Ranges // Designation : const std::vector &designations = data.designations(); // convert data be transfered in via a single call: (easier to handle at ParticipantImpl) QVector designationDataVec; designationDataVec.reserve(designations.size()); for (const ParticipantManagement::LaserDesignation &des : qAsConst(designations)) { Participant::DesignationData designationData; const Core::GeoPosition &spotLocation = des.spotLocation(); designationData.spotLocation.rx() = spotLocation.lon(); designationData.spotLocation.ry() = spotLocation.lat(); designationData.spotLocation.rz() = spotLocation.z(); if (designationData.spotLocation.isNull()) // invalid: continue -> will not be shonw on map display continue; designationData.code = des.code(); designationDataVec.push_back(designationData); } const bool bDesignationChanged = mapObj->setDesignations(designationDataVec); // : Designation if (bInjuryChanged || bAddSymbolChanged || bRangesChanged || bDesignationChanged) addToUpdateNotify(mapObj); // at last, is calling updateView auto itPart = m_participants.constFind(data.participantId()); handleSymbolAppearanceSettingsParticipant(mapObj, itPart == m_participants.constEnd() ? ParticipantManagement::Participant() : itPart.value()); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", data.participantId()" << data.participantId(); return true; } bool Participant2SituationMap::updateElementImpl(const ParticipantManagement::Participant &ddsData, RefPointer mapObj) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " participant = " << participantToString(ddsData) ; // SIDC: // (role is not neccessary here, participant will take it from partyId via partyIdToColorFunctor) SymbolData symData = mapObj->symbol(); symData.sidc = QString::fromStdString(ddsData.symbol()); mapObj->setSymbol(symData); // PartyId: mapObj->setPartyId(ddsData.partyId()); // TextModifier: mapObj->setTextModifier(Participant::TextModifier::T_UniqueDesignation, QString::fromStdString(ddsData.designator())); mapObj->setTextModifier(Participant::TextModifier::M_HigherFormation, QString::fromStdString(ddsData.tacticalIdentifier())); const int32_t participantId = ddsData.id(); // process DynamicData: auto itDpd = m_participantDynamicData.constFind(participantId); if (itDpd != m_participantDynamicData.cend()) updateDynamicParticipantData(mapObj, itDpd.value()); else // update symbol here if no m_participantDynamicData for participantId handleSymbolAppearanceSettingsParticipant(participantId); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; return true; } bool Participant2SituationMap::removeElement(const int32_t simId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " simId=" << simId; // to reduce the amount of calls to updateView(), the mapObj are stored // at m_objectsToUpdate. // If an object is removed by removeing the topic from DDS, // Sim2SituationMap::removeElement is called. // Overload this method to keep m_objectsToUpdate up-to-date: const UniqueMapId uniqueMapId = m_simId2UniqueMapId.value(simId, INVALID_MAP_ID); if (!isUniqueMapIdValid(uniqueMapId)) { WARNING_LOG(m_logger) << LOGFUNCTION << " not found" << ", simId = " << simId; return false; } QMutableVectorIterator> it(m_objectsToUpdate); while (it.hasNext()) { const RefPointer &item = it.next(); if (item->uniqueMapId() == uniqueMapId) it.remove(); } // while TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " simId=" << simId; return BaseClass::removeElement(simId); } void Participant2SituationMap::updateGearEvents(bool doUpdateView) // to let the participant flash { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", m_participantGearEvents.size()" << m_participantGearEvents.size() << ", doUpdateView=" << doUpdateView; if (m_participantGearEvents.isEmpty() && m_lrfParticipantFakeGearEvents.isEmpty() && m_ltdParticipantFakeGearEvents.isEmpty() ) return; if (!m_pluginProvider->isSessionValid()) { DEBUG_LOG(m_logger) << LOGFUNCTION << " no m_session anymore: return"; return; } const qint64 curPartitionTimeMSec = m_pluginProvider->partitionTime(); // msec const qint64 fireEventFlashingDurationMSec = m_currentSymbolSettings.symbolSettings.symbolFireEventFlashingMSec; // msec // // real work: // QSet particpantIdsWithGearEvents; particpantIdsWithGearEvents.reserve(m_participantGearEvents.size() + m_lrfParticipantFakeGearEvents.size() + m_ltdParticipantFakeGearEvents.size()); particpantIdsWithGearEvents += QSet(m_participantGearEvents.keyBegin() , m_participantGearEvents.keyEnd()); particpantIdsWithGearEvents += QSet(m_lrfParticipantFakeGearEvents.keyBegin(), m_lrfParticipantFakeGearEvents.keyEnd()); particpantIdsWithGearEvents += QSet(m_ltdParticipantFakeGearEvents.keyBegin(), m_ltdParticipantFakeGearEvents.keyEnd()); updateGearEventInternal(doUpdateView, curPartitionTimeMSec, fireEventFlashingDurationMSec, particpantIdsWithGearEvents); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } void Participant2SituationMap::updateGearEventInternal(bool doUpdateView, qint64 curPartitionTimeMSec, qint64 fireEventFlashingDurationMSec, const QSet &particpantIdsWithGearEvents) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " curPartitionTimeMSec =" << curPartitionTimeMSec << " fireEventFlashingDurationMSec =" << fireEventFlashingDurationMSec; if (particpantIdsWithGearEvents.isEmpty()) return; for (auto partId : qAsConst(particpantIdsWithGearEvents)) { const UniqueMapId uniqueMapId = m_simId2UniqueMapId.value(partId, INVALID_MAP_ID); if (uniqueMapId == INVALID_MAP_ID) continue; RefPointer mapObj = m_objFac.get(uniqueMapId); if (!mapObj.valid()) continue; // real work: bool bFlash = false; // typical GearEvents: { auto it = m_participantGearEvents.find(partId); if (it != m_participantGearEvents.end()) { const qint64 lastFireEventMSec = it.value(); // (aktuelle Zeit - Zeit letztes FeuerEreignis) > Settings.symbolFireEventFlashingMSec const qint64 ageMSec = (curPartitionTimeMSec - lastFireEventMSec); DEBUG_LOG(m_logger) << LOGFUNCTION << " ageMSec = " << ageMSec << ", fireEventFlashingDurationMSec " << fireEventFlashingDurationMSec; if (ageMSec <= fireEventFlashingDurationMSec) { // INFO_LOG(m_logger) << LOGFUNCTION << " flash caused by normal ammo (no LRF, LTD)"; bFlash = true; } // cleanup container (to avoid unnecessary updateView()) if (ageMSec > (120 + 10) * 1000) // GUI: maximum is 120sec { m_participantGearEvents.erase(it); } } } // faked GearEvents: LRF if (!bFlash) { const int displayDuration = m_symbolAppearanceSettings->settings().symbolSettings.lrfLineDisplayLengthMSec; if (displayDuration > 0) // if slider '0' -> do not show the line { auto it = m_lrfParticipantFakeGearEvents.find(partId); if (it != m_lrfParticipantFakeGearEvents.end()) { const qint64 lastFireEventMSec = it.value(); // (aktuelle Zeit - Zeit letztes FeuerEreignis) > Settings.symbolFireEventFlashingMSec const qint64 ageMSec = (curPartitionTimeMSec - lastFireEventMSec); if (ageMSec <= fireEventFlashingDurationMSec) { // INFO_LOG(m_logger) << LOGFUNCTION << " flash caused by LRF"; bFlash = true; } // cleanup container (to avoid unnecessary updateView()) if (ageMSec > (120 + 10) * 1000) // GUI: maximum is 120sec { m_lrfParticipantFakeGearEvents.erase(it); } } } } // faked GearEvents: LTD if (!bFlash) { const int displayDuration = m_symbolAppearanceSettings->settings().symbolSettings.ltdLineDisplayLengthMSec; if (displayDuration > 0) // if slider '0' -> do not show the line { auto it = m_ltdParticipantFakeGearEvents.find(partId); if (it != m_ltdParticipantFakeGearEvents.end()) { const qint64 lastFireEventMSec = it.value(); // (aktuelle Zeit - Zeit letztes FeuerEreignis) > Settings.symbolFireEventFlashingMSec const qint64 ageMSec = (curPartitionTimeMSec - lastFireEventMSec); if (ageMSec <= fireEventFlashingDurationMSec) { // INFO_LOG(m_logger) << LOGFUNCTION << " flash caused by LTD"; bFlash = true; } // cleanup container (to avoid unnecessary updateView()) if (ageMSec > (120 + 10) * 1000) // GUI: maximum is 120sec { m_ltdParticipantFakeGearEvents.erase(it); } } } } DEBUG_LOG(m_logger) << LOGFUNCTION << " curPartitionTimeMSec =" << curPartitionTimeMSec << " fireEventFlashingDurationMSec =" << fireEventFlashingDurationMSec << " bFlash =" << bFlash; if (bFlash) { // flashing switch (m_fireEventFlashTriState) { case 0: default: mapObj->forcePartyColor(QColor()); // set invalid color to let the partyId take effect break; case 1: mapObj->forcePartyColor(Qt::white); break; case 2: mapObj->forcePartyColor(Qt::gray); break; } } else { // not flashing mapObj->forcePartyColor(QColor()); // set invalid color to let the partyId take effect } if (doUpdateView) { if (!mapObj->updateView()) ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << mapObj->debugInfo() << "'" << " updateView failed"; } else addToUpdateNotify(mapObj); } // for INFO_LOG(m_logger) << LOGFUNCTION << " END"; } // function name is not really optimal ... // @return true when an additional symbol should be displayed, otherwise false static inline bool symbolVisbilityHelper(FilterManagement::SymbolAppearanceSettings::SymbolVisibility &curSv, FilterManagement::SymbolAppearanceSettings::SymbolVisibility requestedSv, bool bActive) { if (!bActive) return false; // Standard < Faded < Hidden switch (requestedSv) { case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::AdditionalSymbol: case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Standard: // curSv = curSv; break; case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Faded: curSv = (curSv == FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Hidden) ? curSv : requestedSv; break; case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Hidden: curSv = requestedSv; break; } return requestedSv == FilterManagement::SymbolAppearanceSettings::SymbolVisibility::AdditionalSymbol; } void Participant2SituationMap::handleSymbolAppearanceSettings(RefPointer &mapObj) { Q_UNUSED(mapObj); // Don`t use BaseClass implementation, handle this on our own } void Participant2SituationMap::onSymbolAppearanceSettingsChanged(FilterManagement::SymbolAppearanceSettings::Part part) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", appid=" << int(m_pluginProvider->appId()) << ", workspaceId=" << m_pluginProvider->workspaceId() << ", part=" << static_cast(part) << ", this=" << this; Q_UNUSED(part); // Don`t use BaseClass implementation, handle this on our own: if (!m_symbolAppearanceSettings) return; // TODO: check what really changed and only update this -> remember last filter and compare values // for now: only testing ... const bool relevant = symbolAppearanceSettingsChangesAreRelevant(m_symbolAppearanceSettings->settings()); m_currentSymbolSettings = m_symbolAppearanceSettings->settings(); const bool bShowCenterPosNew = m_currentSymbolSettings.symbolSettings.unitSymbolPosition == FilterManagement::SymbolAppearanceSettings::UnitSymbolPosition::Center; if (bShowCenterPosNew != m_bShowCenterPositions) { m_bShowCenterPositions = bShowCenterPosNew; for (auto it = m_participantPositions.begin(); it != m_participantPositions.end(); ++it) { InternalPosition &pos = it.value(); if (!pos.isUnit) continue; pos.bShowCenter = m_bShowCenterPositions; updateParticipantPosition(pos); } } if (!relevant) { DEBUG_LOG(m_logger) << LOGFUNCTION << " not relevant, skip"; return; } // // real work: // // MovementTrack: if (m_movementTrackManager) { int movementTrackLengthMSec = m_currentSymbolSettings.symbolSettings.movementTrackLengthMSec; m_movementTrackManager->setMaxViewAgeMsec(movementTrackLengthMSec); const QVector &simIds = m_movementTrackManager->activeTrackIds(); for (int simId : simIds) { RefPointer mapObj = this->getMapObjectOfSimId(simId); if (!mapObj.valid()) continue; const PositionCont &movementTrack = m_movementTrackManager->activeAndVisibleTrack(simId); mapObj->setMovementTrack(movementTrack); addToUpdateNotify(mapObj); } // for } for (const ParticipantManagement::Participant &participant : qAsConst(m_participants)) { const UniqueMapId uniqueMapId = m_simId2UniqueMapId.value(participant.id(), INVALID_MAP_ID); if (uniqueMapId == INVALID_MAP_ID) { DEBUG_LOG(m_logger) << LOGFUNCTION << "Found a participant which is in internal map but has no map object...: " << participant.id(); continue; } RefPointer mapObj = m_objFac.get(uniqueMapId); if (!mapObj.valid()) continue; handleSymbolAppearanceSettingsParticipant(mapObj, participant); } // apply filter directly notifyMapAboutModifedObjects(); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", appid=" << int(m_pluginProvider->appId()) << ", workspaceId=" << m_pluginProvider->workspaceId() << ", part=" << static_cast(part) << ", this=" << this; } static inline bool hasManipulation(const ParticipantManagement::DynamicParticipantData &dpd) { return dpd.manipulation().size() > 0; } bool Participant2SituationMap::hasBitError(const ParticipantManagement::DynamicParticipantData &dpd) const { return m_hasBiteError.contains(dpd.participantId()); } bool Participant2SituationMap::isSimulatedPosition(const InternalPosition &pos) const { return (pos.reportingSource == Core::PositionReportingSource::MovementSimulation || pos.reportingSource == Core::PositionReportingSource::SetByOperator); } bool Participant2SituationMap::isOutOfDate(const InternalPosition &pos) const { if (!m_pluginProvider->isSessionValid()) { DEBUG_LOG(m_logger) << LOGFUNCTION << " no m_session anymore: return"; return false; } return LtsUtils::isPositionTimeOutdated(m_pluginProvider->partitionTime(), pos.sampleTime()); } void Participant2SituationMap::handleSymbolAppearanceSettingsParticipant(int participantId) { const UniqueMapId uniqueMapId = m_simId2UniqueMapId.value(participantId, INVALID_MAP_ID); if (uniqueMapId == INVALID_MAP_ID) { DEBUG_LOG(m_logger) << LOGFUNCTION << " Got a participant which is in internal map but has no map object...: " << participantId; return; } RefPointer mapObj = m_objFac.get(uniqueMapId); if (!mapObj.valid()) return; if (!m_symbolAppearanceSettings) return; const auto itPart = m_participants.constFind(participantId); handleSymbolAppearanceSettingsParticipant(mapObj, itPart == m_participants.constEnd() ? ParticipantManagement::Participant() : itPart.value()); } void Participant2SituationMap::handleSymbolAppearanceSettingsParticipant(RefPointer &mapObj, const ParticipantManagement::Participant &participant) { const auto itDpd = m_participantDynamicData.constFind(participant.id()); const auto itPos = m_participantPositions.constFind(participant.id()); handleSymbolAppearanceSettingsParticipant(mapObj, participant, itPos == m_participantPositions.cend() ? InternalPosition() : itPos.value(), itDpd == m_participantDynamicData.cend() ? ParticipantManagement::DynamicParticipantData() : itDpd.value()); } void Participant2SituationMap::handleSymbolAppearanceSettingsParticipant(RefPointer &mapObj, const ParticipantManagement::Participant &participant, const InternalPosition &pPos, const ParticipantManagement::DynamicParticipantData &dpd) { if (!m_symbolAppearanceSettings) { addToUpdateNotify(mapObj); return; } const FilterManagement::SymbolAppearanceSettings::Settings &s = m_symbolAppearanceSettings->settings(); const auto l = s.symbolSettings.symbolPartyAppearance.value(participant.partyId()); mapObj->setTextModifierVisibility(Participant::TextModifier::T_UniqueDesignation, l.showDesignator); mapObj->setTextModifierVisibility(Participant::TextModifier::M_HigherFormation, l.showTacticalIdentifier); mapObj->setUserSymbolOpacity(1.0f - s.symbolSettings.symbolTransparency); mapObj->setScale(s.symbolSettings.symbolScale); bool bIsChanged = false; // combine the four visibility to current visibility FilterManagement::SymbolAppearanceSettings::SymbolVisibility sv = FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Standard; bIsChanged |= mapObj->setAdditionalSymbol(SituationMap::AdditionalSymbolType::BiteError, symbolVisbilityHelper(sv, s.symbolSettings.bitErrorVisibility, hasBitError(dpd))); bIsChanged |= mapObj->setAdditionalSymbol(SituationMap::AdditionalSymbolType::Manipulation, symbolVisbilityHelper(sv, s.symbolSettings.manipulationVisibility, hasManipulation(dpd))); bIsChanged |= mapObj->setAdditionalSymbol(SituationMap::AdditionalSymbolType::NoPosition, symbolVisbilityHelper(sv, s.symbolSettings.positionOutOfDateVisibility, isOutOfDate(pPos))); bIsChanged |= mapObj->setMarked(symbolVisbilityHelper(sv, s.symbolSettings.simulatedPositionVisibility, isSimulatedPosition(pPos))); bool bSymbolVisible = true; bool bSymbolIsFaded = false; switch (sv) { case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Standard: bSymbolVisible = true; bSymbolIsFaded = false; break; case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Faded: bSymbolVisible = true; bSymbolIsFaded = true; break; case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::Hidden: bSymbolVisible = false; bSymbolIsFaded = false; break; case FilterManagement::SymbolAppearanceSettings::SymbolVisibility::AdditionalSymbol: // should never happen, change nothing break; } if (!bSymbolVisible && s.symbolSettings.showFiringUnits && m_participantsWithProperties.value(participant.id(), false)) bSymbolVisible = true; if (!l.isVisible) bSymbolVisible = false; if (bSymbolVisible && !m_pluginProvider->isVisibleParty(participant.partyId())) bSymbolVisible = false; if (bSymbolVisible && s.hiddenTypeFilter.value(participant.partyId()).contains(participant.typeId())) bSymbolVisible = false; if (bSymbolVisible && hasGeoPositionNaN(pPos.position())) bSymbolVisible = false; if (bSymbolVisible && pPos.bIsMountedOn) bSymbolVisible = false; if (bSymbolVisible) { const auto youngestState = m_ppmDtc->youngestStateInformation(dpd.stateHistory()); if (s.hiddenStateFilter.value(participant.partyId()).contains(youngestState.vulnerabilityState().underlying())) bSymbolVisible = false; } if (bSymbolVisible && m_symbolAppearanceSettings->isParticipantVisibileCollapseExpand(participant.id())) bSymbolVisible = false; bool oldVisibility = mapObj->visibility(); mapObj->setVisibility(bSymbolVisible); mapObj->setUseLightColor(bSymbolIsFaded); // TODO: only call when really something changed // only update if mapObj is visible or becomes visible due to recent changes if (oldVisibility || bSymbolVisible) addToUpdateNotify(mapObj); // refresh MapObjectSelectionWidget or similar: if (bIsChanged) emit mapObjectChanged(mapObj->uniqueMapId()); TRACE_LOG(m_logger) << LOGFUNCTION << " Symbol visibilty for Entity: " << mapObj->debugInfo() << " with participant.id " << participant.id() << " and partyId " << participant.partyId() << " is " << bSymbolVisible; } void Participant2SituationMap::addToUpdateNotify(const RefPointer &mapObj) { if (mapObj) m_objectsToUpdate += mapObj; } void Participant2SituationMap::notifyMapAboutModifedObjects() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, m_objectsToUpdate.size() = " << m_objectsToUpdate.size() << ", this=" << this; for (RefPointer mapObj : qAsConst(m_objectsToUpdate)) { if (mapObj) mapObj->updateView(); } m_objectsToUpdate.clear(); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", this=" << this; } } // namespace SituationMap