/** * @RDE_HEADER@ * Interface * $Id$ * $HeadURL$ * $Author$ */ // Own #include "hitlineinfo_to_map.h" #include "pdm_handler.h" #include #include #include #include #include #include #include // Qt #include #include namespace SituationMap { HitlineInfo2SituationMap::HitlineInfo2SituationMap(PdmHandler *pdmHandler, Participant2SituationMap *participant2SituationMap, ObjectFactory &objFac, const QString &layername, int drawOrder, const char *loggerName) : BaseClass(objFac, layername, drawOrder, loggerName) , FilterClass(loggerName, this) , m_pdmHandler(pdmHandler) , m_participant2SituationMap(participant2SituationMap) , m_pBuildingHitlineCleanupTimer(new QTimer(this)) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; m_pBuildingHitlineCleanupTimer->setInterval(1000 * 60 * 5); // every 5 min should be enough connect(m_pBuildingHitlineCleanupTimer, &QTimer::timeout, this, &HitlineInfo2SituationMap::onCleanupBuildingHitlines); m_pBuildingHitlineCleanupTimer->start(); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } HitlineInfo2SituationMap::~HitlineInfo2SituationMap() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } int32_t HitlineInfo2SituationMap::getSimId(const Sim2SituationMap::DDS_T &ddsData) const { return ddsData.id(); } bool HitlineInfo2SituationMap::init(PluginProvider *pluginProvider) { if (!pluginProvider || !pluginProvider->ppmDtc()) return false; m_pluginProvider = pluginProvider; m_ppmDtc = pluginProvider->ppmDtc(); if (!m_ppmDtc) return false; auto bldDtc = m_pluginProvider->bldDtc(); if (bldDtc) { connect(bldDtc.get(), &Buildings::Interface::Dtc::buildingHitLineInfoChanged, this, &HitlineInfo2SituationMap::onBuildingHitlineInfoChanged); bldDtc->buildingHitLineInfos([this](const Buildings::BuildingHitLineInfo & data) { onBuildingHitlineInfoChanged(data, DataModification::eAdded); }); } // FilterManagement: FilterClass::init(m_pluginProvider); // PartitionTime connect(m_pluginProvider, &PluginProvider::partitionTimeChanged, this, &HitlineInfo2SituationMap::onPartitionTimeChanged); return true; } bool HitlineInfo2SituationMap::run() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; if (!m_ppmDtc) { ERROR_LOG(m_logger) << LOGFUNCTION << " no dtc"; return false; } connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::hitLineInfoChanged, this, &HitlineInfo2SituationMap::onHitLineInfoChanged); // get Hitlines: m_ppmDtc->hitLineInfos([this](const Sim2SituationMap::DDS_T & ddsData) { onHitLineInfoChanged(ddsData, DataModification::eAdded); }); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; return true; } void HitlineInfo2SituationMap::stop() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; if (m_ppmDtc) m_ppmDtc->disconnect(this); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } bool HitlineInfo2SituationMap::isElapsed(const ParticipantManagement::HitLineInfo &ddsData) const { // true -> hide // false -> show // GUEZE-2861 says we have to create LTD this way again //######################################################### // GUEZE-2550: // No LTD is created this way... // if (m_pdmHandler->isLTDChargeType(ddsData.chargeTypeId())) // return true; //######################################################### // [GUEZE-495]: 3D Lagedarstellung Trefferlinien // Data of real life: HitLineInfo contains (0,0,0) // Therefore the HitLine was densified to ~600000 vertices!!! // The slowed down the OpApp dramatically!! // Fix: ignore Hitlines with this -> elapsed const Core::GeoPosition &startGeoPos = ddsData.originatorPos(); const Core::GeoPosition &endGeoPos = ddsData.injuryPos(); { Point3D dummy(startGeoPos.lon(), startGeoPos.lat(), startGeoPos.z()); if (dummy.isNull()) return true; } { Point3D dummy(endGeoPos.lon(), endGeoPos.lat(), endGeoPos.z()); if (dummy.isNull()) return true; } // GUEZE-495: RPL Nr 769: 3D Lagedarstellung Trefferlinien // [DoD]: Es werden in der 3D-Ansicht keinerlei Trefferlinien oder deren // Bestandteile angezeigt, bei denen der Start- und/oder Endpunkt unterhalb // der Erdoberfläche liegt. In der 2D-Ansicht werden weiterhin alle // Trefferlinien (ober- sowie unterirdisch) angezeigt. if (m_objFac.map() && (m_objFac.map()->mode() == Mode::Mode_3D)) { if (startGeoPos.z() < 0) return true; if (endGeoPos.z() < 0) return true; } return false; } bool HitlineInfo2SituationMap::mapObjectsClicked(const QVector &uniqueMapIds) { auto ppmParticipantInfoIfc = m_pluginProvider->ppmParticipantInfoIfc(); if (!ppmParticipantInfoIfc) return false; const QVector &simIds = uniqueMapIds2SimIds(uniqueMapIds); if (simIds.isEmpty()) return true; ppmParticipantInfoIfc->showHitLineInfoWindow(simIds); return true; } QString HitlineInfo2SituationMap::chargeTypeIdentifier(const int32_t originatorChargeTypeId) const { if (!m_pdmHandler) return QStringLiteral("ctId_%1").arg(originatorChargeTypeId); QString chargeTypeString = m_pdmHandler->trChargeTypeShortName(originatorChargeTypeId); if (chargeTypeString.isEmpty()) return QStringLiteral("ctId_%1").arg(originatorChargeTypeId); return chargeTypeString; } bool HitlineInfo2SituationMap::unifiedMapObjectData(UniqueMapId uniqueMapId, UnifiedMapObjectData &umodOut) const { if (!m_ppmDtc) return false; int32_t simIdOut = -1; if (!this->uniqueMapId2SimId(uniqueMapId, simIdOut)) return false; umodOut = UnifiedMapObjectData(); RefPointer mapObj = this->getMapObjectOfUniqueMapId(uniqueMapId); if (mapObj.valid()) umodOut.m_image = mapObj->previewImage(); const auto cb = [this, simIdOut, &umodOut](const Sim2SituationMap::DDS_T & ddsData) -> bool { if (ddsData.id() == simIdOut) { umodOut.m_partyId = ddsData.originatorPartyId(); const int32_t originatorChargeTypeId = ddsData.chargeTypeId(); umodOut.m_description1 = chargeTypeIdentifier(originatorChargeTypeId); return true; // break loop } return false; }; // sonst kann sich der compiler nicht zwischen bool und void entscheiden... !? std::function cb1 = cb; return m_ppmDtc->hitLineInfos(cb1); } bool HitlineInfo2SituationMap::onHitLineInfoChanged(const Sim2SituationMap::DDS_T &ddsData, DataModification modType) { DEBUG_LOG(m_logger) << LOGFUNCTION << " START" << ", ddsData.id()=" << ddsData.id() << ", ddsData.injuryBuildingId()=" << ddsData.injuryBuildingId() << ", modType=" << modType << ", m_buildingHits.size()=" << m_buildingHits.size(); // avoid construction if invisible due to BldgHitline? if (((modType==DataModification::eAdded) || (modType==DataModification::eModified)) && (!visibleByBldgHitlines(ddsData)) ) { DEBUG_LOG(m_logger) << "Not showing participant hitline " << ddsData << " since there is already a hitline on the building"; return true; } updateDdsDataContainer(ddsData, modType); // helpfull for FilterManagement/onSymbolAppearanceSettingsChanged return handleChanged(ddsData, modType); } // same as in building viewer (BuildingViewerDdsController) void HitlineInfo2SituationMap::onBuildingHitlineInfoChanged(const Buildings::BuildingHitLineInfo &data, DataModification modType) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", data.id()=" << data.id() << ", data.originatorParticipantId()=" << data.originatorParticipantId() << ", data.buildingId()=" << data.buildingId() << ", modType=" << modType << ", m_buildingHits.size()=" << m_buildingHits.size(); if (modType != DataModification::eAdded && modType != DataModification::eModified) return; BuildingHit bh; bh.sampleTime = data.sampleTime(); bh.originatorParticipantId = data.originatorParticipantId(); bh.chargeTypeId = data.chargeTypeId(); m_buildingHits[data.buildingId()].push_back(bh); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", data.id()=" << data.id() << ", data.originatorParticipantId()=" << data.originatorParticipantId() << ", data.buildingId()=" << data.buildingId() << ", modType=" << modType << ", m_buildingHits.size()=" << m_buildingHits.size(); } void HitlineInfo2SituationMap::onCleanupBuildingHitlines() { const qint64 tNow = QDateTime::currentMSecsSinceEpoch(); const auto isOutdated = [tNow](const BuildingHit &bh) { return LtsUtils::isPositionTimeOutdated(tNow, bh.sampleTime); }; for (auto &hitLines : m_buildingHits) hitLines.erase(std::remove_if(hitLines.begin(), hitLines.end(), isOutdated), hitLines.end()); } bool HitlineInfo2SituationMap::createElement(const DDS_T &ddsData) { const int32_t simId = getSimId(ddsData); if (!m_simId2UniqueMapId.contains(simId)) { INFO_LOG(m_logger) << LOGFUNCTION << " new Hitline !!!!" << " simId = " << simId; const SpecialChargeType specialChargeTypeId = m_pdmHandler->isSpecialChargeType(ddsData.chargeTypeId()); if (PdmHandler::isChargeTypeLtdOrLrf(specialChargeTypeId)) m_participant2SituationMap->onParticipantFakeGearEventChanged(ddsData.originatorParticipantId(), ddsData.sampleTime(), specialChargeTypeId); // Note: // do not remove the FakeEvent 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 } return BaseClass::createElement(ddsData); } bool HitlineInfo2SituationMap::updateElementImpl(const Sim2SituationMap::DDS_T &ddsData, RefPointer mapObj) { const qint32 hitlineId = ddsData.id(); TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " id=" << hitlineId; // mapObj->setPickable(true); // default: pickable == true mapObj->setVisibility(SituationMap::ObjectAppearance::Tactical, false); mapObj->rLineStyle().setLineWidth(3); // will influence LineWith and ArrowSize // Line Style LineStyle ls = mapObj->lineStyle(); const SpecialChargeType sct = m_pdmHandler->isSpecialChargeType(ddsData.chargeTypeId()); switch (sct) { case SpecialChargeType::LRF: ls.setLinePattern(LinePattern::Dot); break; case SpecialChargeType::LTD: ls.setLinePattern(LinePattern::Solid); break; case SpecialChargeType::NoneSpecial: default: ls.setLinePattern(LinePattern::Dash); } // switch mapObj->setLineStyle(ls); // PartyId: mapObj->setPartyId(ddsData.originatorPartyId()); // Identifier const int32_t originatorChargeTypeId = ddsData.chargeTypeId(); mapObj->setIdentifier(chargeTypeIdentifier(originatorChargeTypeId)); // setStartAndEndPoint const Core::GeoPosition &startGeoPos = ddsData.originatorPos(); const Core::GeoPosition &endGeoPos = ddsData.injuryPos(); mapObj->setStartAndEndPoint(Point3D(startGeoPos.lon(), startGeoPos.lat(), startGeoPos.z()), Point3D(endGeoPos.lon(), endGeoPos.lat(), endGeoPos.z())); // FilterManagement: handleSymbolAppearanceSettings(mapObj, ddsData); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " id=" << hitlineId; return true; } void HitlineInfo2SituationMap::onSymbolAppearanceSettingsChanged(FilterManagement::SymbolAppearanceSettings::Part part) { if ((part != FilterManagement::SymbolAppearanceSettings::Part::All) && (part != FilterManagement::SymbolAppearanceSettings::Part::Symbol)) return; // Part::All || Part::Symbol const bool relevant = symbolAppearanceSettingsChangesAreRelevant(m_symbolAppearanceSettings->settings()); m_currentSymbolSettings = m_symbolAppearanceSettings->settings(); if (!relevant) return; handleSymbolAppearanceSettings(); } bool HitlineInfo2SituationMap::symbolAppearanceSettingsChangesAreRelevant(const FilterManagement::SymbolAppearanceSettings::Settings &newSas) const { return qFuzzyCompare(m_currentSymbolSettings.symbolSettings.symbolScale, newSas.symbolSettings.symbolScale) || (m_currentSymbolSettings.symbolSettings.symbolPartyAppearance != newSas.symbolSettings.symbolPartyAppearance) || (m_currentSymbolSettings.symbolSettings.hitLineDisplayLengthMSec != newSas.symbolSettings.hitLineDisplayLengthMSec|| (m_currentSymbolSettings.symbolSettings.lrfLineDisplayLengthMSec != newSas.symbolSettings.lrfLineDisplayLengthMSec)|| (m_currentSymbolSettings.symbolSettings.ltdLineDisplayLengthMSec != newSas.symbolSettings.ltdLineDisplayLengthMSec)); } void HitlineInfo2SituationMap::handleSymbolAppearanceSettings() { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_ddsDataContainer.size()=" << m_ddsDataContainer.size(); for (const ParticipantManagement::HitLineInfo &hitline : qAsConst(m_ddsDataContainer)) { RefPointer mapObj = getMapObjectOfSimId(hitline.id()); if (!mapObj.valid()) continue; handleSymbolAppearanceSettings(mapObj, hitline); } // for TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_ddsDataContainer.size()=" << m_ddsDataContainer.size(); } void HitlineInfo2SituationMap::handleSymbolAppearanceSettings(RefPointer mapObj, const Sim2SituationMap::DDS_T &ddsData) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " ddsData.id()=" << ddsData.id() << ", mapObj = " << mapObj->debugInfo(); const FilterManagement::SymbolAppearanceSettings::Settings &filterSettings = m_symbolAppearanceSettings->settings(); mapObj->setScale(filterSettings.symbolSettings.symbolScale); bool bUncertain = false; const bool isVisi = isVisible(ddsData, &bUncertain); if (bUncertain) { // N. Thoden, 25.09.2020 // Do NOT store mapObj at lambda! The lambda will be destroyed AFTER // HitlineInfo2SituationMap and the ESRI map. So the RefPointer is still // holding a MapObject. The destruction will fail due to non-existing ESRI map... QTimer::singleShot(200, this, [this, ddsData]() { RefPointer mapObj = getMapObjectOfSimId(ddsData.id()); if (!mapObj.valid()) return; handleSymbolAppearanceSettings(mapObj, ddsData); }); } if (mapObj->visibility() != isVisi) { mapObj->setVisibility(isVisi); if (!mapObj->updateView()) WARNING_LOG(m_logger) << LOGFUNCTION << " updateView failed," << " mapObj = " << mapObj->debugInfo(); } TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " ddsData.id()=" << ddsData.id() << ", mapObj = " << mapObj->debugInfo(); } bool HitlineInfo2SituationMap::isVisible(const Sim2SituationMap::DDS_T &ddsData, bool *bUncertain) const { return visibleByBldgHitlines(ddsData) && visibleBySymbolPartyAppearance(ddsData) && visibleByHitLineDisplayLength(m_pluginProvider->partitionTime(), ddsData) && // partitionTime() -> msec visibleByParticipants(ddsData, bUncertain); } bool HitlineInfo2SituationMap::visibleBySymbolPartyAppearance(const Sim2SituationMap::DDS_T &ddsData) const { const FilterManagement::SymbolAppearanceSettings::Settings &filterSettings = m_symbolAppearanceSettings->settings(); const auto spa = filterSettings.symbolSettings.symbolPartyAppearance.value(ddsData.originatorPartyId()); return spa.isVisible; } bool HitlineInfo2SituationMap::visibleByParticipants(const Sim2SituationMap::DDS_T &ddsData, bool *bUncertain) const { // show hitline only if // ddsData.originatorParticipantId() && ddsData.injuryParticipantId() are visible: if (!m_participant2SituationMap) return true; bool bParticipant1Exists = true; bool bParticipant2Exists = true; bool visi = m_participant2SituationMap->isParticipantVisible(ddsData.originatorParticipantId(), &bParticipant1Exists) && m_participant2SituationMap->isParticipantVisible(ddsData.injuryParticipantId(), &bParticipant2Exists); if ((!bParticipant1Exists || !bParticipant2Exists) && bUncertain) *bUncertain = true; return visi; } bool HitlineInfo2SituationMap::visibleByBldgHitlines(const Sim2SituationMap::DDS_T &ddsData) const { auto itBuildingHit = m_buildingHits.constFind(ddsData.injuryBuildingId()); if (itBuildingHit != m_buildingHits.constEnd()) { const QVector &bHits = itBuildingHit.value(); // get displayLength according chargeTypeId: qint64 displayLength = m_currentSymbolSettings.symbolSettings.hitLineDisplayLengthMSec; const SpecialChargeType sct = m_pdmHandler->isSpecialChargeType(ddsData.chargeTypeId()); switch (sct) { case SpecialChargeType::LRF: displayLength = m_currentSymbolSettings.symbolSettings.lrfLineDisplayLengthMSec; break; case SpecialChargeType::LTD: displayLength = m_currentSymbolSettings.symbolSettings.ltdLineDisplayLengthMSec; break; case SpecialChargeType::NoneSpecial: default: // empty break; } // switch // player is in a building which got hit in the last few minutes for (const BuildingHit &bh : bHits) { if (bh.chargeTypeId == ddsData.chargeTypeId() && bh.originatorParticipantId == ddsData.originatorParticipantId() && ddsData.sampleTime() - displayLength < bh.sampleTime) { DEBUG_LOG(m_logger) << "Not showing participant hitline " << ddsData << " since there is already a hitline on the building"; return false; } } } return true; } bool HitlineInfo2SituationMap::visibleByHitLineDisplayLength(const qint64 newPartitionTime, const Sim2SituationMap::DDS_T &ddsData) const { const qint64 stateTime = ddsData.sampleTime(); qint64 displayLength = m_currentSymbolSettings.symbolSettings.hitLineDisplayLengthMSec; const SpecialChargeType sct = m_pdmHandler->isSpecialChargeType(ddsData.chargeTypeId()); switch (sct) { case SpecialChargeType::LRF: displayLength = m_currentSymbolSettings.symbolSettings.lrfLineDisplayLengthMSec; break; case SpecialChargeType::LTD: displayLength = m_currentSymbolSettings.symbolSettings.ltdLineDisplayLengthMSec; break; case SpecialChargeType::NoneSpecial: default: // empty break; } // switch DEBUG_LOG(m_logger) << LOGFUNCTION << " newPartitionTime = " << newPartitionTime << " stateTime = " << stateTime << " age = " << (newPartitionTime - stateTime) << " SpecialChargeType = " << sct << " displayLengthMSec = " << displayLength; if (displayLength <= 0) return false; return (newPartitionTime - stateTime) <= displayLength; } void HitlineInfo2SituationMap::onPartitionTimeChanged() { handleSymbolAppearanceSettings(); } }