/** * Controller ShootbackAreas (L400) * $Id$ * $HeadURL$ * $Author$ * Created by N. Thoden, NLE, 06.04.2020 */ #include "shootback_controller.h" #include "object_shootback_area.h" #include "object_shootback_target.h" #include "object_target_area_assignment.h" #include "main_handler.h" #include // isPositionTimeOutdated #include #include #include #include #include //------------------------------------------------------------------------------ namespace TargetManagement { // which data can change: // ~static data // - onShootbackArea // - targetAreaAssignment // ~dynamic data // - participantPosition // - targetPosition (moving target) // Step 1: Which participants are in an area? // onParticipantPositionChanged, onShootbackAreaChanged1, onShootbackAreaChanged2 // -> updateInternal_participantPerArea (better updateParticipantsPerAreaCont!?) // Step 2: updateInternalLos -> updateInternalLosSingleAssignment // Step 3: syncExternal -> transfer data to DDS // // some helper ease the logging of some data // QString toString(const QSet &cont) { QString ret = QString(QStringLiteral("QSet(%1)")).arg(cont.size()); for (auto val : cont) ret += QString(QStringLiteral("(%1)")).arg(val); return ret; } QString toString(const std::vector &cont) { QString ret = QString(QStringLiteral("std::vector(%1)")).arg(cont.size()); for (auto val : cont) ret += QString(QStringLiteral("(%1)")).arg(val); return ret; } // // class ShootbackController // ShootbackController::ShootbackController(MainHandler *mainHandler) : BaseClass(mainHandler) , m_mainHandler(mainHandler) , m_elevationMap(nullptr) , m_losCalculation(nullptr) , m_logger(getLogger("ShootbackController")) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } ShootbackController::~ShootbackController() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; // disconnect external "signals".. disconnect(m_mainHandler->dds()->dtcPpm().get()); disconnect(m_handlerShootbackArea.get()); // the assignments.. disconnect(m_handlerTargetAreaAssignment.get()); delete m_losCalculation; m_losCalculation = nullptr; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } bool ShootbackController::initLOS(SituationMap::ElevationMap *elevationMap) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, elevationMap = " << elevationMap << " isValid = " << elevationMap->isValid(); if (!elevationMap->isValid()) { ERROR_LOG(m_logger) << LOGFUNCTION << " elevationMap not valid -> return"; return false; } delete m_losCalculation; m_elevationMap = elevationMap; INFO_LOG(m_logger) << LOGFUNCTION << " m_elevationMap.isValid() =" << m_elevationMap->isValid(); m_losCalculation = new SituationMap::LineOfSightCalculation(m_elevationMap); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; return true; } bool ShootbackController::initDds(std::shared_ptr handlerArea, std::shared_ptr handlerTarget, std::shared_ptr handlerTargetAreaAssignment) { m_handlerShootbackArea = handlerArea; m_handlerShootbackTarget = handlerTarget; m_handlerTargetAreaAssignment = handlerTargetAreaAssignment; // ShootbackArea connect(m_handlerShootbackArea.get(), &HandlerShootbackArea::objectChanged, this, &ShootbackController::onShootbackAreaChanged1, Qt::UniqueConnection); m_handlerShootbackArea->objectShootbackAreas([this](const TargetManagement::ObjectShootbackArea & data) { onShootbackAreaChanged2(data); }); // ShootbackTarget connect(m_handlerShootbackTarget.get(), &HandlerShootbackTarget::objectChanged, this, &ShootbackController::onShootbackTargetChanged, Qt::UniqueConnection); // TargetAreaAssignment connect(m_handlerTargetAreaAssignment.get(), &HandlerTargetAreaAssignment::objectChanged, this, &ShootbackController::onTargetAreaAssignmentChanged, Qt::UniqueConnection); // finally: // PPM: connect(m_mainHandler->dds()->dtcPpm().get(), &ParticipantManagement::Interface::Dtc::participantPositionChanged, this, &ShootbackController::onParticipantPositionChanged, Qt::UniqueConnection); return true; } void ShootbackController::onShootbackAreaChanged1(qint32 areaId, bool removed) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " shootbackAreaId=" << areaId << " removed=" << removed; if (removed) { // handle removed area: if (m_participantsPerArea.contains(areaId)) { m_participantsPerArea.remove(areaId); updateInternalLos({}, {}, {}); } } else { const QHash &areaMap = m_handlerShootbackArea->getObjectMap(); auto it = areaMap.find(areaId); if (it == areaMap.end()) { ERROR_LOG(m_logger) << LOGFUNCTION << " obj changes, but connot be found" << " shootbackAreaId=" << areaId; return; } onShootbackAreaChanged2(*(it.value())); updateInternalLos({}, {areaId}, {}); } syncExternal(); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " shootbackAreaId=" << areaId << " removed=" << removed; } void ShootbackController::onShootbackAreaChanged2(const TargetManagement::ObjectShootbackArea &area) { const int areaId = area.getId(); TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " shootbackAreaId=" << areaId; // handle new/modified area and check all participants: const QMap &participantPositions = m_mainHandler->dds()->dtcPpm()->participantPositions(); for (const ParticipantManagement::ParticipantPosition &posData : participantPositions) { // ToDo: posData relevant??? updateInternal_participantPerArea(area, posData); } // for TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " shootbackAreaId=" << areaId; } void ShootbackController::onShootbackTargetChanged(qint32 targetId, bool removed) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", targetId=" << targetId << ", removed=" << removed; // Note: // updateInternalLos is not required if (removed == true) // syncExternal depends on m_visibleParticipantsPerAssignment (via visibleParticipantsPerTargetParticipantId) if (removed) { // Due to the foreignKey the according assignments should be removed before // to nothing: return; } else updateInternalLos({}, {}, {targetId}); syncExternal(); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " targetId=" << targetId << " removed=" << removed; } void ShootbackController::onTargetAreaAssignmentChanged(qint32 assignmentId, bool removed) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " assignmentId=" << assignmentId << " removed=" << removed; // Note: // updateInternalLos is not required on removed == true // syncExternal depends on m_visibleParticipantsPerAssignment (via visibleParticipantsPerTargetParticipantId) if (removed) m_visibleParticipantsPerAssignment.remove(assignmentId); else updateInternalLos({assignmentId}, {}, {}); syncExternal(); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " assignmentId=" << assignmentId << " removed=" << removed; } void ShootbackController::onParticipantPositionChanged(const ParticipantManagement::ParticipantPosition &posData, DataModification modType) { TRACE_LOG(m_logger) << LOGFUNCTION << " participantId changed, participantId=" << posData << " mod=" << modType; if (!participantPositionTopicIsValid(posData)) { // DEBUG_LOG(m_logger) << LOGFUNCTION << " NOT VALID, participantId=" << posData.participantId(); return; } const int participantId = posData.participantId(); bool bIsTarget = false; if (isTarget(participantId, bIsTarget) && bIsTarget) { INFO_LOG(m_logger) << LOGFUNCTION << " TARGET, participantId=" << participantId; ObjectShootbackTarget *osbTarget = m_handlerShootbackTarget->dataForTargetParticipantId(participantId); if (!osbTarget) { ERROR_LOG(m_logger) << LOGFUNCTION << " ObjectShootbackTarget not found for participantId=" << participantId; return; } osbTarget->updateVolatileData(posData.pos()); } INFO_LOG(m_logger) << LOGFUNCTION << " participantId=" << participantId << ", isTarget=" << bIsTarget; if ((modType == DataModification::eAdded) || (modType == DataModification::eModified)) { // update const QHash &objMap = m_handlerShootbackArea->getObjectMap(); DEBUG_LOG(m_logger) << LOGFUNCTION << " objMap.size" << objMap.size(); QMapIterator< int, QSet> it(m_participantsPerArea); while (it.hasNext()) { it.next(); DEBUG_LOG(m_logger) << " areaId = " << it.key() << "-> partIdRef VORHERXX" << toString(it.value()); } for (const auto &area : qAsConst(objMap)) { if (area) updateInternal_participantPerArea(*area, posData); } // for { QMapIterator> it_inner(m_participantsPerArea); while (it_inner.hasNext()) { it_inner.next(); DEBUG_LOG(m_logger) << " areaId = " << it_inner.key() << "-> partIdRef NACHHERXX" << toString(it_inner.value()); } } updateInternalLos({}, {}, {}, participantId); } else { removeParticipantFromCont(m_participantsPerArea, participantId); removeParticipantFromCont(m_visibleParticipantsPerAssignment, participantId); } syncExternal(); } bool ShootbackController::participantPositionTopicIsValid(const ParticipantManagement::ParticipantPosition &posData) { // maybe add or replace: // (!LtsUtils::isPositionTimeOutdated(ddsHandler->currentPartitionTime(), sampleTime)) && // participantTypeId // maybe check: typeId -> typeClassId... return ((posData.participantId() > 0) && (posData.partyId() >= 2) && // 0->POOLS ; 1 -> O/C Service ; 2 RED... (!LtsUtils::isPositionTimeOutdated(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch(), posData.sampleTime())) ); } bool ShootbackController::isTarget(qint32 participantId, bool &bIsTarget) const { // maybe move this code to ppm as convenience!? std::shared_ptr dtcPpm = m_mainHandler->dds()->dtcPpm(); if (!dtcPpm) { ERROR_LOG(m_logger) << LOGFUNCTION << " no dtcPpm, return"; return false; } const ParticipantManagement::Participant &participant = dtcPpm->participant(participantId); if (participant.id() != participantId) return false; const int typeId = participant.typeId(); const ParticipantManagement::Type &type = dtcPpm->type(typeId); if (type.id() != typeId) return false; const int equipmentTypeId = type.equipmentTypeId(); const ParticipantManagement::EquipmentType &eqType = dtcPpm->equipmentType(equipmentTypeId); if (eqType.id() != equipmentTypeId) return false; switch (eqType.wmsTypeClass().underlying()) { case ParticipantManagement::WmsTypeClass::None: case ParticipantManagement::WmsTypeClass::Building: case ParticipantManagement::WmsTypeClass::Vehicle: case ParticipantManagement::WmsTypeClass::Human: case ParticipantManagement::WmsTypeClass::Airborne: case ParticipantManagement::WmsTypeClass::VehicleMarker: bIsTarget = false; break; case ParticipantManagement::WmsTypeClass::TargetVehicle: case ParticipantManagement::WmsTypeClass::TargetHuman: bIsTarget = true; break; default: break; } // switch return true; } void ShootbackController::updateInternal_participantPerArea(const ObjectShootbackArea &area, const ParticipantManagement::ParticipantPosition &posData) { const int areaId = area.getId(); const int participantId = posData.participantId(); bool contains = areaContainsPosition(area, posData); QSet &partIdRef = m_participantsPerArea[areaId]; bool alreadyInCont = partIdRef.contains(participantId); DEBUG_LOG(m_logger) << " areaId = " << area.getId() << " ---> partIdRef VORHER" << toString(partIdRef); DEBUG_LOG(m_logger) << LOGFUNCTION << " " << " shootbackAreaId=" << areaId << " shootbackAreaName=" << area.getName() << " area.position=" << area.getData().position() << " participantId=" << participantId << " ParticipantPosition=" << posData << " contains=" << contains << " alreadyInCont=" << alreadyInCont; if (contains && !alreadyInCont) { // ENTER partIdRef.insert(participantId); DEBUG_LOG(m_logger) << LOGFUNCTION << " ENTER: participantId=" << participantId << " areaId=" << areaId; } else if (!contains && alreadyInCont) { // LEAVE DEBUG_LOG(m_logger) << LOGFUNCTION << " ENTER: participantId=" << participantId << " areaId=" << areaId; partIdRef.remove(participantId); } DEBUG_LOG(m_logger) << " areaId = " << area.getId() << " ---> partIdRef NACHER" << toString(partIdRef); } bool ShootbackController::areaContainsPosition(const TargetManagement::ObjectShootbackArea &area, const ParticipantManagement::ParticipantPosition &posData) { const ShootbackArea &sbArea = area.getData(); const LtsCoordTrans::UtmCoordinate &utmCoordCenter = LtsCoordTrans::UtmCoordinate::fromGeographic(sbArea.position().lon(), sbArea.position().lat()); double xMin = utmCoordCenter.x - (sbArea.width() / 2.0); double xMax = utmCoordCenter.x + (sbArea.width() / 2.0); double yMin = utmCoordCenter.y - (sbArea.height() / 2.0); double yMax = utmCoordCenter.y + (sbArea.height() / 2.0); const LtsCoordTrans::UtmCoordinate &utmCoordParticipant = LtsCoordTrans::UtmCoordinate::fromGeographic(posData.pos().lon(), posData.pos().lat()); if ( (xMin <= utmCoordParticipant.x) && (utmCoordParticipant.x <= xMax) && (yMin <= utmCoordParticipant.y) && (utmCoordParticipant.y <= yMax) ) return true; return false; } void ShootbackController::removeParticipantFromCont(QMap> &contRef, int participantId) { // removed QMutableMapIterator>it(contRef); while (it.hasNext()) { it.next(); QSet &partIdRef = it.value(); partIdRef.remove(participantId); } } QSet ShootbackController::areasWithParticipant(qint32 participantId) const { QSet relAreaIds; QMapIterator>it(m_participantsPerArea); while (it.hasNext()) { it.next(); const QSet &partIdRef = it.value(); if (partIdRef.contains(participantId)) relAreaIds.insert(it.key()); // it.key() == areaId } return relAreaIds; } QSet ShootbackController::assignmentsWithTargetParticipantId(qint32 targetParticipantId) const { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " targetParticipantId=" << targetParticipantId; QSet ret; // all assignments const QHash &assignmentMap = m_handlerTargetAreaAssignment->getObjectMap(); for (ObjectTargetAreaAssignment *targetAreaAssignment : assignmentMap) { if (!targetAreaAssignment) continue; if (targetAreaAssignment->getData().targetParticipantId() == targetParticipantId) ret += targetAreaAssignment->getId(); } // for TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " areaId=" << targetParticipantId; return ret; } bool idIsRelevantHelper(const QSet &container, int id) { if (container.isEmpty()) return true; return container.contains(id); } void ShootbackController::updateInternalLos(const QSet &changingAssignmentIds, // ~static const QSet &changingAreaIds, // ~static const QSet &changingTartgetIds, // ~dynamic int changedParticipantId) // ~dynamic { DEBUG_LOG(m_logger) << LOGFUNCTION << " START" << ", changingAssigmentIds=" << toString(changingAssignmentIds) << ", changingAreaIds=" << toString(changingAreaIds) << ", changingTartgetIds=" << toString(changingTartgetIds) << ", changedParticipantId=" << changedParticipantId; // loop over every assignment const QHash &allAssignments = m_handlerTargetAreaAssignment->getObjectMap(); DEBUG_LOG(m_logger) << LOGFUNCTION << " allAssignments.size() " << allAssignments.size(); for (ObjectTargetAreaAssignment *assignment : allAssignments) { if (!assignment) { DEBUG_LOG(m_logger) << LOGFUNCTION << " continue, curAssignment " << assignment->getId(); continue; } // 1. check changingAssigmentIds const int assignmentId = assignment->getId(); if (!idIsRelevantHelper(changingAssignmentIds, assignmentId)) { DEBUG_LOG(m_logger) << LOGFUNCTION << " continue, curAssignment " << assignment->getId(); continue; } const TargetAreaAssignment &assignmentData = assignment->getData(); // 2. check changingAreaIds const int areaId = assignmentData.areaId(); if (!idIsRelevantHelper(changingAreaIds, areaId)) { DEBUG_LOG(m_logger) << LOGFUNCTION << " continue, curAssignment " << assignment->getId(); continue; } // 3. check changingTartgetIds const int targetParticpantId = assignmentData.targetParticipantId(); const int targetDataId = m_handlerShootbackTarget->targetDataIdForTargetParticipantId(targetParticpantId); if (!idIsRelevantHelper(changingTartgetIds, targetDataId)) { DEBUG_LOG(m_logger) << LOGFUNCTION << " continue, curAssignment " << assignment->getId() << ", changingTartgetIds=" << toString(changingTartgetIds) << ", targetParticpantId=" << targetParticpantId << ", targetDataId=" << targetDataId; continue; } // 4. check changedParticipantId if (changedParticipantId > 0) { // Note: m_participantsPerArea is up-to-date QSet areasWithPart = areasWithParticipant(changedParticipantId); DEBUG_LOG(m_logger) << LOGFUNCTION << " areasWithPart=" << areasWithPart.size(); for (auto curAreaId : qAsConst(areasWithPart)) DEBUG_LOG(m_logger) << LOGFUNCTION << " -> curAreaId=" << curAreaId; if (!idIsRelevantHelper(areasWithPart, areaId)) { DEBUG_LOG(m_logger) << LOGFUNCTION << " continue, curAssignment " << assignment->getId(); continue; } } // real work: // one assignment -> single target, single area, some participants (which are in that single area): if (!updateInternalLosSingleAssignment(assignment)) ERROR_LOG(m_logger) << LOGFUNCTION << " updateInternalLosSingleAssignment failed, assignmentId=" << assignment->getId(); } // for TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", changingAssigmentIds=" << changingAssignmentIds.size() << ", changingAreaIds=" << changingAreaIds.size() << ", changingTartgetIds=" << changingTartgetIds.size() << ", changedParticipantId=" << changedParticipantId; } bool ShootbackController::updateInternalLosSingleAssignment(ObjectTargetAreaAssignment *assignment) { if (!assignment) return false; const int assignmentId = assignment->getId(); TRACE_LOG(m_logger) << LOGFUNCTION << " START, assignmentId=" << assignmentId; const TargetAreaAssignment &assignmentData = assignment->getData(); const int areaId = assignmentData.areaId(); const int targetParticipantId = assignmentData.targetParticipantId(); DEBUG_LOG(m_logger) << LOGFUNCTION << " START, assignmentId=" << assignmentId << ", areaId = " << areaId << ", targetParticipantId = " << targetParticipantId; ObjectShootbackArea *area = m_handlerShootbackArea->data(areaId); if (!area) { // The area seems to be removed (no foreignKey area <-> assignment??) // Neverless, for this assignment -> area there are no participants visible: // clear container: m_visibleParticipantsPerAssignment[assignmentId] = {}; ERROR_LOG(m_logger) << LOGFUNCTION << " no area for areaId=" << areaId; return false; } ObjectShootbackTarget *target = m_handlerShootbackTarget->dataForTargetParticipantId(targetParticipantId); if (!target) { ERROR_LOG(m_logger) << LOGFUNCTION << " no target for targetParticipantId=" << targetParticipantId; return false; } QSet visiPartCont; if (m_participantsPerArea.contains(areaId)) { QSet &partIdRef = m_participantsPerArea[areaId]; for (auto participantId : qAsConst(partIdRef)) { // check lineOfSight: DEBUG_LOG(m_logger) << LOGFUNCTION << " check lineOfSight for: assignmentId=" << assignmentId << ", areaId = " << areaId << ", targetParticipantId = " << targetParticipantId << ", participantId=" << participantId; // 1 targetGeoPos ShootbackTarget sbTarget = target->getData(); const Core::GeoPosition &sbTargetGeoPos = sbTarget.position(); // 2 participantId -> geoPos: const ParticipantManagement::ParticipantPosition &participantPos = m_mainHandler->dds()->dtcPpm()->participantPosition(participantId); if (participantPos.participantId() != participantId) { ERROR_LOG(m_logger) << LOGFUNCTION << " no position for participantId=" << participantId; return false; } const Core::GeoPosition &participantGeoPos = participantPos.pos(); bool visi = lineOfSight(sbTargetGeoPos, participantGeoPos); DEBUG_LOG(m_logger) << LOGFUNCTION << " current targetId = " << target->getId() << " visi = " << visi; if (visi) visiPartCont.insert(participantId); else visiPartCont.remove(participantId); } // for } // if (m_participantsPerArea.contains(areaId)) DEBUG_LOG(m_logger) << LOGFUNCTION << " assignmentId = " << assignmentId << " visiPartCont.size() " << visiPartCont.size(); // store at member m_visibleParticipantsPerAssignment[assignmentId] = visiPartCont; TRACE_LOG(m_logger) << LOGFUNCTION << " END, assignmentId=" << assignmentId; return true; } QSet ShootbackController::visibleParticipantsPerTargetParticipantId(int targetParticipantId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, targetParticipantId = " << targetParticipantId; QSet visiPartPerTarget; const QSet &assignmentsWithTargetParticipantIdCont = assignmentsWithTargetParticipantId(targetParticipantId); for (auto assignmentId : assignmentsWithTargetParticipantIdCont) { if (m_visibleParticipantsPerAssignment.contains(assignmentId)) visiPartPerTarget += m_visibleParticipantsPerAssignment[assignmentId]; } TRACE_LOG(m_logger) << LOGFUNCTION << " END, targetParticipantId = " << targetParticipantId << ", visiPartPerTarget = " << toString(visiPartPerTarget); return visiPartPerTarget; } void ShootbackController::syncExternal() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; // loop over all targets const QHash &allTargets = m_handlerShootbackTarget->getObjectMap(); INFO_LOG(m_logger) << LOGFUNCTION << " allTargets.size() " << allTargets.size(); for (ObjectShootbackTarget *target : allTargets) { if (!target) continue; const QSet &visiPartPerTarget = visibleParticipantsPerTargetParticipantId(target->getTargetParticipantId()); // QSet -> unique and sorted std::vector visiPartPerTargetStd; visiPartPerTargetStd.reserve(visiPartPerTarget.size()); INFO_LOG(m_logger) << LOGFUNCTION << " targetId " << target->getId() << ", visiPartPerTarget = " << toString(visiPartPerTarget); for (auto id : qAsConst(visiPartPerTarget)) visiPartPerTargetStd.push_back(id); const ShootbackTarget &sbTarget = target->getData(); if (visiPartPerTargetStd != sbTarget.visibleParticipants()) { // data has changed INFO_LOG(m_logger) << LOGFUNCTION << " targetId differ -> updateObject.."; INFO_LOG(m_logger) << LOGFUNCTION << " visiOld =" << toString(sbTarget.visibleParticipants()); INFO_LOG(m_logger) << LOGFUNCTION << " visiNew =" << toString(visiPartPerTargetStd); target->updateVolatileData(visiPartPerTargetStd); // update and publish } } // for TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } bool ShootbackController::lineOfSight(const Core::GeoPosition &observerPos, const Core::GeoPosition &targetPos) const { DEBUG_LOG(m_logger) << LOGFUNCTION << " calculate m_losCalculation = " << m_losCalculation << ", observerPos=" << observerPos << ", targetPos=" << targetPos; if (!m_losCalculation) { ERROR_LOG(m_logger) << LOGFUNCTION << " Error: no m_losCalculation"; return false; } SituationMap::ObserverTargetData observerTarget(Point3D(observerPos.lon(), observerPos.lat(), observerPos.z() + 2.0), // 2.0m als Augenhöhe Point3D(targetPos.lon(), targetPos.lat(), observerPos.z() + 2.0) // 2.0m als Augenhöhe ); bool success = m_losCalculation->calculate(observerTarget); if (!success) { ERROR_LOG(m_logger) << LOGFUNCTION << " calculate failed:" << " observerPos=" << observerPos << " targetPos=" << targetPos; return false; } const SituationMap::LineOfSightVisibility visi = observerTarget.lineOfSightVisibility(); return SituationMap::LineOfSightVisibility::Visible == visi; } } // Namespace TargetManagement