/** * Handler for the HMI language * $Id$ * $HeadURL$ * $Author$ */ #include "object_mine_area.h" #include "handler_mine_area.h" #include "qtransform.h" #include "special_area_provider.h" #include "Utils/polygons.h" #include #include #include #include // LtsUtils::toOptional #include #include namespace AreaEffectSimulation { namespace Database { bool operator==(const AesMineAreaSegment &a, const AesMineAreaSegment &b) { return ((a.id == b.id) && (a.mineAreaId == b.mineAreaId) && (a.elementNumber == b.elementNumber) && (a.endPosition == b.endPosition) && (a.startPosition == b.startPosition) && (a.endPosition == b.endPosition)); } } // namespace Database ObjectMineArea::ObjectMineArea(MainHandler *mainHandler, QObject *parent) : ObjectBaseTmpl(mainHandler, QStringLiteral("MineArea"), parent) , m_triggeredMinesChanged(false) { DEBUG_LOG(m_logger) << "ObjectMineArea::ObjectMineArea"; connect(this, &ObjectMineArea::effectStateChanged, this, &ObjectMineArea::onEffectStateChanged); } ObjectMineArea::~ObjectMineArea() { DEBUG_LOG(m_logger) << "ObjectMineArea::~ObjectMineArea"; removeTopics(); } Core::FunctionReplyObject ObjectMineArea::createObject(const Database::AesMineArea &data, const std::vector &segments) { DEBUG_LOG(m_logger) << "ObjectMineArea::createObject id=" << data.id << " externalId=" << data.externalId << " isCentral=" << data.isCentral << " chargeTypeId=" << data.chargeTypeId << " width=" << data.width << " segments.size()=" << segments.size(); return createObjectTmpl(data, segments); } Core::FunctionReplyObject ObjectMineArea::createObject(const Database::AesMineArea &data, const std::vector &segments) { DEBUG_LOG(m_logger) << "ObjectMineArea::createObject id=" << data.id << " segments.size()=" << segments.size(); return createObjectTmpl(data, segments); } Core::FunctionReplyObject ObjectMineArea::updateObject(const Database::AesMineArea &data, const std::vector &segments) { DEBUG_LOG(m_logger) << "ObjectMineArea::updateObject id=" << data.id << " segments.size=" << segments.size(); return updateObjectTmpl(data, segments); } Core::FunctionReplyObject ObjectMineArea::updateObject(const Database::AesMineArea &data, const std::vector &segments) { DEBUG_LOG(m_logger) << "ObjectMineArea::updateObject id=" << data.id << " segments.size=" << segments.size(); return updateObjectTmpl(data, segments); } bool ObjectMineArea::removeObjectFromDb() { return m_mainHandler->db()->removeAesMineArea(m_data.id); } bool ObjectMineArea::isDusMineArea() const { return static_cast(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::DUS; } bool ObjectMineArea::isConcreteMine() const { return static_cast(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::Concrete; } bool ObjectMineArea::isSingleVirtual() const { return static_cast(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::VirtualSingle; } bool ObjectMineArea::isVirtualMine() const { return static_cast(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::Virtual; } int ObjectMineArea::lastMineNo() const { int lastMineNoResult = 0; if (m_triggeredMines.isEmpty()) { DEBUG_LOG(m_logger) << "ObjectMineArea::lastMineNo=" << 0; return 0; } lastMineNoResult = m_triggeredMines.lastKey(); DEBUG_LOG(m_logger) << "ObjectMineArea::lastMineNo=" << lastMineNoResult; return lastMineNoResult; } int ObjectMineArea::getMineIdByIndex(int index) const { if (m_triggeredMines.size() < index) return -1; DEBUG_LOG(m_logger) << "getMineIdByIndex index: " << index; int count = 0; for (const Database::AesMineAreaTriggeredMines &tm : qAsConst(m_triggeredMines)) { DEBUG_LOG(m_logger) << "tm: mineId: " << tm.mineId; if (count == index) return tm.mineId; count++; } return -1; } bool ObjectMineArea::isReactivatableDusMineArea() const { bool isReactivatableDusMineAreaResult = m_mainHandler->dds()->pdmDtc()->isReactivatable(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId); DEBUG_LOG(m_logger) << "isReactivatableDusMineArea=" << isReactivatableDusMineAreaResult; return isReactivatableDusMineAreaResult; } void ObjectMineArea::clearCorridor(const AesItemView &aiv) { if (effectState() != AesEffectState::EsActivated) return; QPolygonF aivUtmPoly; aivUtmPoly.reserve(aiv.form().size()); int zone = LtsCoordTrans::UtmCoordinate::fromGeographic(aiv.form()[0].lon(), aiv.form()[0].lat()).zone; for (const auto &pos : aiv.form()) // Last Pos just for Length and Direction... { LtsCoordTrans::UtmCoordinate utmPos = LtsCoordTrans::UtmCoordinate::fromGeographic(pos.lon(), pos.lat(), zone); aivUtmPoly.push_back(QPointF(utmPos.x, utmPos.y)); } QVector minesToTrigger; minesToTrigger.reserve(m_triggeredMines.size()); for (const Database::AesMineAreaTriggeredMines &tm : qAsConst(m_triggeredMines)) { LtsCoordTrans::UtmCoordinate utmMinePos = LtsCoordTrans::UtmCoordinate::fromGeographic(tm.position, zone); if (aivUtmPoly.containsPoint(QPointF(utmMinePos.x, utmMinePos.y), Qt::OddEvenFill)) minesToTrigger << tm.mineId; } triggerMines(minesToTrigger, true, QPointF()); } void ObjectMineArea::clearBuildings() { int zoneOut = -1; // Check if buildings intersect with mine area bool isGroundLevel = true; const QVector &mseIds = m_mainHandler->dds()->ismDtc()->getBuildingByPolygon(m_mainHandler->dds()->exerciseId(), m_mineAreaPolygon, false, isGroundLevel); DEBUG_LOG(m_logger) << "mseIds size: " << mseIds.size(); QVector minesToTrigger; for (int mseId : qAsConst(mseIds)) { DEBUG_LOG(m_logger) << "mseId: " << mseId; const QPolygonF &footprint = m_mainHandler->dds()->ismDtc()->getUtmFootprintById(mseId, zoneOut, isGroundLevel); minesToTrigger.clear(); minesToTrigger.reserve(m_triggeredMines.size()); for (const Database::AesMineAreaTriggeredMines &tm : qAsConst(m_triggeredMines)) { const LtsCoordTrans::UtmCoordinate &utmMinePos = LtsCoordTrans::UtmCoordinate::fromGeographic(tm.position, zoneOut); if (footprint.containsPoint(QPointF(utmMinePos.x, utmMinePos.y), Qt::OddEvenFill)) minesToTrigger << tm.mineId; } DEBUG_LOG(m_logger) << "minesToTrigger: " << minesToTrigger.size(); removeTriggeredMines(minesToTrigger); } } bool ObjectMineArea::removeTriggeredMines(const QVector &mineIds) { DBTransaction trans(m_mainHandler->db()->database()); for (int mineId : mineIds) { Database::AesMineAreaTriggeredMines &tm = m_triggeredMines[mineId]; DEBUG_LOG(m_logger) << "remove triggered Mine " << mineId; bool bOk = m_mainHandler->db()->removeAesMineAreaTriggeredMines(tm.id); if (!bOk) { ERROR_LOG(m_logger) << "Error removing mine " << tm.mineId; return false; } } // remove from m_triggeredMines after suscessful removed from db for (int mineId : mineIds) m_triggeredMines.remove(mineId); if (!isDusMineArea()) { // This not nice. QList values = m_triggeredMines.values(); m_triggeredMines.clear(); bool bOk = true; for (int i = 0; i < values.size(); ++i) { values[i].mineId = i; m_triggeredMines[i] = values[i]; bOk &= m_mainHandler->db()->update(values[i]); } if (!bOk) { ERROR_LOG(m_logger) << "Error updating at least one mine "; return false; } } trans.commit(); m_triggeredMinesChanged = true; onPublishData(); return true; } QVector ObjectMineArea::unpairedMines() const { QVector result; for (auto it = m_triggeredMines.constBegin(); it != m_triggeredMines.constEnd(); ++it) { if (it.value().mineId < 70000 || it.value().isTriggered) continue; result.append(it.value()); } return result; } Core::FunctionReplyObject ObjectMineArea::createSegments(const std::vector &inSegments, int mineAreaId, QVector &outSegments, double &totalLengthOut) { DEBUG_LOG(m_logger) << "createDbSegments inSegments.size=" << inSegments.size(); outSegments.clear(); if (inSegments.size() < 2) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "There have to be at least two points to create a Mine Area Segment.")); int count = 0; outSegments.reserve(inSegments.size()); LtsCoordTrans::UtmCoordinate start = LtsCoordTrans::UtmCoordinate::fromGeographic(inSegments[0].lon(), inSegments[0].lat()); int zone = start.zone; totalLengthOut = 0.0; for (size_t i = 0; i < inSegments.size() - 1; ++i) // Last Pos just for Length and Direction... { Database::AesMineAreaSegment newSegment; newSegment.elementNumber = ++count; newSegment.mineAreaId = mineAreaId; newSegment.id = 0; newSegment.startPosition = QPointF(inSegments[i].lon(), inSegments[i].lat()); newSegment.endPosition = QPointF(inSegments[i + 1].lon(), inSegments[i + 1].lat()); // calculate length const LtsCoordTrans::UtmCoordinate end = LtsCoordTrans::UtmCoordinate::fromGeographic(inSegments[i + 1].lon(), inSegments[i + 1].lat(), zone); // QLine's y-axis is southbound! QLineF line(QPointF(start.x, start.y), QPointF(end.x, end.y)); // if (line.length() * density < 0.95) // We top that up to one mine later on. // { // outSegments.clear(); // return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Distance between Point %1 and Point %2 is too short to fit at least one mine.")).arg(i + 1).arg(i + // 2); // } start = end; // Misuse mineCount as SegmentLength in [m] ? // newSegment.mineCount = qRound(line.length()); totalLengthOut += line.length(); outSegments.push_back(std::move(newSegment)); } return Core::FunctionReplyObject(m_data.id); } Core::FunctionReplyObject ObjectMineArea::checkDensity(double &densityOut, const double totalLength, int maxMineCount) { DEBUG_LOG(m_logger) << "density: " << densityOut << " totalLength: " << totalLength; if (densityOut * totalLength > maxMineCount) // = MineCount... densityOut = maxMineCount / totalLength; DEBUG_LOG(m_logger) << "new density: " << densityOut; if (densityOut < 0.01) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Calculated density is too low for the given length")); if (densityOut > 0.9) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Calculated density is too high for the given length")); return Core::FunctionReplyObject(m_data.id); } Core::FunctionReplyObject ObjectMineArea::createSegments( const std::vector &inSegments, int mineAreaId, QVector &outSegments, const double &totalLengthOut // const double &totalLengthOut since totalLengthOut is unused and exits just for signature purpose ) { Q_UNUSED(totalLengthOut) DEBUG_LOG(m_logger) << "createSegments inSegments.size=" << inSegments.size(); outSegments.clear(); const size_t newSegmentsSize = inSegments.size(); outSegments.reserve(newSegmentsSize); for (size_t i = 0; i < newSegmentsSize; ++i) { Database::AesMineAreaSegment dbSegment; const AreaEffectSimulation::MineSegment &segment = inSegments.at(i); dbSegment.elementNumber = i; dbSegment.startPosition = QPointF(segment.positionStart().lon(), segment.positionStart().lat()); dbSegment.endPosition = QPointF(segment.positionEnd().lon(), segment.positionEnd().lat()); dbSegment.mineAreaId = mineAreaId; outSegments.push_back(std::move(dbSegment)); } return Core::FunctionReplyObject(m_data.id); } bool ObjectMineArea::publishTriggeredImpacts() { DEBUG_LOG(m_logger) << "publishTriggeredImpacts"; // Build an own tes-bitfield and compare, only store and publish if changed. TriggeredImpacts tis(m_tis); tis.impactCount(m_triggeredMines.size()); tis.bitfield().resize((m_triggeredMines.size() + 7) / 8); int i = 0; for (const auto &mine : qAsConst(m_triggeredMines)) { // Triggered Impact == 0, if mine is disarmed/exploded LtsUtils::BitArray::setBit(tis.bitfield(), i++, !mine.isTriggered); } if ((m_tis != tis) || m_triggeredMinesChanged) { m_tis = tis; m_tis.sampleTime(partitionTimeMSec()); return m_mainHandler->dds()->dts()->publish(m_tis); } return true; } bool ObjectMineArea::publishDusMineStatus() { DEBUG_LOG(m_logger) << "publishDusMineStatus"; // DusMineStatus mines vector is coherent to triggered impacts bitfield DusMineState dms(m_dms); dms.mines().resize(m_triggeredMines.size()); int i = 0; for (const auto &mine : qAsConst(m_triggeredMines)) { const DusMine entry(mine.mineId, mine.isTriggered, mine.chargeTypeId, {mine.position.x(), mine.position.y()}); dms.mines()[i++] = entry; } if (m_dms != dms || m_triggeredMinesChanged) { m_dms = dms; m_dms.sampleTime(partitionTimeMSec()); return m_mainHandler->dds()->dts()->publish(m_dms); } return true; } Core::FunctionReplyObject ObjectMineArea::changeSegmentsToLine(bool sortMines) { if (m_triggeredMines.size() < 1) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Could not change mine area segments with no mines.")); QVector vec; vec.reserve(m_triggeredMines.size()); QVector polyUtm; polyUtm.reserve(m_mineAreaPolygon.size()); for (const QPointF &point : qAsConst(m_mineAreaPolygon)) { LtsCoordTrans::UtmCoordinate posUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(point.x(), point.y()); DEBUG_LOG(m_logger) << "mineArea Polygon: " << posUtm.mgrs(); polyUtm.append(posUtm); } QVector minesUtm; minesUtm.reserve(m_triggeredMines.size()); for (const Database::AesMineAreaTriggeredMines &tm : qAsConst(m_triggeredMines)) { LtsCoordTrans::UtmCoordinate posUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(tm.position.x(), tm.position.y()); DEBUG_LOG(m_logger) << "triggeredMines: " << posUtm.mgrs(); minesUtm.append(posUtm); } for (const Database::AesMineAreaTriggeredMines &tm : qAsConst(m_triggeredMines)) { Core::GeoPosition2D geoPos = Core::GeoPosition2D(tm.position.x(), tm.position.y()); vec.append(geoPos); } DEBUG_LOG(m_logger) << "vec.size: " << vec.size(); std::vector vecSorted; if (!sortMines) { // In case of Concrete Mines and a DUS Line of only 2 Mines there is no need for any change if (vec.size() < 3 || isConcreteMine()) vecSorted = std::vector(vec.begin(), vec.end()); else { // In case of MineLayerPoisition (Not Sorting) with DUS Mines and more than 2 Mines (1 Segment) we // ignore the mines and just use the segments positions (Start is the startingPos of the the first segment // then add all end Positions of all segments for (const auto &segment : qAsConst(m_segments)) { if (vecSorted.empty()) vecSorted.push_back(Core::GeoPosition2D(segment.startPosition.x(), segment.startPosition.y())); vecSorted.push_back(Core::GeoPosition2D(segment.endPosition.x(), segment.endPosition.y())); } } } else { // get startPos of all mines (nearest to the smaller side of the polygon) int index = msGetIndexMine(polyUtm, minesUtm); DEBUG_LOG(m_logger) << "index: " << index; Core::GeoPosition2D startPos = vec[index]; LtsCoordTrans::UtmCoordinate startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(startPos.lon(), startPos.lat()); DEBUG_LOG(m_logger) << "startPosUtm: " << startPositionUtm.mgrs(); double length = 0.0; vecSorted.push_back(startPos); // Mine positions are the new segments while (vec.size() > 0) { Core::GeoPosition2D &pos = vec.first(); DEBUG_LOG(m_logger) << "pos: " << pos.lon() << ", " << pos.lat(); if (pos == startPos) { DEBUG_LOG(m_logger) << "Remove same Position: " << pos; vec.removeFirst(); continue; } length = LtsCoordTrans::squareDistance(startPositionUtm, pos.lon(), pos.lat()); DEBUG_LOG(m_logger) << "Distance between startPos and pos: " << length; Core::GeoPosition2D newPos = pos; for (const Core::GeoPosition2D &pos2 : qAsConst(vec)) { if (pos2 == pos || pos2 == startPos) continue; DEBUG_LOG(m_logger) << "pos2: " << pos2.lon() << ", " << pos2.lat(); double length2 = LtsCoordTrans::squareDistance(startPositionUtm, pos2.lon(), pos2.lat()); DEBUG_LOG(m_logger) << "Distance between startPos and pos2: " << length; if (length2 < length) { newPos = pos2; length = length2; DEBUG_LOG(m_logger) << "NewPos: length: " << length; } } vecSorted.push_back(newPos); vec.removeOne(newPos); startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(newPos.lon(), newPos.lat(), startPositionUtm.zone); DEBUG_LOG(m_logger) << "New startPosUtm: " << startPositionUtm.mgrs(); } if (vecSorted.size() == 1) { DEBUG_LOG(m_logger) << "only one segment - create 2 new with distance 10m"; const LtsCoordTrans::UtmCoordinate pointUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(vecSorted[0].lon(), vecSorted[0].lat()); LtsCoordTrans::UtmCoordinate point1Utm = pointUtm; LtsCoordTrans::UtmCoordinate point2Utm = pointUtm; point1Utm.y = pointUtm.y - 5.01; point2Utm.y = pointUtm.y + 5.01; vecSorted.clear(); vecSorted.reserve(2); QPointF point = point1Utm.geographic(); Core::GeoPosition2D pos(point.x(), point.y()); vecSorted.push_back(pos); point = point2Utm.geographic(); pos.lon(point.x()); pos.lat(point.y()); vecSorted.push_back(pos); } } // for (const auto &secPos : vecSorted) // { // NOTICE_LOG(m_logger) << secPos; // } QVector dbSegments; double totalLength = 0.0; Core::FunctionReplyObject reply = createSegments(vecSorted, m_data.privMineAreaId, dbSegments, totalLength); if (reply.error()) return reply; DBTransaction trans(m_mainHandler->db()->database()); m_mainHandler->db()->removeAesMineAreaSegmentByMineAreaId(m_data.privMineAreaId); removeTopics(); for (Database::AesMineAreaSegment &segment : dbSegments) { if (!m_mainHandler->db()->insert(segment)) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error updating AesMineAreaSegment-data in database.")); } // Mine area is now a line with width 0.0 m_data.width = 0.0; int equipNo = m_data.name.toInt(); if (equipNo > 0) m_data.name = m_data.name + QStringLiteral("_") + QString::number(m_data.id) + QStringLiteral("Minensperre"); if (!m_mainHandler->db()->update(m_data)) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error updating AesMineArea-data in database.")); trans.commit(); // read all Segments from database m_segments.clear(); if (!m_mainHandler->db()->selectByMineAreaId(m_segments, m_data.privMineAreaId)) // db { ERROR_LOG(m_logger) << "Error reading data from database!"; return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error reading triggered mines from database.")); ; } std::sort(m_segments.begin(), m_segments.end(), [](const Database::AesMineAreaSegment &a, const Database::AesMineAreaSegment &b) { return a.elementNumber < b.elementNumber; }); m_mineAreaPolygon = Polygons::mineArea(m_segments, m_data.width, m_data.density, m_data.isCenterLine); m_triggeredMinesChanged = true; onPublishData(); return Core::FunctionReplyObject(m_data.id); } template Core::FunctionReplyObject ObjectMineArea::createObjectTmpl(const Database::AesMineArea &data, const std::vector &segments) { DEBUG_LOG(m_logger) << "createObjectTmpl" << " width=" << data.width << " chargeTypeId=" << data.chargeTypeId << " effectDuration=" << data.effectDuration << " segments.size=" << segments.size(); auto getRightId = [](int id, bool originOc) -> int { return (originOc) ? (id * -1) : (id); }; bool originOc = false; DBTransaction trans(m_mainHandler->db()->database()); m_data = data; // recalc effect state except it's already expired, removed or it's paused. if (data.effectStateId != AesEffectState::EsRemoved && data.effectStateId != AesEffectState::EsExpired && data.effectStateId != AesEffectState::EsPaused) m_data.effectStateId = AesEffectState::EsUnknown; // On Objects, that are already in DB, segments is empty! double totalLength = 0.0; Core::FunctionReplyObject reply = createSegments(segments, -1, m_segments, totalLength); if (m_data.id <= 0 && reply.error()) return reply; // New Data! if (m_data.id <= 0) { if (m_segments.isEmpty()) return Core::FunctionReplyObject(getRightId(m_data.id, originOc), tr_lts("dds", "Could not create mine area without segments.")); std::vector form; ddsVecFromPoly(generateForm(), form); m_data.isCentral = checkIsCentral(form, {}, false); if (m_data.isCentral) reply = checkDensity(m_data.density, totalLength, m_mainHandler->mineAreaHandler()->maxCentralSingleMinesCount()); else reply = checkDensity(m_data.density, totalLength, m_mainHandler->mineAreaHandler()->maxSingleMinesCount()); if (reply.error()) return reply; if (m_data.id < 0) // is the id given as negative? make positive again -> used by OC-Laptop { m_data.id *= -1; m_data.priv_entityId = m_data.id; // OC is using id from Tetra and these are unique originOc = true; } if (!m_mainHandler->db()->insert(m_data)) return Core::FunctionReplyObject(getRightId(m_data.id, originOc), tr_lts("dds", "Error inserting AesMineArea-data into database.")); for (Database::AesMineAreaSegment &segment : m_segments) { // update ids segment.id = 0; segment.mineAreaId = m_data.privMineAreaId; // db DEBUG_LOG(m_logger) << "m_mainHandler->db()->insert(segment) segment.id=" << segment.id << " mineAreaId=" << segment.mineAreaId; if (!m_mainHandler->db()->insert(segment)) return Core::FunctionReplyObject(getRightId(m_data.id, originOc), tr_lts("dds", "Error inserting AesMineAreaSegment-data into database.")); } } else { DEBUG_LOG(m_logger) << "else"; // No update. Will only be called after gathering that data from db // read all Segments from database m_segments.clear(); if (!m_mainHandler->db()->selectByMineAreaId(m_segments, m_data.privMineAreaId)) // db { ERROR_LOG(m_logger) << "Error reading data from database!"; return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error reading triggered mines from database.")); ; } std::sort(m_segments.begin(), m_segments.end(), [](const Database::AesMineAreaSegment &a, const Database::AesMineAreaSegment &b) { return a.elementNumber < b.elementNumber; }); } DEBUG_LOG(m_logger) << "Vor m_segments: " << m_segments.size() << " width: " << m_data.width << " dataId: " << m_data.id << " privateAreaId: " << m_data.privMineAreaId; m_mineAreaPolygon = Polygons::mineArea(m_segments, m_data.width, m_data.density, m_data.isCenterLine); if (isVirtualMine()) { DEBUG_LOG(m_logger) << "Vor generateVirtualMines"; generateVirtualMines(); clearBuildings(); } // read all Triggered Mines from database // Store all those of this mine area in m_triggeredMines auto cb = [this](const Database::AesMineAreaTriggeredMines &mine) { m_triggeredMines.insert(mine.mineId, mine); }; if (!m_mainHandler->db()->selectByMineAreaId(cb, m_data.id)) { ERROR_LOG(m_logger) << "Error reading data from database!"; return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error reading triggered mines from database.")); ; } // Set TriggeredExplosions Base Data m_tis.id(1000 * m_data.id + AesType::AtMineAreaSingleMines); m_tis.aesItemId(m_data.id); m_tis.aesTypeId(AesType::AtMineAreaSingleMines); // Set DUS-Mine status Base Data m_dms.id(m_data.id); trans.commit(); // to get initial dds objects for empty m_triggeredMines m_triggeredMinesChanged = true; // start state machine (will call onPublish() to publish MineArea, AIV and m_tes) m_publishDataTimer->start(); checkEnterNewState(); DEBUG_LOG(m_logger) << "Vor return Core::FunctionReplyObject(getRightId(" << m_data.id << ", " << originOc << ")"; return Core::FunctionReplyObject(getRightId(m_data.id, originOc)); } template Core::FunctionReplyObject ObjectMineArea::updateObjectTmpl(const Database::AesMineArea &data, const std::vector &segments) { DEBUG_LOG(m_logger) << "updateObjectTmpl " << " data.id=" << data.id << " externalId=" << data.externalId << " chargeTypeId=" << data.chargeTypeId << " segments.size=" << segments.size(); Core::FunctionReplyObject ret = ObjectBaseTmpl::updateObject(data); if (ret.error() || (isOnlyObjectNameModifiable() && (data.id > 0))) // data.id > 0 -> negative values from oc always get a full update return ret; // only allow some fields // Todo: Complete??? Database::AesMineArea tmpData(m_data); tmpData.id = std::abs(data.id); tmpData.name = data.name; tmpData.effectStart = data.effectStart; tmpData.effectDuration = data.effectDuration; tmpData.participantId = data.participantId; tmpData.partyId = data.partyId; tmpData.effectMarkingInTa = data.effectMarkingInTa; tmpData.density = data.density; tmpData.layingProcedureTypeId = data.layingProcedureTypeId; tmpData.isLaidHidden = data.isLaidHidden; tmpData.width = data.width; tmpData.isCenterLine = data.isCenterLine; tmpData.effectStateId = data.effectStateId; // overwrite for oc tmpData.properties = data.properties; // before updating chargeTypeId: if chargeTypeId changed, check if already was DUS-area and release mines bool wasDusArea = isDusMineArea(); bool wasConcreteArea = isConcreteMine(); if ((tmpData.chargeTypeId != data.chargeTypeId)) { if (wasDusArea || wasConcreteArea) { QList minesToBeReleased = m_triggeredMines.values(); m_triggeredMines.clear(); m_triggeredMinesChanged = true; emit releaseMines(m_data.id, minesToBeReleased); DEBUG_LOG(m_logger) << "Cleared m_triggeredMines for minearea-ID " << m_data.id; } } tmpData.chargeTypeId = data.chargeTypeId; QVector dbSegments; double totalLength = 0.0; Core::FunctionReplyObject reply = createSegments(segments, m_data.privMineAreaId, dbSegments, totalLength); if (reply.error()) return reply; if (m_data.isCentral) reply = checkDensity(tmpData.density, totalLength, 1500); else reply = checkDensity(tmpData.density, totalLength, 1200); if (reply.error()) return reply; DBTransaction trans(m_mainHandler->db()->database()); DEBUG_LOG(m_logger) << "removeAesMineAreaSegmentByMineAreaId m_data.privMineAreaId=" << m_data.privMineAreaId; m_mainHandler->db()->removeAesMineAreaSegmentByMineAreaId(m_data.privMineAreaId); for (Database::AesMineAreaSegment &segment : dbSegments) { if (!m_mainHandler->db()->insert(segment)) return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating AesMineAreaSegment-data in database.")); } // if Segments changed for DUS-area, release mines that are not in the new polygon // canHandleMine() needs the new m_mineAreaPolygon m_mineAreaPolygon = Polygons::mineArea(dbSegments, tmpData.width, tmpData.density, tmpData.isCenterLine); bool segmentsChanged = false; if (m_segments != dbSegments && (isDusMineArea() || isConcreteMine())) { DEBUG_LOG(m_logger) << "Shape changed: release mines that are not inside the minearea anymore."; QMutableMapIterator it(m_triggeredMines); QList releasedMines; while (it.hasNext()) { auto mine = it.next().value(); if (!canHandleMine(mine.mineId, mine.chargeTypeId, mine.position)) { DEBUG_LOG(m_logger) << "Releasing mine " << mine.mineId << " from mine Area " << m_data.id << " as it is not inside the minearea anymore."; releasedMines << mine; it.remove(); } } if (!releasedMines.isEmpty()) { m_triggeredMinesChanged = true; emit releaseMines(m_data.id, releasedMines); } segmentsChanged = true; } m_segments = dbSegments; if (!m_mainHandler->db()->update(tmpData)) return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating mine area with id %1 in database.")).arg(m_data.id); m_data = std::move(tmpData); trans.commit(); // needs to be emitted after "m_data = std::move(tmpData);" or we will get assigned our released DUS-mines if (segmentsChanged) emit shapeChanged(m_data.id); // needs to be emitted after "m_data = std::move(tmpData);" so isDusArea() operates on new data if ((isDusMineArea() && !wasDusArea) || (isConcreteMine() && !wasConcreteArea)) { m_triggeredMines.clear(); // was virtual, is now DUS -> delete triggered m_triggeredMinesChanged = true; } else if (isVirtualMine()) { generateVirtualMines(); clearBuildings(); if (wasDusArea || wasConcreteArea) removeDusMineStateTopic(); } m_publishDataTimer->start(); checkEnterNewState(); return Core::FunctionReplyObject(data.id); } /*! * \brief ObjectMineArea::createObjectFromMineData * \param data * \param segment * \return */ Core::FunctionReplyObject ObjectMineArea::createObjectFromMineData(const Database::AesMineArea &data, const std::vector &segment, const std::vector &mineData) { DEBUG_LOG(m_logger) << "--[createObjectFromMineData] " << " width=" << data.width << " chargeTypeId=" << data.chargeTypeId << " effectDuration=" << data.effectDuration << " segments.size=" << segment.size(); auto getRightId = [](int id, bool originOc) -> int { return (originOc) ? (id * -1) : (id); }; bool originOc = false; DBTransaction trans(m_mainHandler->db()->database()); m_data = data; m_data.properties = data.properties; // recalc effect state except it's already expired, removed or it's paused. if (data.effectStateId != AesEffectState::EsRemoved && data.effectStateId != AesEffectState::EsExpired && data.effectStateId != AesEffectState::EsPaused) m_data.effectStateId = AesEffectState::EsUnknown; // On Objects, that are already in DB, segments is empty! double totalLength = 0.0; Core::FunctionReplyObject reply = createSegments(segment, -1, m_segments, totalLength); if (reply.error()) return reply; if (m_segments.isEmpty()) return Core::FunctionReplyObject(getRightId(m_data.id, originOc), tr_lts("dds", "Could not create mine area without segments.")); m_data.isCentral = true; // checkIsCentral(form, {}, false); if (m_data.id < 0) // is the id given as negative? make positive again -> used by OC-Laptop { m_data.id *= -1; m_data.priv_entityId = m_data.id; // OC is using id from Tetra and these are unique originOc = true; } if (!m_mainHandler->db()->insert(m_data)) return Core::FunctionReplyObject(getRightId(m_data.id, originOc), tr_lts("dds", "Error inserting AesMineArea-data into database.")); // db insert for (Database::AesMineAreaSegment &segment : m_segments) { segment.id = 0; segment.mineAreaId = m_data.privMineAreaId; // db DEBUG_LOG(m_logger) << "--[createObjectFromMineData] insert into DB " << " privMineAreaId=" << m_data.privMineAreaId << " pos start: " << segment.startPosition << " pos End: " << segment.endPosition; if (!m_mainHandler->db()->insert(segment)) return Core::FunctionReplyObject(getRightId(m_data.id, originOc), tr_lts("dds", "Error inserting AesMineAreaSegment-data into database.")); } clearBuildings(); // read all Triggered Mines from database if (!addMineData(mineData)) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error adding triggered mines to database.")); // Set TriggeredExplosions Base Data m_tis.id(1000 * m_data.id + AesType::AtMineAreaSingleMines); m_tis.aesItemId(m_data.id); m_tis.aesTypeId(AesType::AtMineAreaSingleMines); // Set DUS-Mine status Base Data m_dms.id(m_data.id); trans.commit(); // to get initial dds objects for empty m_triggeredMines m_triggeredMinesChanged = true; // start state machine (will call onPublish() to publish MineArea, AIV and m_tes) m_publishDataTimer->start(); checkEnterNewState(); DEBUG_LOG(m_logger) << "--[createObjectFromMineData] end new ------> m_triggeredMines count: " << m_triggeredMines.count(); return Core::FunctionReplyObject(getRightId(m_data.id, originOc)); } /*! * \brief ObjectMineArea::updateObjectFromReferenceAesItem * \param data * \param segments * \return */ Core::FunctionReplyObject ObjectMineArea::updateObjectFromMineData(const Database::AesMineArea &data, const std::vector &segments, const std::vector &mineData) { DEBUG_LOG(m_logger) << "[updateObjectFromMineData] >> \ndata.id= " << data.id << " \nprivMineAreaId: " << m_data.privMineAreaId << " \nchargeTypeId= " << data.chargeTypeId << " \nmineData.size= " << segments.size() << " partyId: " << data.partyId; Database::AesMineArea tmpData(data); Core::FunctionReplyObject ret; tmpData.id = std::abs(data.id); tmpData.name = data.name; tmpData.effectStart = data.effectStart; tmpData.effectDuration = data.effectDuration; tmpData.participantId = data.participantId; tmpData.partyId = data.partyId; tmpData.effectMarkingInTa = data.effectMarkingInTa; tmpData.density = data.density; tmpData.layingProcedureTypeId = data.layingProcedureTypeId; tmpData.isLaidHidden = data.isLaidHidden; tmpData.width = data.width; tmpData.isCenterLine = data.isCenterLine; tmpData.effectStateId = data.effectStateId; // overwrite for oc tmpData.properties = data.properties; tmpData.chargeTypeId = data.chargeTypeId; QVector dbSegments; double totalLength = 0.0; ret = createSegments(segments, m_data.privMineAreaId, m_segments, totalLength); if (ret.error()) return ret; DBTransaction trans(m_mainHandler->db()->database()); DEBUG_LOG(m_logger) << "--[updateObjectFromMineData] remove existing segments..."; m_mainHandler->db()->removeAesMineAreaSegmentByMineAreaId(m_data.privMineAreaId); for (Database::AesMineAreaSegment &segment : m_segments) { if (!m_mainHandler->db()->insert(segment)) return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating AesMineAreaSegment-data in database.")); } if (!m_mainHandler->db()->update(tmpData)) return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating mine area with id %1 in database.")).arg(m_data.id); m_mineAreaPolygon = Polygons::mineArea(m_segments, tmpData.width, tmpData.density, tmpData.isCenterLine); m_data = tmpData; trans.commit(); // add mine data to trigger mine if (!addMineData(mineData)) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error adding triggered mines to database.")); // if (selectMineFromDb(m_triggeredMines)) // return; clearBuildings(); m_triggeredMinesChanged = true; m_publishDataTimer->start(); checkEnterNewState(); DEBUG_LOG(m_logger) << "--[updateObjectFromMineData] end add ------> m_triggeredMines count: " << m_triggeredMines.count(); return Core::FunctionReplyObject(data.id); } double ObjectMineArea::squareLen(const QPointF &p1, const QPointF &p2) const { double vx = p2.x() - p1.x(); double vy = p2.y() - p1.y(); return vx * vx + vy * vy; } double ObjectMineArea::distancePointLine(const QPointF &pUtm, const QPointF &l1Utm, const QPointF &l2Utm) const { DEBUG_LOG(m_logger) << "p=" << pUtm.x() << "/" << pUtm.y() << " l1=" << l1Utm.x() << "/" << l1Utm.y() << " l2=" << l2Utm.x() << "/" << l2Utm.y(); const double u = (((pUtm.x() - l1Utm.x()) * (l2Utm.x() - l1Utm.x())) + ((pUtm.y() - l1Utm.y()) * (l2Utm.y() - l1Utm.y()))) / squareLen(l2Utm, l1Utm); if (u < 0.0f || u > 1.0f) { QLineF pToP1(pUtm, l1Utm); QLineF pToP2(pUtm, l2Utm); qreal length1 = pToP1.length(); qreal length2 = pToP2.length(); if (length1 < length2) { DEBUG_LOG(m_logger) << "a distancePointLine p=" << pUtm.x() << "/" << pUtm.y() << " line: p1=" << l1Utm.x() << "/" << l1Utm.y() << " p2=" << l2Utm.x() << "/" << l2Utm.y() << " distance=" << length1 << ", return=" << length1; return length1; } else { DEBUG_LOG(m_logger) << "b distancePointLine p=" << pUtm.x() << "/" << pUtm.y() << " line: p1=" << l1Utm.x() << "/" << l1Utm.y() << " p2=" << l2Utm.x() << "/" << l2Utm.y() << " distance=" << length2 << ", return=" << length2; return length2; } } QPointF intersectPnt(l1Utm.x() + u * (l2Utm.x() - l1Utm.x()), l1Utm.y() + u * (l2Utm.y() - l1Utm.y())); double distance = sqrt(squareLen(pUtm, intersectPnt)); DEBUG_LOG(m_logger) << "c distancePointLine p=" << pUtm.x() << "/" << pUtm.y() << " line: p1=" << l1Utm.x() << "/" << l1Utm.y() << " p2=" << l2Utm.x() << "/" << l2Utm.y() << " distance=" << distance; return distance; } bool ObjectMineArea::containsPointInExpandedMinefield(const QPointF &positionGeo) const { QPolygonF mineAreaPolygonUtm; mineAreaPolygonUtm.reserve(m_mineAreaPolygon.size()); const LtsCoordTrans::UtmCoordinate pointUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(positionGeo.x(), positionGeo.y()); DEBUG_LOG(m_logger) << "centerPoint=" << pointUtm.x << "/" << pointUtm.y << " zone=" << pointUtm.zone; for (const auto &pos : m_mineAreaPolygon) // Last Pos just for Length and Direction... { const LtsCoordTrans::UtmCoordinate &pointMineAreaUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(pos, pointUtm.zone); DEBUG_LOG(m_logger) << "pointMineAreaUtm=" << pointMineAreaUtm.x << "/" << pointMineAreaUtm.y << " zone=" << pointMineAreaUtm.zone; mineAreaPolygonUtm.push_back(QPointF(pointMineAreaUtm.x, pointMineAreaUtm.y)); } const double distance = 0.5; for (const QPointF &mineAreaPointUTM : qAsConst(mineAreaPolygonUtm)) { double x = pointUtm.x - mineAreaPointUTM.x(); double y = pointUtm.y - mineAreaPointUTM.y(); double distancePointPointValue = sqrt(x * x + y * y); DEBUG_LOG(m_logger) << "distancePointPointValue=" << distancePointPointValue << " Point =" << pointUtm.x << "/" << pointUtm.y << " PointPoly=" << mineAreaPointUTM.x() << "/" << mineAreaPointUTM.y(); if (distancePointPointValue < distance) { DEBUG_LOG(m_logger) << "ObjectMineArea::containsPoint return true"; return true; } } return false; } bool ObjectMineArea::canHandleMine(int mineId, int chargeTypeId, const QPointF &position) const { DEBUG_LOG(m_logger) << "Requesting Mine Area " << m_data.id << " to handle the DUS-Mine " << mineId << " . Request has " << (position.isNull() ? "no " : "valid ") << "position."; // DUS mine triggers, that are not yet associated to this mine area and are not within the polygon shall be ignored. if (!isDusMineArea() && !isConcreteMine()) DEBUG_LOG(m_logger) << "Mine Area " << m_data.id << " is not a DUS mine area."; // EffectState has to be Created, Prepared, Active and Paused Areas if (m_data.effectStateId.underlying() < AesEffectState::EsCreated) { DEBUG_LOG(m_logger) << "Mine Area " << m_data.id << " can not handle mine because its current AesEffectState is" << AreaEffectSimulation::toString(m_data.effectStateId); return false; } if (!position.isNull()) { // DEBUG_LOG(m_logger) << "Mine Area " << m_data.id << (m_mineAreaPolygon.containsPoint(position, Qt::OddEvenFill) ? " contains " : " does not contain ") << "mine // position."; DEBUG_LOG(m_logger) << "Mine Area " << m_data.id << (containsPointInExpandedMinefield(position) ? " contains " : " does not contain ") << "mine position."; if (containsPointInExpandedMinefield(position)) return true; } if (chargeTypeId > 0 && m_data.chargeTypeId != chargeTypeId) { DEBUG_LOG(m_logger) << "Mine Area" << m_data.id << " does not have the matching chargeTypeId " << chargeTypeId; return false; } DEBUG_LOG(m_logger) << "Mine is " << (m_triggeredMines.contains(mineId) ? "part of " : "not part of ") << "this mine area." << " mineId=" << mineId << "m_triggeredMines.size=" << m_triggeredMines.size(); if (!m_triggeredMines.contains(mineId) && (position.isNull() || !m_mineAreaPolygon.containsPoint(position, Qt::OddEvenFill))) { DEBUG_LOG(m_logger) << "Therefore, we skip this mine area."; return false; } DEBUG_LOG(m_logger) << "Mine Area " << m_data.id << " can handle this mine"; return true; } bool ObjectMineArea::triggerMine(int mineId, bool trigger, const QPointF &position) { DEBUG_LOG(m_logger) << "Triggering mine " << mineId << " trigger " << trigger; return triggerMines({mineId}, trigger, position); } bool ObjectMineArea::updateMine(Database::AesMineAreaTriggeredMines &mine) { DEBUG_LOG(m_logger) << "Updating mine " << mine.mineId << " with dbId " << mine.id << ", isTriggerd: " << mine.isTriggered; mine.mineAreaId = m_data.id; // mine.isTriggered = true; mine.triggerTime = QDateTime::fromMSecsSinceEpoch(partitionTimeMSec()); m_triggeredMines[mine.mineId] = mine; if (!m_mainHandler->db()->update(mine)) ERROR_LOG(m_logger) << "Error updating mine " << mine.mineId << " in db!"; onPublishData(); return true; } void ObjectMineArea::updateMineByOldMineId(Database::AesMineAreaTriggeredMines &mine, qint32 oldMineId) { DEBUG_LOG(m_logger) << "Updating mine by oldMineId " << oldMineId << " with new mineId " << mine.mineId << " and dbId " << mine.id << ", isTriggerd: " << mine.isTriggered; mine.mineAreaId = m_data.id; mine.triggerTime = QDateTime::fromMSecsSinceEpoch(partitionTimeMSec()); m_triggeredMines.remove(oldMineId); m_triggeredMines.insert(mine.mineId, mine); if (!m_mainHandler->db()->update(mine)) ERROR_LOG(m_logger) << "Error updating mine " << mine.mineId << " in db!"; onPublishData(); } int ObjectMineArea::mineCount() { double totalLength = 0.0; for (const Database::AesMineAreaSegment &segment : qAsConst(m_segments)) { LtsCoordTrans::UtmCoordinate startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(segment.startPosition); LtsCoordTrans::UtmCoordinate endPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(segment.endPosition, startPositionUtm.zone); const QLineF line({startPositionUtm.x, startPositionUtm.y}, {endPositionUtm.x, endPositionUtm.y}); totalLength += line.length(); } return qRound(totalLength * m_data.density); } bool ObjectMineArea::triggerMines(const QVector &mineIds, bool trigger, const QPointF &position) { DEBUG_LOG(m_logger) << "triggerMines mineIds.size()=" << mineIds.size() << " trigger=" << trigger; // Do not trigger different mines with the same positition... if (mineIds.size() > 1 && !position.isNull()) return false; DBTransaction trans(m_mainHandler->db()->database()); for (int mineId : mineIds) { Database::AesMineAreaTriggeredMines &tm = m_triggeredMines[mineId]; tm.mineAreaId = m_data.id; tm.mineId = mineId; tm.isTriggered = trigger; tm.chargeTypeId = m_data.chargeTypeId; if (trigger) tm.triggerTime = QDateTime::fromMSecsSinceEpoch(partitionTimeMSec()); if (!position.isNull()) tm.position = position; DEBUG_LOG(m_logger) << "tm.id: " << tm.id << ", mineId: " << tm.mineId << ", mineAreaId: " << tm.mineAreaId; bool bOk = true; if (!tm.id) { ERROR_LOG(m_logger) << "insert triggered Mine"; bOk = m_mainHandler->db()->insert(tm); } else { ERROR_LOG(m_logger) << "update triggered Mine"; bOk = m_mainHandler->db()->update(tm); } if (!bOk) { ERROR_LOG(m_logger) << "Error " << (tm.id ? "updating " : "inserting ") << "mine " << tm.mineId; return false; } } trans.commit(); onPublishData(); return true; } void ObjectMineArea::generateVirtualMines() { DEBUG_LOG(m_logger) << "generateVirtualMines"; // Ensure, all virtual mines are in map. QVector mineposVec = Polygons::minePositions(m_data, m_segments); Database::AesMineAreaTriggeredMines tm; tm.mineAreaId = m_data.id; const auto triggerdMinesCopy = m_triggeredMines; m_triggeredMines.clear(); // use mineposVec.size(), since mineCount counts all mines (incl. removed mines in buildings) for (int i = 0; i < mineposVec.size(); ++i) { tm.mineId = i; tm.isTriggered = triggerdMinesCopy.value(i).isTriggered; m_triggeredMines[i] = tm; m_triggeredMines[i].position = mineposVec[i]; // Set Position for all mines. i starts at 1 but mineposVec at 0 } DEBUG_LOG(m_logger) << "Virtual mines for minearea-ID " << m_data.id << " generated."; } bool ObjectMineArea::addMineData(const std::vector &mineData) { // get existing mine position from db before adding new one if (!selectMineFromDb(m_triggeredMines)) ERROR_LOG(m_logger) << "--[addMineData] ERROR selectMineFromDb RETURN FALSE "; QVector mineIds; int idx = m_triggeredMines.isEmpty() ? 0 : m_triggeredMines.lastKey() + 1; QMap newMines; // sort mineIds and get last entry which is the greatest id to start counting from it Database::AesMineAreaTriggeredMines tm; DBTransaction trans(m_mainHandler->db()->database()); // insert mine in der DB later fill m_triggerMine with db->selectByMineAreaId(..) for (const auto &mine : mineData) { tm.id = 0; tm.mineAreaId = m_data.id; tm.chargeTypeId = m_data.chargeTypeId; tm.mineId = idx++; tm.appearenceTime = QDateTime::fromMSecsSinceEpoch(mine.appearenceTime()); tm.armingTime = QDateTime::fromMSecsSinceEpoch(mine.armingTime()); tm.position = QPointF(mine.position().lon(), mine.position().lat()); if (!m_mainHandler->db()->insert(tm)) { ERROR_LOG(m_logger) << "--[addMineData] Couldn't insert trigger mine into db"; return false; } newMines.insert(tm.mineId, tm); } trans.commit(); m_triggeredMines.insert(newMines); return true; } bool ObjectMineArea::selectMineFromDb(QMap &outTriggerMines) { QMap tmpMines; // Store all those of this mine area in m_triggeredMines auto cb = [this, &tmpMines](const Database::AesMineAreaTriggeredMines &mine) { DEBUG_LOG(m_logger) << "--[selectMineFromDb] >>> m_triggeredMines.insert mineId=" << mine.mineId << " mineAreaId=" << mine.mineAreaId; tmpMines.insert(mine.mineId, mine); }; if (!m_mainHandler->db()->selectByMineAreaId(cb, m_data.id)) { ERROR_LOG(m_logger) << "Error reading data from database!"; return false; } outTriggerMines = tmpMines; return true; } void ObjectMineArea::removeDusMineStateTopic() { DEBUG_LOG(m_logger) << "removeDusMineStateTopic"; auto dts = m_mainHandler->dds()->dts(); dts->removeDusMineStateByKey(m_data.id); } bool ObjectMineArea::resetMine(int mineId) { DEBUG_LOG(m_logger) << "resetMine mineId=" << mineId; // single reset only allowed in dus mine areas if (!isDusMineArea() && !isConcreteMine()) return false; DEBUG_LOG(m_logger) << "Trying to reset mine with id " << mineId; if (!m_triggeredMines.contains(mineId)) return false; DEBUG_LOG(m_logger) << "m_data.id: " << m_data.id; return triggerMine(mineId, false); } bool ObjectMineArea::resetMines() { DEBUG_LOG(m_logger) << "Resetting all mines in area " << m_data.id; if (m_triggeredMines.isEmpty()) return true; bool bOk = true; auto it = m_triggeredMines.begin(); while (it != m_triggeredMines.end()) { Database::AesMineAreaTriggeredMines &tm = (*it); if (tm.isTriggered) { tm.isTriggered = false; bOk &= m_mainHandler->db()->update(tm); } ++it; } if (!bOk) return false; onPublishData(); return true; } Database::AesMineAreaTriggeredMines ObjectMineArea::takeMine(int mineId) { DEBUG_LOG(m_logger) << "takeMine mineId=" << mineId; if (!m_triggeredMines.contains(mineId)) return Database::AesMineAreaTriggeredMines(); Database::AesMineAreaTriggeredMines ret = m_triggeredMines.value(mineId); m_triggeredMines.remove(mineId); onPublishData(); return ret; } double ObjectMineArea::singleMineRadius() const { double singleMineRadiusResult = m_mainHandler->dds()->pdmDtc()->impactRadius(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId); DEBUG_LOG(m_logger) << "singleMineRadius=" << singleMineRadiusResult; return singleMineRadiusResult; } void ObjectMineArea::onPublishData() { DEBUG_LOG(m_logger) << "onPublishData()"; DDSConnector::CoherentSet cs(m_mainHandler->dds()->ddsConnector()); ObjectBaseTmpl::onPublishData(); if (!m_mainHandler->db()->update(m_data)) ERROR_LOG(m_logger) << "Error updating AesMineSingle-data in database"; std::vector segVec; segVec.reserve(m_segments.size()); for (const auto &segment : qAsConst(m_segments)) segVec.emplace_back( MineSegment(Core::GeoPosition2D(segment.startPosition.x(), segment.startPosition.y()), Core::GeoPosition2D(segment.endPosition.x(), segment.endPosition.y()))); // Todo: Enhancement: // store this as member and compare (without partitionTime!) before sending! // calc width/height from m_mineAreaPolygon (geopos!) const QRectF boundingRect = m_mineAreaPolygon.boundingRect(); const LtsCoordTrans::UtmCoordinate tl(LtsCoordTrans::UtmCoordinate::fromGeographic(boundingRect.topLeft())); const LtsCoordTrans::UtmCoordinate br(LtsCoordTrans::UtmCoordinate::fromGeographic(boundingRect.bottomRight(), tl.zone)); MineArea ddsData(m_data.id, m_data.name.toStdString(), effectState() == AesEffectState::EsRemoved, false, partitionTimeMSec(), {m_data.chargeTypeId, 0, 0}, m_data.effectDuration, toOptional(toMSec(m_data.effectStart)), m_data.effectStateId, m_data.effectSourceId, toOptional(m_data.partyId), toOptional(m_data.participantId), m_data.effectMarkingInTa, m_data.externalId, m_data.isCentral, m_data.properties.toStdString(), m_data.density, 0.5, m_data.width, Core::GeoPosition2D(boundingRect.topLeft().x(), // Is bottomLeft as coordinate sytems boundingRect.topLeft().y()), // as QRect has its origin at top left qAbs(tl.x - br.x), qAbs(tl.y - br.y), segVec, singleMineRadius(), m_data.isLaidHidden, m_data.layingProcedureTypeId, isDusMineArea(), isReactivatableDusMineArea(), m_data.isCenterLine, isConcreteMine(), true); m_mainHandler->dds()->dts()->publish(ddsData); publishTriggeredImpacts(); if (isDusMineArea() || isConcreteMine()) publishDusMineStatus(); publishAiv(); m_triggeredMinesChanged = false; } void ObjectMineArea::publishAiv() { DEBUG_LOG(m_logger) << "publishAiv()"; // Todo: calculate unique id. // Better than aiv.id(1000 * aesItemId + AesType::AtMineArea); AesItemView aiv(m_aiv); aiv.aesItemId(m_data.id); aiv.chargeStruct().chargeTypeId(m_data.chargeTypeId); aiv.effectStart(toMSec(m_data.effectStart)); ddsVecFromPoly(m_mineAreaPolygon, aiv.form()); const auto br = boundingRect(); // m_mineAreaPolygon.boundingRect() aiv.boundingBoxBottomLeft(Core::GeoPosition2D(br.bottomLeft().x(), br.bottomLeft().y())); aiv.boundingBoxTopRight(Core::GeoPosition2D(br.topRight().x(), br.topRight().y())); aiv.aesTypeId(AesType::AtMineArea); aiv.effectStateId(effectState()); aiv.id(1000 * m_data.id + AesType::AtMineArea); if (m_data.partyId != 0) aiv.partyId(m_data.partyId); checkIsCentralAndPublish(aiv.form(), m_aiv.form(), m_data.isCentral); if (aiv != m_aiv) { DEBUG_LOG(m_logger) << "aiv changed - publish"; aiv.sampleTime(partitionTimeMSec()); m_aiv = aiv; m_mainHandler->dds()->dts()->publish(aiv); emit newAiv(aiv); } AesItemView aiv2(m_aiv2); aiv2.aesItemId(m_data.id); aiv2.chargeStruct().chargeTypeId(m_data.chargeTypeId); aiv2.effectStart(toMSec(m_data.effectStart)); aiv2.form().clear(); aiv2.form().reserve(m_triggeredMines.size()); for (const auto &tm : qAsConst(m_triggeredMines)) aiv2.form().resize(aiv2.form().size() + 1, Core::GeoPosition(tm.position.x(), tm.position.y(), 0.0)); aiv2.boundingBoxBottomLeft(aiv.boundingBoxBottomLeft()); aiv2.boundingBoxTopRight(aiv.boundingBoxTopRight()); aiv2.aesTypeId(AesType::inner_enum::AtMineAreaSingleMines); aiv2.id(1000 * m_data.id + AesType::inner_enum::AtMineAreaSingleMines); aiv2.effectStateId(m_data.effectStateId); if (m_data.partyId != 0) aiv2.partyId(m_data.partyId); if (aiv2 != m_aiv2 || m_triggeredMinesChanged) { DEBUG_LOG(m_logger) << "aiv2 changed"; aiv2.sampleTime(partitionTimeMSec()); m_aiv2 = aiv2; m_mainHandler->dds()->dts()->publish(aiv2); emit newAiv(aiv2); } } void ObjectMineArea::removeTopics() { DEBUG_LOG(m_logger) << "removeTopics()"; auto dts = m_mainHandler->dds()->dts(); dts->removeMineAreaByKey(m_data.id); dts->removeAesItemViewByKey(1000 * m_data.id + AesType::inner_enum::AtMineArea); dts->removeAesItemViewByKey(1000 * m_data.id + AesType::inner_enum::AtMineAreaSingleMines); dts->removeTriggeredImpactsByKey(1000 * m_data.id + AesType::inner_enum::AtMineAreaSingleMines); removeDusMineStateTopic(); emit removeAiv(m_data.id); } /*! * \brief ObjectMineArea::createSegmentsFromMineData * \param data: Data width will be actualized after the new rect is determined * \param mineData: are the mines pos data tha need to be added to the existing mine area * \param outSegments: size is 2 corresponding to the lenght of the rect. width is derived from Area(MneData + triggerMines) * \return */ Core::FunctionReplyObject ObjectMineArea::createGeoSegmentsFromMineData(double &newWidth, const std::vector &mineData, std::vector &outSegments) { Core::FunctionReplyObject ret; // reconstruct GeoPos segment from mineData + triggeredMine // DusMineState is derived from triggeredMine // 1. create a mine vector from mineData + m_triggeredMines // 2. get rectangle from mine vector // 3. build segment from it std::vector tempMinePos; tempMinePos.reserve(m_triggeredMines.size() + mineData.size()); for (const auto &t : qAsConst(m_triggeredMines)) tempMinePos.emplace_back(Core::GeoPosition2D(t.position.x(), t.position.y())); for (const auto &md : mineData) tempMinePos.emplace_back(md.position()); DEBUG_LOG(m_logger) << "--[createSegmentsFromMineData]: --> tempMinePos.Size: " << tempMinePos.size() << " m_triggeredMinesSIze: " << m_triggeredMines.size() << " mineDataSize " << mineData.size(); if (tempMinePos.empty()) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Mine data couldn't be added")); LtsCoordTrans::UtmCoordinate tempCoord = LtsCoordTrans::UtmCoordinate::fromGeographic(tempMinePos.front().lon(), tempMinePos.front().lat()); // calculate position double angleDeg = 0.0; const QVector points = Polygons::geoVecToUtmPointF(tempMinePos); QRectF areaRect = Polygons::getMinAreaRect(points, angleDeg); QPointF bottomRight(areaRect.width(), 0); QTransform trans; trans.rotate(-angleDeg); auto endButtomRight = trans.map(bottomRight); LtsCoordTrans::UtmCoordinate coordBr(tempCoord.zone, endButtomRight.x() + areaRect.bottomLeft().x(), endButtomRight.y() + areaRect.bottomLeft().y()); QPointF topRight(0, areaRect.height()); auto endTopRight = trans.map(topRight); LtsCoordTrans::UtmCoordinate coordTr(tempCoord.zone, endTopRight.x() + areaRect.bottomLeft().x(), endTopRight.y() + areaRect.bottomLeft().y()); QPointF topleft(areaRect.width(), areaRect.height()); auto endTopleft = trans.map(topleft); LtsCoordTrans::UtmCoordinate coordTl(tempCoord.zone, endTopleft.x() + areaRect.bottomLeft().x(), endTopleft.y() + areaRect.bottomLeft().y()); LtsCoordTrans::UtmCoordinate coordBl(tempCoord.zone, areaRect.bottomLeft().x(), areaRect.bottomLeft().y()); DEBUG_LOG(m_logger) << "--[createSegmentsFromMineData] AREA HEIGHT(newWidth): " << areaRect.height() << " oldWidth: " << m_data.width; newWidth = areaRect.height(); // anscheint darstellen beginnt von mitte QPointF centerTopleft(areaRect.width(), areaRect.height() / 2.0); auto transTopleft = trans.map(centerTopleft); LtsCoordTrans::UtmCoordinate coordCenterTl(tempCoord.zone, transTopleft.x() + areaRect.bottomLeft().x(), transTopleft.y() + areaRect.bottomLeft().y()); QPointF centerTopRight(0, areaRect.height() / 2.0); auto transTopRight = trans.map(centerTopRight); LtsCoordTrans::UtmCoordinate coordCenterTr(tempCoord.zone, transTopRight.x() + areaRect.bottomLeft().x(), transTopRight.y() + areaRect.bottomLeft().y()); outSegments.reserve(2); // to generate segment from rect 2 point are needed (width is known) outSegments.emplace_back(Core::GeoPosition2D(coordCenterTl.geographic().x(), coordCenterTl.geographic().y())); outSegments.emplace_back(Core::GeoPosition2D(coordCenterTr.geographic().x(), coordCenterTr.geographic().y())); DEBUG_LOG(m_logger) << " ------ [createSegmentsFromMineData] geo bl: " << Core::GeoPosition2D(coordBl.geographic().x(), coordBl.geographic().y()); DEBUG_LOG(m_logger) << " ------ [createSegmentsFromMineData] geo br: " << Core::GeoPosition2D(coordBr.geographic().x(), coordBr.geographic().y()); DEBUG_LOG(m_logger) << " --**-- [createSegmentsFromMineData] geo tl: " << Core::GeoPosition2D(coordTl.geographic().x(), coordTl.geographic().y()); DEBUG_LOG(m_logger) << " --**-- [createSegmentsFromMineData] geo tr: " << Core::GeoPosition2D(coordTr.geographic().x(), coordTr.geographic().y()); DEBUG_LOG(m_logger) << " --**-- [createSegmentsFromMineData] geo ctl: " << Core::GeoPosition2D(coordCenterTl.geographic().x(), coordCenterTl.geographic().y()); DEBUG_LOG(m_logger) << " --**-- [createSegmentsFromMineData] geo ctr: " << Core::GeoPosition2D(coordCenterTr.geographic().x(), coordCenterTr.geographic().y()); return Core::FunctionReplyObject(m_data.id); } void ObjectMineArea::onEffectStateChanged() { DEBUG_LOG(m_logger) << id() << " EffectStateChanged for MineArea " << m_data.id << ". Check if mines have to be released."; // If is an DUS-MineArea and it's state is Expired or Removed and still has mines if ((isDusMineArea() || isConcreteMine()) && (m_data.effectStateId.underlying() == AesEffectState::EsExpired || m_data.effectStateId.underlying() == AesEffectState::EsRemoved) && !m_triggeredMines.isEmpty()) { DEBUG_LOG(m_logger) << id() << " Releasing all mines from " << toString(m_data.effectStateId) << " MineArea " << m_data.id; emit releaseMines(m_data.id, m_triggeredMines.values()); m_triggeredMines.clear(); } } QPolygonF ObjectMineArea::generateForm() { DEBUG_LOG(m_logger) << "generateForm()"; return Polygons::mineArea(m_segments, m_data.width, m_data.density, m_data.isCenterLine); } // get startPos of all mines (nearest to the smaller side of the polygon) int ObjectMineArea::msGetIndexMine(const QVector &box, const QVector &minen) { double dist; double dist0; double dist1; double dx0; double dy0; double dx1; double dy1; double px; double py; double px0; double py0; double px1; double py1; int idx; dx0 = box.at(1).x - box.at(0).x; dy0 = box.at(1).y - box.at(0).y; dist0 = sqrt(dx0 * dx0 + dy0 * dy0); dx1 = box.at(2).x - box.at(1).x; dy1 = box.at(2).y - box.at(1).y; dist1 = sqrt(dx1 * dx1 + dy1 * dy1); if (dist0 < dist1) { px0 = box.at(1).x; py0 = box.at(1).y; px1 = box.at(0).x; py1 = box.at(0).y; } else { px0 = box.at(2).x; py0 = box.at(2).y; px1 = box.at(1).x; py1 = box.at(1).y; } px = minen.at(0).x; py = minen.at(0).y; dist = msGetDistanceFromLine(px, py, px0, py0, px1, py1); idx = 0; for (int i = 0; i < minen.size(); i++) { px = minen.at(i).x; py = minen.at(i).y; if (dist > msGetDistanceFromLine(px, py, px0, py0, px1, py1)) { dist = msGetDistanceFromLine(px, py, px0, py0, px1, py1); idx = i; } } return idx; } double ObjectMineArea::msGetDistanceFromLine(double px, double py, double px0, double py0, double px1, double py1) { double dist; double a11; double a12; double a21; double a22; double b1; double b2; double det; double x; a11 = px1 - px0; a12 = py1 - py0; a21 = py1 - py0; a22 = px0 - px1; b1 = px - px0; b2 = py - py0; det = a11 * a22 - a12 * a21; if (fabs(det) < 0.001) return 0.0; x = (a11 * b2 - b1 * a21) / det; dist = x * sqrt(a21 * a21 + a22 * a22); return fabs(dist); } } // Namespace AreaEffectSimulation