Files
qt5-legatus-legacy/Lts/Plugins/SituationMapPlugin/Gui/SituationMap/AdapterDds/participant_to_map.cpp
T
2026-08-13 18:33:10 +02:00

1893 lines
69 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Interface
* $Id$
* $HeadURL$
* $Author$
*/
#include "participant_to_map.h"
#include "pdm_handler.h"
#include <Map/map.h>
#include <Map/object_factory.h>
#include <Map/layer_factory.h>
#include <Map/party_id_to_color_functor.h>
#include <PluginInterfaces/gui_plugin_interface.h>
#include <LtsUtilsCore/outdated_position.h>
#include <LtsSession/lts_session.h>
#include <LtsSession/lts_session_data.h>
#include <set> // std::set
#include <Symbology/symbol_data_to_render_role.h>
#include <ProjectDataManagement/pdm_to_string.h>
#include <ParticipantManagement/ppm_enums.hpp>
#include <QJsonDocument>
//Qt
#include <QElapsedTimer>
#include <QTimer>
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<int>()); // 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<int> &simIds = m_movementTrackManager->activeTrackIds();
for (int simId : simIds)
onMovementTrackActivityChanged(simId, true);
}
{
const QVector<int> &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<UniqueMapId> &uniqueMapIds)
{
if (!m_ppmParticipantInfoIfc && !m_ocParticipantInfoIfc)
return false;
const QVector<int32_t> &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<SituationMap::Participant> 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<MAP_T> 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<int> simIds;
if (m_movementTrackManager)
{
m_movementTrackManager->setCurrentTimeMsec(newPartitionTime);
simIds = m_movementTrackManager->activeTrackIds();
for (int32_t simId : qAsConst(simIds))
{
RefPointer<MAP_T> 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<qint32> 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 <typename T>
void updateContainer(QHash<int, T> &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<Sim2SituationMap::MAP_T> mapObj = m_objFac.get<Sim2SituationMap::MAP_T>(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<Sim2SituationMap::MAP_T> mapObj = m_objFac.get<Sim2SituationMap::MAP_T>(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<MAP_T> 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<AreaEffectSimulation::AesType::inner_enum> 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<MAP_T> 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<int> &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<MAP_T> 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 -> <originatorParticipantId, participantId>
if (m_unknownOriginatorsParticipant.isEmpty())
return;
auto cb = [this, possibleOriginatorParticipantId](QPair<int, int> 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 -> <originEffectSimulationId, participantId>
if (m_unknownOriginatorsAes.isEmpty())
return;
auto cb = [this, possibleOriginatorEffectSimId](QPair<int, int> 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<Sim2SituationMap::MAP_T> 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<QPair<int, int>> validGearCharges;
validGearCharges.reserve(data.gear().size());
for (auto const &gear : data.gear())
{
QPair<int, int> candidate(gear.gearTypeId(), gear.chargeTypeId());
if (!validGearCharges.contains(candidate))
validGearCharges.append(candidate);
} // for
// now duplicates should be removed
QVector<int> gearRanges;
gearRanges.reserve(data.gear().size());
for (const QPair<int, int> &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<ParticipantManagement::LaserDesignation> &designations = data.designations();
// convert data be transfered in via a single call: (easier to handle at ParticipantImpl)
QVector<Participant::DesignationData> 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<SituationMap::Participant> 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<RefPointer<MAP_T>> it(m_objectsToUpdate);
while (it.hasNext())
{
const RefPointer<MAP_T> &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<int> particpantIdsWithGearEvents;
particpantIdsWithGearEvents.reserve(m_participantGearEvents.size() + m_lrfParticipantFakeGearEvents.size() + m_ltdParticipantFakeGearEvents.size());
particpantIdsWithGearEvents += QSet<int>(m_participantGearEvents.keyBegin() , m_participantGearEvents.keyEnd());
particpantIdsWithGearEvents += QSet<int>(m_lrfParticipantFakeGearEvents.keyBegin(), m_lrfParticipantFakeGearEvents.keyEnd());
particpantIdsWithGearEvents += QSet<int>(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<int> &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<MAP_T> mapObj = m_objFac.get<MAP_T>(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<MAP_T> &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<int>(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<int> &simIds = m_movementTrackManager->activeTrackIds();
for (int simId : simIds)
{
RefPointer<MAP_T> 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<MAP_T> mapObj = m_objFac.get<MAP_T>(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<int>(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<MAP_T> mapObj = m_objFac.get<MAP_T>(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<MAP_T> &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<MAP_T> &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<MAP_T> &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<MAP_T> mapObj : qAsConst(m_objectsToUpdate))
{
if (mapObj)
mapObj->updateView();
}
m_objectsToUpdate.clear();
TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", this=" << this;
}
} // namespace SituationMap