Files
qt5-legatus-legacy/Lts/Services/AreaEffectSimulation/object_mine_area.cpp
T
2026-08-13 18:33:10 +02:00

1662 lines
62 KiB
C++
Executable File

/**
* 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 <AreaEffectSimulation/aes_translation.h>
#include <QLineF>
#include <QPolygonF>
#include <LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/request_helper.h> // LtsUtils::toOptional
#include <LtsUtilsCore/dds_bitarray.h>
#include <ngldb/dbtransaction.h>
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<Core::GeoPosition2D> &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<MineSegment> &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<Core::GeoPosition2D> &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<MineSegment> &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<MineClass::type>(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId))
== MineClass::DUS;
}
bool ObjectMineArea::isConcreteMine() const
{
return static_cast<MineClass::type>(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId))
== MineClass::Concrete;
}
bool ObjectMineArea::isSingleVirtual() const
{
return static_cast<MineClass::type>(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId))
== MineClass::VirtualSingle;
}
bool ObjectMineArea::isVirtualMine() const
{
return static_cast<MineClass::type>(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<int> 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<int> &mseIds = m_mainHandler->dds()->ismDtc()->getBuildingByPolygon(m_mainHandler->dds()->exerciseId(), m_mineAreaPolygon, false, isGroundLevel);
DEBUG_LOG(m_logger) << "mseIds size: " << mseIds.size();
QVector<int> 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<int> &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<Database::AesMineAreaTriggeredMines> 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<Database::AesMineAreaTriggeredMines> ObjectMineArea::unpairedMines() const
{
QVector<Database::AesMineAreaTriggeredMines> 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<Core::GeoPosition2D> &inSegments,
int mineAreaId,
QVector<Database::AesMineAreaSegment> &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<AreaEffectSimulation::MineSegment> &inSegments,
int mineAreaId,
QVector<Database::AesMineAreaSegment> &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<Core::GeoPosition2D> vec;
vec.reserve(m_triggeredMines.size());
QVector<LtsCoordTrans::UtmCoordinate> 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<LtsCoordTrans::UtmCoordinate> 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<Core::GeoPosition2D> 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<Core::GeoPosition2D>(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<Database::AesMineAreaSegment> 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 <typename T> Core::FunctionReplyObject ObjectMineArea::createObjectTmpl(const Database::AesMineArea &data, const std::vector<T> &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<Core::GeoPosition> 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 <typename T> Core::FunctionReplyObject ObjectMineArea::updateObjectTmpl(const Database::AesMineArea &data, const std::vector<T> &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<Database::AesMineAreaTriggeredMines> 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<Database::AesMineAreaSegment> 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<int, Database::AesMineAreaTriggeredMines> it(m_triggeredMines);
QList<Database::AesMineAreaTriggeredMines> 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<Core::GeoPosition2D> &segment,
const std::vector<MineData> &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<Core::GeoPosition2D> &segments,
const std::vector<MineData> &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<Database::AesMineAreaSegment> 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<int> &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<QPointF> 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> &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<int> mineIds;
int idx = m_triggeredMines.isEmpty() ? 0 : m_triggeredMines.lastKey() + 1;
QMap<int, Database::AesMineAreaTriggeredMines> 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<int, Database::AesMineAreaTriggeredMines> &outTriggerMines)
{
QMap<int, Database::AesMineAreaTriggeredMines> 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<MineSegment> 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> &mineData,
std::vector<Core::GeoPosition2D> &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<Core::GeoPosition2D> 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<QPointF> 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<LtsCoordTrans::UtmCoordinate> &box, const QVector<LtsCoordTrans::UtmCoordinate> &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