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

1770 lines
67 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
*/
#include "polygons.h"
#include "tne_algorithmen.h"
#include <aes.hpp>
#include <AreaEffectSimulation/logger.h>
#include <ElevationMap/elevation_map.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include <LtsCoordinate/lts_coord_vector.h>
#include <LtsCoordinate/lts_coordinate.h>
#include <LtsUtilsCore/qpoint3d.h>
#include <ParticipantManagement/ppm_dtc.h>
#include <ProjectDataManagement/pdm_db_structs.h>
#include <Trajectory/trajectory_helper.h>
#include <float.h>
#include <math.h>
#include <QtCore/QRectF>
#include <QtGui/QGuiApplication>
#include <QtGui/QIcon>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <QtGui/QPainterPath>
#include <QtGui/QPen>
#include <QtGui/QPolygonF>
#include <QtGui/QTransform>
// // #define NEVER
// Note: All polygones are open!
std::ostream &operator<<(std::ostream &stream, const QVector<QPointF> &value)
{
stream << "QVector<QPointF>(" << value.size() << "): ";
for (const QPointF &item : value)
stream << item << ";";
return stream;
}
std::ostream &operator<<(std::ostream &stream, const QVector<LtsCoordTrans::UtmCoordinate> &value)
{
stream << "QVector<UtmCoordinate>(" << value.size() << "): ";
for (const LtsCoordTrans::UtmCoordinate &item : value)
stream << item << ";";
return stream;
}
namespace AreaEffectSimulation
{
namespace Math
{
template <class T> static inline T compass2rad(T degree)
{
static_assert(!std::is_integral<T>::value, "Floating Point Value Required! Integer may be in Centidegree?!?");
return (M_PI / 2.0) - (degree * M_PI / 180.0);
}
template <class T> static inline T rad2compass(T rad) { return 90.0 - (rad * 180.0 / M_PI); }
template <class T> static inline T deg2rad(T degree)
{
static_assert(!std::is_integral<T>::value, "Floating Point Value Required! Integer may be in Centidegree?!?");
return degree * M_PI / 180.0;
}
template <class T> static inline T rad2deg(T rad) { return rad * 180.0 / M_PI; }
static constexpr int MZ = 32768;
static constexpr int CZ = 101;
static constexpr int AZ = 21;
} // namespace Math
namespace Polygons
{
// regarding ExplosionCluster:
bool isPointSimilar(AreaEffectSimulation::AesExplosionGeometry geometry)
{
return (geometry.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint)
|| (geometry.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgPoint);
}
// AreaBottomLeft :
AreaBottomLeft::AreaBottomLeft(const QPointF &cornerBottomLeftMeter, double widthMeter, double heightMeter, double rotation_deg)
: m_cornerBottomLeft(cornerBottomLeftMeter), m_width(widthMeter), m_height(heightMeter), m_rotation_deg(rotation_deg)
{
}
AreaBottomLeft::AreaBottomLeft(const LtsCoordTrans::UtmCoordinate &cornerBottomLeftMeter, double widthMeter, double heightMeter, double rotation_deg)
: m_cornerBottomLeft(QPointF(cornerBottomLeftMeter.x, cornerBottomLeftMeter.y)), m_width(widthMeter), m_height(heightMeter), m_rotation_deg(rotation_deg)
{
}
bool AreaBottomLeft::isValid() const { return !m_cornerBottomLeft.isNull() && (m_width > 0) && (m_height > 0); }
// : AreaBottomLeft
const int DEGREE_PER_STEP = 5;
static inline QTransform fromRotation(double rot) { return QTransform().rotate(rot); }
static QPolygonF utmToGeogrphic(const QPolygonF &polyUtm, const LtsCoordTrans::UtmCoordinate &base)
{
QPolygonF poly;
poly.reserve(polyUtm.size());
for (const QPointF &point : qAsConst(polyUtm))
poly << base.geographic(point);
return poly;
}
static const QString string4poly(const QPolygonF &poly)
{
if (poly.isEmpty())
return (QStringLiteral("Empty Polygon!"));
int zone = LtsCoordTrans::UtmCoordinate::fromGeographic(poly.first()).zone;
QStringList pointString;
pointString.reserve(poly.size());
for (const QPointF &point : poly)
pointString << LtsCoordTrans::UtmCoordinate::fromGeographic(point, zone).mgrs();
return QStringLiteral("%1 Values (%2)").arg(poly.count()).arg(pointString.join(QStringLiteral(" --- ")));
}
static const QString string4poly(const QPair<QPolygonF, QVector<double>> &pair)
{
if (pair.first.isEmpty())
return (QStringLiteral("Empty Polygon!"));
int zone = LtsCoordTrans::UtmCoordinate::fromGeographic(pair.first[0]).zone;
QStringList pointString;
pointString.reserve(pair.first.size());
for (int i = 0; i < pair.first.size(); ++i)
pointString << LtsCoordTrans::UtmCoordinate::fromGeographic(pair.first[i], zone).mgrs() << QStringLiteral(" ") << QString::number(pair.second[i], 'f', 1);
return QStringLiteral("%1 Values (%2)").arg(pair.first.count()).arg(pointString.join(QStringLiteral(" --- ")));
}
QPolygonF circle(const QPointF &center, qreal radius, int steps)
{
const LtsCoordTrans::UtmCoordinate &centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(center);
QPolygonF poly;
if (steps < 6)
steps = 6;
poly.reserve(steps);
for (int i = 0; i < 360; i += (360 / steps))
{
double angle = Math::compass2rad(double(i));
poly << centerUtm.geographic(QPointF(cos(angle) * radius, sin(angle) * radius));
}
return poly;
}
/* BCT
* ,,,x
* ,,,´´´ |
* ,,,´´´ |
* MCT ,,,´´´ | |
* SCT ,,,,,,,,,x´´´ | |
* x----´´´´´´´´,,,´´´ | | |
* ´ ` ,,,´´´ | | |
* / \´´ | Span |
* | Cx | MCC x Angle |
* \ /,, | | |
* , , ```,,, | | |
* x----,,,,,,,,```,,, | | |
* SCB `````````x,,, | |
* MCB ```,,, | |
* ```,,, |
* ```,,, |
* ```x
* BCB
*
* SCT = Small (Medium, Big) Circle Top (Bottom), etc
*
* At SCT (SCB) are right angles between MCT (MCB) and C!
* C->MCT == C->MCB = borderLengthInner
* MCT->BCT == MCT->BCB = borderLengthOuter
* C->SCT == C->SCB = radius
*
*/
QPolygonF nContamination(const Database::AesNContamination &awf)
{
// Special Case: BorderLengthInner == 0
LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(awf.impactPosition);
if (qFuzzyIsNull(awf.borderLengthInner))
{
if (!qFuzzyIsNull(awf.borderLengthOuter))
{
DEBUG_LOG(getLogger("Utils.NContamination")) << "Borderlength Inner Area may only be null if Borderlength outer Area is also null!";
return QPolygonF();
}
return circle(awf.impactPosition, awf.impactRadius, 360 / DEGREE_PER_STEP);
}
if (awf.impactRadius > awf.borderLengthInner)
{
DEBUG_LOG(getLogger("Utils.NContamination")) << "Borderlength Inner Area must be greater than impact radius!";
return QPolygonF();
}
double halfSpanAngleRad = Math::deg2rad(awf.spanAngle) / 2.0; // Angle between MCC and MCT at C
double acos_val = awf.impactRadius / awf.borderLengthInner; // Adjacent / Hypotenuse (Angle between SCT and MCT at C)
double scAngleRad = acos(acos_val) + halfSpanAngleRad; // Thus, Angle between MCC and SCT at C. ("Small Circle Angle")
// Calculate Points:
QPointF sct(cos(scAngleRad) * awf.impactRadius, sin(scAngleRad) * awf.impactRadius);
// QPointF scb (cos(scAngleRad) * awf.impactRadius, -sin(scAngleRad) * awf.impactRadius);
QPointF mct(cos(halfSpanAngleRad) * (awf.borderLengthInner), sin(halfSpanAngleRad) * (awf.borderLengthInner));
QPointF mcb(cos(halfSpanAngleRad) * (awf.borderLengthInner), -sin(halfSpanAngleRad) * (awf.borderLengthInner));
// QPointF bct ( cos(halfSpanAngleRad) * (awf.borderLengthInner + awf.borderLengthOuter),
// sin(halfSpanAngleRad) * (awf.borderLengthInner + awf.borderLengthOuter));
QPointF bcb(cos(halfSpanAngleRad) * (awf.borderLengthInner + awf.borderLengthOuter), -sin(halfSpanAngleRad) * (awf.borderLengthInner + awf.borderLengthOuter));
QPolygonF polyUtm;
polyUtm.reserve(360 / DEGREE_PER_STEP + 6);
polyUtm << sct;
polyUtm << mct;
// poly << bct; // In For
int halfSpanAngleDeg = qRound(awf.spanAngle / 2); // Calculate in degree for easier "for"
for (int i = halfSpanAngleDeg; i > -halfSpanAngleDeg; i -= DEGREE_PER_STEP) // 2 degree steps
{
double angleRad = Math::deg2rad(double(i));
QPointF pointOnBigCircle(cos(angleRad) * (awf.borderLengthInner + awf.borderLengthOuter), sin(angleRad) * (awf.borderLengthInner + awf.borderLengthOuter));
polyUtm << pointOnBigCircle;
}
polyUtm << bcb; // May not be in for, if (spanAngle % steps != 0)
polyUtm << mcb;
// poly << scb; // In For
int scAngleDeg = qRound(Math::rad2deg(scAngleRad));
// From SCB to SCT, counterclockwise:
for (int i = -scAngleDeg; i > scAngleDeg - 360; i -= DEGREE_PER_STEP)
{
double angleRad = Math::deg2rad(double(i));
QPointF pointOnSmallCircle(cos(angleRad) * awf.impactRadius, sin(angleRad) * awf.impactRadius);
polyUtm << pointOnSmallCircle;
}
// poly << sct; // May not be in for, if (spanAngle % steps != 0) Only necessary to close Polygon
// The cunstructed poly ist heading eastwards but must be rotated as from north
polyUtm = fromRotation(90.0 - awf.rotationAngle).map(polyUtm);
const QPolygonF poly(utmToGeogrphic(polyUtm, impactPositionUtm));
DEBUG_LOG(getLogger("Utils.NContamination")) << string4poly(poly);
return poly;
}
static inline double sanitizeExpansionFactor(double expIn)
{
// Assure: 0 <= Expansion Factor <= 2.55
if (expIn < 0)
return 0.;
if (expIn > 2.55)
return 2.55;
return expIn;
}
void bcCurrentCenterPoint(const Database::AesBCContamination &cwf,
ProjectDataManagement::VolatilityType volatilityType,
const QDateTime &currentTime,
QPointF &newCenterPointGeographic,
double &distance)
{
distance = 0;
// no weather data for now or no effect start for now or effect start in the future
// -> current position == impact position
if (cwf.weatherTimestamp.isNull() || cwf.effectStart.isNull() || cwf.effectStart > currentTime)
{
newCenterPointGeographic = cwf.impactPosition;
return;
}
QPointF startPoint;
if (volatilityType == ProjectDataManagement::VolatilityType::Floating)
{
// Moved Distance since Weather Time
if (currentTime > cwf.weatherTimestamp)
distance = cwf.windSpeed * cwf.weatherTimestamp.secsTo(currentTime);
// when the wind changes, the new positon is calculated from the last weather position + relative distance
startPoint = cwf.weatherPosition;
}
else
{
// if not yet started -> start point is impact position
const LtsCoordTrans::UtmCoordinate weatherUtm(LtsCoordTrans::UtmCoordinate::fromGeographic(cwf.weatherPosition));
const LtsCoordTrans::UtmCoordinate impactUtm(LtsCoordTrans::UtmCoordinate::fromGeographic(cwf.impactPosition, weatherUtm.zone));
// Bestimmen der Länge bis zur gueltigkeitszeit_wetterdaten
QLineF vec(QPointF(weatherUtm.x, weatherUtm.y), QPointF(impactUtm.x, impactUtm.y));
// Berechnen der gesamten Länge
if (currentTime > cwf.weatherTimestamp)
distance = cwf.windSpeed * cwf.weatherTimestamp.secsTo(currentTime) + vec.length();
// when the wind changes, the new positon is calculated from impact position + absolute distance
startPoint = cwf.impactPosition;
}
// Size: Max 10km!
if (distance > 10000.0)
distance = 10000.0;
double propagationDirectionRad = Math::compass2rad(cwf.propagationDirection);
LtsCoordTrans::UtmCoordinate centerPointUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(startPoint);
centerPointUtm.x += distance * cos(propagationDirectionRad);
centerPointUtm.y += distance * sin(propagationDirectionRad);
newCenterPointGeographic = centerPointUtm.geographic();
}
QPolygonF bcContamination(const Database::AesBCContamination &cwf,
ProjectDataManagement::VolatilityType volatilityType,
double expansionFactor,
const QDateTime &currentTime)
{
double distance = 0;
QPointF centerPoint;
bcCurrentCenterPoint(cwf, volatilityType, currentTime, centerPoint, distance);
if (volatilityType == ProjectDataManagement::VolatilityType::Floating) // FLUECHTIG
{
double radius = cwf.impactRadius;
if (!cwf.effectStart.isNull() && currentTime >= cwf.effectStart)
{
expansionFactor = sanitizeExpansionFactor(expansionFactor);
// Radius Growth since Effect Start
radius += expansionFactor * cwf.effectStart.secsTo(currentTime);
}
QPolygonF poly = circle(centerPoint, radius, 360 / DEGREE_PER_STEP);
DEBUG_LOG(getLogger("Utils.BCContamination")) << string4poly(poly);
return poly;
}
// Non volatile: SESSHAFT
/*!
*The non volatile c area has maximum distance of 10km
* gemaess TN0006, Kapitel 3.5.1.2.1 (GUEZE-3614)
!*/
if (distance > 10000)
distance = 10000;
QPolygonF polyUtm;
polyUtm.reserve(DEGREE_PER_STEP + 2); // + (0=360) + (180=180) if applicable
LtsCoordTrans::UtmCoordinate impactUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(cwf.impactPosition);
for (int i = 0; i <= 180; i += DEGREE_PER_STEP)
{
double angleRad = Math::compass2rad(double(i));
polyUtm << QPointF(cos(angleRad) * cwf.impactRadius + distance, sin(angleRad) * cwf.impactRadius);
}
for (int i = 180; i <= 360; i += DEGREE_PER_STEP)
{
double angleRad = Math::compass2rad(double(i));
polyUtm << QPointF(cos(angleRad) * cwf.impactRadius, sin(angleRad) * cwf.impactRadius);
}
// The constructed poly ist heading eastwards but must be rotated as from north
polyUtm = fromRotation(90.0 - cwf.propagationDirection).map(polyUtm);
const QPolygonF poly(utmToGeogrphic(polyUtm, impactUtm));
DEBUG_LOG(getLogger("Utils.BCContamination")) << string4poly(poly);
return poly;
}
QPolygonF airSupportImpact(const Database::AesAirSupportImpact &as, double chargeRadius)
{
QPolygonF poly = circle(as.impactPosition, chargeRadius);
DEBUG_LOG(getLogger("Utils.AirSupportImpact")) << string4poly(poly);
return poly;
}
QPolygonF explosionSingle(const Database::AesExplosionSingle &ef, double chargeRadius)
{
QPolygonF poly = circle(ef.impactPosition, chargeRadius);
DEBUG_LOG(getLogger("Utils.ExplosionSingle")) << string4poly(poly);
return poly;
}
QPolygonF explosionArea(const Database::AesExplosionArea &ea)
{
QPolygonF polyUtm = QPolygonF({QPointF(0, 0), QPointF(ea.impactWidth, 0), QPointF(ea.impactWidth, ea.impactLength), QPointF(0, ea.impactLength)});
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
polyUtm = fromRotation(-ea.rotationAngle).map(polyUtm);
LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(ea.impactPosition);
const QPolygonF poly(utmToGeogrphic(polyUtm, impactPositionUtm));
DEBUG_LOG(getLogger("Utils.ExplosionArea")) << string4poly(poly);
return poly;
}
AreaBottomLeft explosionClusterAddTla(const AreaBottomLeft &areaBottomLeftIn, int tla)
{
if (tla < 0) // ZieOrtungsGenauigkeit
tla = 0;
QPointF tlaOffset(-tla, -tla);
QPointF tlaOffsetRot = QTransform().rotate(-areaBottomLeftIn.m_rotation_deg).map(tlaOffset);
AreaBottomLeft areaWithTla = areaBottomLeftIn;
areaWithTla.m_cornerBottomLeft = areaBottomLeftIn.m_cornerBottomLeft + tlaOffsetRot;
areaWithTla.m_width += 2 * tla;
areaWithTla.m_height += 2 * tla;
return areaWithTla;
}
QPolygonF explosionClusterArea(const Database::AesExplosionCluster &explosionCluster)
{
long tla = explosionCluster.zog; // ZieOrtungsGenauigkeit
if (tla < 0)
tla = 0;
QPolygonF poly;
const bool bIsPointSimilar = isPointSimilar(explosionCluster.explosionGeometryId);
if (bIsPointSimilar || explosionCluster.explosionGeometryId.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial)
{
// get center ... (zog isn´t relevant yet)
const double radius = explosionCluster.impactWidth / 2.0;
const LtsCoordTrans::UtmCoordinate &impactPositionUtm
= LtsCoordTrans::UtmCoordinate::fromGeographic(explosionCluster.impactPosition); // impactPosition = cornerBottomLeft
const QPointF &centerGeo = impactPositionUtm.geographic(radius, radius);
// ... and now take zog into account:
poly = circle(centerGeo, radius + tla);
}
else
{
QPolygonF polyUtm = QPolygonF({QPointF(0 - tla, 0 - tla),
QPointF(explosionCluster.impactWidth + tla, 0 - tla),
QPointF(explosionCluster.impactWidth + tla, explosionCluster.impactLength + tla),
QPointF(0 - tla, explosionCluster.impactLength + tla)});
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
polyUtm = fromRotation(-explosionCluster.rotationAngle).map(polyUtm);
LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(explosionCluster.impactPosition);
poly = utmToGeogrphic(polyUtm, impactPositionUtm);
}
DEBUG_LOG(getLogger("Utils.ExplosionCluster")) << string4poly(poly);
return poly;
}
QVector<QPointF> effectClusterSingleExplosionsGAUSS(const Database::AesEffectCluster &effectCluster)
{
QVector<QPointF> vec;
const int impactCount = effectCluster.impactCount;
if (impactCount <= 0 || (effectCluster.effectStart.isNull() && effectCluster.originalEffectStart.isNull()))
return vec;
int randomNumber = effectCluster.id;
const LtsCoordTrans::UtmCoordinate &impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(effectCluster.impactPosition); // impactPosition = cornerBottomLeft
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
DEBUG_LOG(logger) << "impactCount=" << impactCount;
const QVector<QPointF> &impactsGAUSS = TNE_ALGORITHMEN::areaExlosionsGAUSS(randomNumber, effectCluster.impactWidth, effectCluster.impactLength, impactCount);
DEBUG_LOG(logger) << "impactsGAUSS.size=" << impactsGAUSS.size();
vec.reserve(impactCount);
const QTransform trans(fromRotation(-effectCluster.rotationAngle)); // Compass, not mathematical!
for (const QPointF &point : qAsConst(impactsGAUSS))
{
QPointF p = impactPositionUtm.geographic(trans.map(point));
DEBUG_LOG(logger) << "Point = " << p;
vec.push_back(p);
}
return vec;
}
QVector<QPointF> effectClusterSingleExplosionsGAUSS_Mtp(const Database::AesEffectCluster &effectCluster, const QHash<int, Database::AesEffectorData> &effectorData)
{
int impactCount = 0;
int mhpCount = 0;
int mtpRotationNorthDegree = 0;
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
for (const auto &effector : effectorData)
{
impactCount += effector.chargeAmount;
mhpCount++;
mtpRotationNorthDegree = effector.mhpRotation;
}
if ((impactCount <= 0) || (mhpCount <= 0)
// || ((explosionCluster.effectStart.isNull() && explosionCluster.originalEffectStart.isNull()))
)
{
ERROR_LOG(logger) << "data invalid: impactCount = " << impactCount << ", mhpCountRequest = " << mhpCount;
return {};
}
//
// some convenience for ModuleTests :
//
int randomNumber = effectCluster.id;
if (effectCluster.name.startsWith(QStringLiteral("ModuleTest"), Qt::CaseSensitive))
{
randomNumber = 0;
if (effectCluster.name.startsWith(QStringLiteral("ModuleTest1_"), Qt::CaseSensitive))
randomNumber = 1;
else if (effectCluster.name.startsWith(QStringLiteral("ModuleTest_TneVerification"), Qt::CaseSensitive))
randomNumber = 32767;
}
// : some convenience for ModuleTests
if (randomNumber <= 1)
{
NOTICE_LOG(logger) << "XXXXXXXXXX randomNumber is: " << randomNumber;
NOTICE_LOG(logger) << "XXXXXXXXXX randomNumber is: " << randomNumber;
NOTICE_LOG(logger) << "XXXXXXXXXX randomNumber is: " << randomNumber;
}
DEBUG_LOG(logger) << "impactCount=" << impactCount << ", randomNumber=" << randomNumber;
const LtsCoordTrans::UtmCoordinate &impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(effectCluster.impactPosition); // impactPosition = cornerBottomLeft
const Polygons::AreaBottomLeft areaBLOrgWithoutTla(impactPositionUtm, effectCluster.impactWidth, effectCluster.impactLength, effectCluster.rotationAngle);
const Polygons::AreaBottomLeft areaBlWithTla = Polygons::explosionClusterAddTla(areaBLOrgWithoutTla, effectCluster.tla);
const int numberOfMTPsRequested = mhpCount;
Q_ASSERT(numberOfMTPsRequested > 0);
double singleWidthOut, singleHeightOut;
singleWidthOut = singleHeightOut = 0;
//
// calculate the subAreas / mtps:
//
const QVector<QPointF> &mtps_asUtm = explosionClusterAreaToMtps(areaBlWithTla, numberOfMTPsRequested, &singleWidthOut, &singleHeightOut);
if (mtps_asUtm.size() != numberOfMTPsRequested)
{
ERROR_LOG(logger) << "ERROR mismatch: numberOfMTPsRequested=" << numberOfMTPsRequested << ", mtps_asUtm.size=" << mtps_asUtm.size();
return {};
}
DEBUG_LOG(logger) << " singleWidthOut=" << singleWidthOut << ", singleHeightOut=" << singleHeightOut << ", mtps_asUtm" << mtps_asUtm;
QVector<LtsCoordTrans::UtmCoordinate> mtps_asRealUtm;
mtps_asRealUtm.reserve(mtps_asUtm.size());
for (const QPointF &mtp_asUtm : qAsConst(mtps_asUtm))
{
// convert QPointF back to UtmCoordinate: pay attention to order!!!
const LtsCoordTrans::UtmCoordinate mtpUtm(impactPositionUtm.zone, mtp_asUtm.x(), mtp_asUtm.y());
mtps_asRealUtm.push_back(mtpUtm);
}
// Bsp: 5 Impacts bei 3 MTPs
// 1st impact -> 1st MTP
// 2nd impact -> 2nd MTP
// 3rd impact -> 3rd MTP
// 4th impact -> 1st MTP
// 5th impact -> 2nd MTP
// ..
//
// !!! like TNE !!!
//
//
// !!! like TNE !!!
//
//
// !!! like TNE !!!
//
//
// like TNE calculate all impacts and move them alternating to the mtps:
//
// {
// singleWidthOut = 1;
// singleHeightOut = 1;
// }
const QVector<QPointF> &impactsGAUSS = TNE_ALGORITHMEN::areaExlosionsGAUSS_aroundCenter(randomNumber, singleWidthOut, singleHeightOut, impactCount); // breite , tiefe
QVector<QPointF> finalVecGeo;
finalVecGeo.reserve(impactsGAUSS.size());
int idx = 0;
const QTransform transform(fromRotation(-mtpRotationNorthDegree)); // Compass, not mathematical!
for (const QPointF &point : qAsConst(impactsGAUSS))
{
// which mtp:
int mtpIdx = (idx++ % mtps_asUtm.size()); // modulo
const LtsCoordTrans::UtmCoordinate &mtpRealUtm = mtps_asRealUtm[mtpIdx];
// const QPointF &p = mtpRealUtm.geographic(offset);
const QPointF &p = mtpRealUtm.geographic(transform.map(point)); // geographic -> geoCoord
finalVecGeo.push_back(p);
} // for
#ifdef NEVER
qCritical() << " TNE VERIFICATION: CHECK:"
<< ", randomNumber=" << randomNumber << " singleWidthOut=" << singleWidthOut << " singleHeightOut=" << singleHeightOut
<< " rot=" << explosionCluster.rotationAngle;
for (auto item : qAsConst(finalVec))
{
const LtsCoordTrans::UtmCoordinate &utm = LtsCoordTrans::UtmCoordinate::fromGeographic(item);
qCritical() << " TNE VERIFICATION: CHECK:"
<< " utm: " << utm.mgrs() << ", item=" << item;
}
#endif // NEVER
return finalVecGeo;
}
QPolygonF effectClusterArea(const Database::AesEffectCluster &effectCluster)
{
long tla = effectCluster.tla; // ZieOrtungsGenauigkeit
if (tla < 0)
tla = 0;
QPolygonF poly;
const bool bIsPointSimilar = isPointSimilar(effectCluster.explosionGeometryId);
if (bIsPointSimilar || effectCluster.explosionGeometryId.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial)
{
// get center ... (zog isn´t relevant yet)
const double radius = effectCluster.impactWidth / 2.0;
const LtsCoordTrans::UtmCoordinate &impactPositionUtm
= LtsCoordTrans::UtmCoordinate::fromGeographic(effectCluster.impactPosition); // impactPosition = cornerBottomLeft
const QPointF &centerGeo = impactPositionUtm.geographic(radius, radius);
// ... and now take zog into account:
poly = circle(centerGeo, radius + tla);
}
else
{
QPolygonF polyUtm = QPolygonF({QPointF(0 - tla, 0 - tla),
QPointF(effectCluster.impactWidth + tla, 0 - tla),
QPointF(effectCluster.impactWidth + tla, effectCluster.impactLength + tla),
QPointF(0 - tla, effectCluster.impactLength + tla)});
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
polyUtm = fromRotation(-effectCluster.rotationAngle).map(polyUtm);
LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(effectCluster.impactPosition);
poly = utmToGeogrphic(polyUtm, impactPositionUtm);
}
DEBUG_LOG(getLogger("Utils.EffectCluster")) << string4poly(poly);
return poly;
}
QVector<QPolygonF> mineAreaSingleSegments(const QVector<Database::AesMineAreaSegment> &segments, double width, bool bIsCenterLine)
{
QVector<QPolygonF> singleSegments;
singleSegments.reserve(segments.size());
for (const Database::AesMineAreaSegment &segment : segments)
singleSegments.push_back(mineAreaSegment(segment, width, bIsCenterLine));
return singleSegments;
}
QPolygonF mineArea(const QVector<Database::AesMineAreaSegment> &segments, double width, double density, bool bIsCenterLine)
{
LOGGER_T logger = getLogger("Utils.MineArea2");
QPolygonF overallPolygon;
double offset = 0.5 / density;
for (int i = 0; i < segments.size(); ++i)
{
const Database::AesMineAreaSegment &segment = segments[i];
QPolygonF segPoly;
DEBUG_LOG(logger) << "Offset[" << i << "]: " << offset;
segPoly = mineAreaSegment(segment, width, bIsCenterLine);
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});
DEBUG_LOG(logger) << "Offset-Prior[" << i << "]: " << offset;
DEBUG_LOG(logger) << "Line Length: " << line.length();
offset += line.length();
while (offset > (1. / density))
offset -= (1. / density);
DEBUG_LOG(logger) << "Offset-Posterior[" << i << "]: " << offset;
if (segPoly.size() < 4)
continue;
DEBUG_LOG(getLogger("Utils.MineArea2")) << segment.mineAreaId << " - Normal";
const int pos = overallPolygon.size() / 2;
overallPolygon.insert(pos, 4, QPointF());
for (int j = 0; j < 4; ++j)
overallPolygon[pos + j] = segPoly[j];
}
DEBUG_LOG(getLogger("Utils.Mine Area")) << string4poly(overallPolygon);
return overallPolygon;
}
QPolygonF mineAreaSegment(const Database::AesMineAreaSegment &segment, double width, bool bIsCenterLine)
{
// GUEZE-2254: 3D Wirksim -PzAbwVMi 3D
// In 3D wurde die Linie nicht visualisiert.
// Ein Rechteck mit war unendlich schmal, dadurch soll es quasi als
// Linienzug dargestellt werden, war in 3D aber nicht sichtbar.
// Ursache: Der Linienzug hatte identische Koordinaten.
// Das mag das SDK nicht so gerne.
// Leider ist dies so in Polygons::mineArea2Segment programmiert - schade
// Jan, 2024, Norbert Thoden
// Nachtrag:
// Machmal reicht es wohl doch nicht, den Linienzug von A, B, B, C zu
// reduzieren auf A, B, C
// Daher wird in mineArea2Segment nun eine Mindestbreite von 0.1m vorgenommen.
if (width < 1)
width = 0.1;
// : GUEZE-2254
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});
const auto yOfs1 = bIsCenterLine ? (width / 2) : -width;
const auto yOfs2 = bIsCenterLine ? -(width / 2) : 0;
QPolygonF polyUtm = QPolygonF({QPointF(0, yOfs1), QPointF(line.length(), yOfs1), QPointF(line.length(), yOfs2), QPointF(0, yOfs2)});
// Compass, not mathematical!
polyUtm = fromRotation(0.0 - line.angle()).map(polyUtm);
const QPolygonF poly(utmToGeogrphic(polyUtm, startPositionUtm));
DEBUG_LOG(getLogger("Utils.MineArea2Segment")) << string4poly(poly) << ", width=" << width << ", yOfs1=" << yOfs1 << ", yOfs2=" << yOfs2;
return poly;
}
QPolygonF functionalArea(const Database::AesFunctionalArea &fa)
{
QRectF rect(QPointF(), QSizeF(fa.width, fa.length));
QPolygonF overallPolygon(rect);
// overallPolygon = fromRotation(-fr.rotation).map(overallPolygon);
LtsCoordTrans::UtmCoordinate positionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(fa.position);
const QPolygonF poly(utmToGeogrphic(overallPolygon, positionUtm));
return poly;
}
QPolygonF technicalArea(const Database::AesTechnicalArea &ta)
{
QRectF rect(QPointF(), QSizeF(ta.width, ta.length));
QPolygonF overallPolygon(rect);
LtsCoordTrans::UtmCoordinate positionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(ta.position);
const QPolygonF poly(utmToGeogrphic(overallPolygon, positionUtm));
return poly;
}
QPolygonF reconArea(const Database::AesReconArea &ra)
{
QPolygonF polyUtm = QPolygonF({QPointF(0, 0), QPointF(ra.width, 0), QPointF(ra.width, ra.height), QPointF(0, ra.height)});
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
polyUtm = fromRotation(-ra.rotationAngle).map(polyUtm);
LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(ra.position);
const QPolygonF poly(utmToGeogrphic(polyUtm, impactPositionUtm));
DEBUG_LOG(getLogger("Utils.ReconArea")) << string4poly(poly);
return poly;
}
// QPolygonF dangerArea(const shm_gefahrenbereich_rt& gb)
// {
// QPolygonF overallPolygon(gb.anzahl_punkte + 1);
// for (int i=0; i<gb.anzahl_punkte; ++i)
// overallPolygon << QPointF(gb.punkte[i].p[0], gb.punkte[i].p[1]);
// overallPolygon << overallPolygon.value(0);
// return overallPolygon;
// }
QVector<QPointF> minePositions(const Database::AesMineArea &area, const QVector<Database::AesMineAreaSegment> &segments)
{
QVector<QPointF> vec;
if (segments.isEmpty())
return vec;
int totalMinesYet = 0; // Summe der Minen in den Segmenten 1 - (i-1)
quint16 randomNumber = static_cast<quint16>(area.id); // Zufallszahl
double totalLength = 0;
for (const Database::AesMineAreaSegment &segment : 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();
}
int32_t totalMines = qRound(totalLength * area.density);
if (totalMines <= 0)
return vec;
vec.reserve(totalMines);
double offset = 0.5 / area.density;
int currentMine = 0;
for (int currentSegment = 0; currentSegment < segments.count(); ++currentSegment)
{
const Database::AesMineAreaSegment &segment = segments[currentSegment];
const LtsCoordTrans::UtmCoordinate startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(segment.startPosition);
const LtsCoordTrans::UtmCoordinate endPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(segment.endPosition, startPositionUtm.zone);
const QLineF line({startPositionUtm.x, startPositionUtm.y}, {endPositionUtm.x, endPositionUtm.y});
double actualSegmentLength = line.length();
double easting = offset;
while (easting < actualSegmentLength)
{
// Skip for first mine
if (currentMine != 0)
{
// Formula for calculating the pseudo random number
randomNumber = (Math::AZ * randomNumber + Math::CZ) % Math::MZ;
}
// Distance of mine "j" to the line in segment "i"
// As mines are counted from 1, increment "totalMinesYet" first.
double northing = (pow(-1.0, (++totalMinesYet))) * (area.width * randomNumber / 2.0 / Math::MZ);
// if frontline - move 1/2 * width to south
if (!area.isCenterLine)
northing -= area.width / 2;
QPointF minePos(easting, northing);
// Compass, not mathematical!
minePos = fromRotation(0.0 - line.angle()).map(minePos);
easting += (1 / area.density);
auto mPos = startPositionUtm.geographic(minePos);
const LtsCoordTrans::UtmCoordinate minePositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(mPos);
DEBUG_LOG(getLogger("Utils.MinePositions2"))
<< QStringLiteral("WS-MS: z=%1 Mine an %2 Idx=%4").arg(randomNumber, 5, 10, QLatin1Char(' ')).arg(minePositionUtm.mgrs()).arg(currentMine);
vec << mPos;
++currentMine;
}
offset = easting - actualSegmentLength;
}
return vec;
}
QVector<QPointF> explosionAreaSingleExplosions(const Database::AesExplosionArea &explosionArea)
{
QVector<QPointF> vec;
const int impactCount = (explosionArea.cannonCount * explosionArea.cannonRounds);
if (impactCount <= 0 || (explosionArea.effectStart.isNull() && explosionArea.originalEffectStart.isNull()))
return vec;
int randomNumber = explosionArea.id;
const QTransform trans(fromRotation(-explosionArea.rotationAngle)); // Compass, not mathematical!
vec.reserve(impactCount);
const LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(explosionArea.impactPosition);
QPointF impactPoint;
for (int i = 0; i < impactCount; ++i)
{
if (i > 0)
randomNumber = (Math::AZ * randomNumber + Math::CZ) % Math::MZ;
impactPoint.setX(explosionArea.impactWidth * randomNumber / Math::MZ);
randomNumber = fmod((Math::AZ * randomNumber + Math::CZ), Math::MZ);
impactPoint.setY(explosionArea.impactLength * randomNumber / Math::MZ);
vec.push_back(impactPositionUtm.geographic(trans.map(impactPoint)));
}
return vec;
}
QVector<QPointF> explosionClusterSingleExplosionsGAUSS(const Database::AesExplosionCluster &explosionCluster)
{
QVector<QPointF> vec;
const int impactCount = explosionCluster.impactCount;
if (impactCount <= 0 || (explosionCluster.effectStart.isNull() && explosionCluster.originalEffectStart.isNull()))
return vec;
int randomNumber = explosionCluster.id;
const LtsCoordTrans::UtmCoordinate &impactPositionUtm
= LtsCoordTrans::UtmCoordinate::fromGeographic(explosionCluster.impactPosition); // impactPosition = cornerBottomLeft
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
DEBUG_LOG(logger) << "impactCount=" << impactCount;
const QVector<QPointF> &impactsGAUSS = TNE_ALGORITHMEN::areaExlosionsGAUSS(randomNumber, explosionCluster.impactWidth, explosionCluster.impactLength, impactCount);
DEBUG_LOG(logger) << "impactsGAUSS.size=" << impactsGAUSS.size();
vec.reserve(impactCount);
const QTransform trans(fromRotation(-explosionCluster.rotationAngle)); // Compass, not mathematical!
for (const QPointF &point : qAsConst(impactsGAUSS))
{
QPointF p = impactPositionUtm.geographic(trans.map(point));
DEBUG_LOG(logger) << "Point = " << p;
vec.push_back(p);
}
return vec;
}
QVector<QPointF> explosionClusterSingleExplosionsGAUSS_Mtp(const Database::AesExplosionCluster &explosionCluster, double mtpRotationNorthDegree)
{
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
const int impactCount = explosionCluster.impactCount;
if ((impactCount <= 0) || (explosionCluster.mhpCountRequest <= 0)
// || ((explosionCluster.effectStart.isNull() && explosionCluster.originalEffectStart.isNull()))
)
{
ERROR_LOG(logger) << "data invalid: impactCount = " << impactCount << ", mhpCountRequest = " << explosionCluster.mhpCountRequest;
return {};
}
//
// some convenience for ModuleTests :
//
int randomNumber = explosionCluster.id;
if (explosionCluster.name.startsWith(QStringLiteral("ModuleTest"), Qt::CaseSensitive))
{
randomNumber = 0;
if (explosionCluster.name.startsWith(QStringLiteral("ModuleTest1_"), Qt::CaseSensitive))
randomNumber = 1;
else if (explosionCluster.name.startsWith(QStringLiteral("ModuleTest_TneVerification"), Qt::CaseSensitive))
randomNumber = 32767;
}
// : some convenience for ModuleTests
if (randomNumber <= 1)
{
NOTICE_LOG(logger) << "XXXXXXXXXX randomNumber is: " << randomNumber;
NOTICE_LOG(logger) << "XXXXXXXXXX randomNumber is: " << randomNumber;
NOTICE_LOG(logger) << "XXXXXXXXXX randomNumber is: " << randomNumber;
}
DEBUG_LOG(logger) << "impactCount=" << impactCount << ", randomNumber=" << randomNumber;
const LtsCoordTrans::UtmCoordinate &impactPositionUtm
= LtsCoordTrans::UtmCoordinate::fromGeographic(explosionCluster.impactPosition); // impactPosition = cornerBottomLeft
const Polygons::AreaBottomLeft areaBLOrgWithoutTla(impactPositionUtm, explosionCluster.impactWidth, explosionCluster.impactLength, explosionCluster.rotationAngle);
const Polygons::AreaBottomLeft areaBlWithTla = Polygons::explosionClusterAddTla(areaBLOrgWithoutTla, explosionCluster.zog);
const QPointF center = Polygons::bottomLeft2CenterPoint(explosionCluster.impactPosition, explosionCluster.impactWidth, explosionCluster.impactLength, 0.0);
const LtsCoordTrans::UtmCoordinate &centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(center, impactPositionUtm.zone);
const LtsCoordTrans::UtmCoordinate &firingOriginUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(explosionCluster.firingCenterPosition.toPointF());
const int numberOfMTPsRequested = explosionCluster.mhpCountRequest;
Q_ASSERT(numberOfMTPsRequested > 0);
double singleWidthOut, singleHeightOut;
singleWidthOut = singleHeightOut = 0;
QVector<QPointF> mtps_asUtm;
//
// calculate the subAreas / mtps:
//
if (explosionCluster.explosionGeometryId.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial)
mtps_asUtm = explosionClusterRadialToMtps(areaBlWithTla, numberOfMTPsRequested, firingOriginUtm, centerUtm, &singleWidthOut, &singleHeightOut);
else
mtps_asUtm = explosionClusterAreaToMtps(areaBlWithTla, numberOfMTPsRequested, &singleWidthOut, &singleHeightOut);
if (mtps_asUtm.size() != numberOfMTPsRequested)
{
ERROR_LOG(logger) << "ERROR mismatch: numberOfMTPsRequested=" << numberOfMTPsRequested << ", mtps_asUtm.size=" << mtps_asUtm.size();
return {};
}
DEBUG_LOG(logger) << " singleWidthOut=" << singleWidthOut << ", singleHeightOut=" << singleHeightOut << ", mtps_asUtm" << mtps_asUtm;
QVector<LtsCoordTrans::UtmCoordinate> mtps_asRealUtm;
mtps_asRealUtm.reserve(mtps_asUtm.size());
for (const QPointF &mtp_asUtm : qAsConst(mtps_asUtm))
{
// convert QPointF back to UtmCoordinate: pay attention to order!!!
const LtsCoordTrans::UtmCoordinate mtpUtm(impactPositionUtm.zone, mtp_asUtm.x(), mtp_asUtm.y());
mtps_asRealUtm.push_back(mtpUtm);
}
// Bsp: 5 Impacts bei 3 MTPs
// 1st impact -> 1st MTP
// 2nd impact -> 2nd MTP
// 3rd impact -> 3rd MTP
// 4th impact -> 1st MTP
// 5th impact -> 2nd MTP
// ..
//
// !!! like TNE !!!
//
//
// !!! like TNE !!!
//
//
// !!! like TNE !!!
//
//
// like TNE calculate all impacts and move them alternating to the mtps:
//
// {
// singleWidthOut = 1;
// singleHeightOut = 1;
// }
const QVector<QPointF> &impactsGAUSS = TNE_ALGORITHMEN::areaExlosionsGAUSS_aroundCenter(randomNumber, singleWidthOut, singleHeightOut, impactCount); // breite , tiefe
QVector<QPointF> finalVecGeo;
finalVecGeo.reserve(impactsGAUSS.size());
int idx = 0;
const QTransform transform(fromRotation(-mtpRotationNorthDegree)); // Compass, not mathematical!
for (const QPointF &point : qAsConst(impactsGAUSS))
{
// which mtp:
int mtpIdx = (idx++ % mtps_asUtm.size()); // modulo
const LtsCoordTrans::UtmCoordinate &mtpRealUtm = mtps_asRealUtm[mtpIdx];
// const QPointF &p = mtpRealUtm.geographic(offset);
const QPointF &p = mtpRealUtm.geographic(transform.map(point)); // geographic -> geoCoord
finalVecGeo.push_back(p);
} // for
#ifdef NEVER
qCritical() << " TNE VERIFICATION: CHECK:"
<< ", randomNumber=" << randomNumber << " singleWidthOut=" << singleWidthOut << " singleHeightOut=" << singleHeightOut
<< " rot=" << explosionCluster.rotationAngle;
for (auto item : qAsConst(finalVec))
{
const LtsCoordTrans::UtmCoordinate &utm = LtsCoordTrans::UtmCoordinate::fromGeographic(item);
qCritical() << " TNE VERIFICATION: CHECK:"
<< " utm: " << utm.mgrs() << ", item=" << item;
}
#endif // NEVER
return finalVecGeo;
}
QVector<QPointF> explosionClusterAreaToMtps(const AreaBottomLeft &boundary, int numberOfMTPs, double *singleWidthOut, double *singleHeightOut)
{
if (!boundary.isValid() || numberOfMTPs <= 0)
{
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
FATAL_LOG(logger) << "ERROR, data invalid, numberOfMTPs=" << numberOfMTPs;
return {};
}
QVector<QPointF> vec = explosionClusterAreaToMtpsRelativeToBottomLeft(boundary, numberOfMTPs, singleWidthOut, singleHeightOut);
if (vec.size() != numberOfMTPs)
return {};
// move to desired location
QMutableVectorIterator<QPointF> it(vec);
while (it.hasNext())
{
QPointF &ref = it.next();
ref += boundary.m_cornerBottomLeft;
}
return vec;
}
QVector<QPointF> explosionClusterRadialToMtps(const AreaBottomLeft &boundary,
int numberOfMTPs,
const LtsCoordTrans::UtmCoordinate &firingPos,
const LtsCoordTrans::UtmCoordinate &circleCenter,
double *singleWidthOut,
double *singleHeightOut)
{
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
if (!boundary.isValid() || numberOfMTPs <= 0)
{
FATAL_LOG(logger) << "ERROR, data invalid, numberOfMTPs=" << numberOfMTPs;
return {};
}
QVector<QPointF> vec = explosionClusterRadialToMtpsRelativeToBottomLeft(boundary, numberOfMTPs, firingPos, circleCenter, singleWidthOut, singleHeightOut);
if (vec.size() != numberOfMTPs)
return {};
QMutableVectorIterator<QPointF> it(vec);
while (it.hasNext())
{
QPointF &ref = it.next();
ref += boundary.m_cornerBottomLeft;
}
return vec;
}
QVector<QPointF> explosionClusterAreaToMtpsRelativeToBottomLeft(const AreaBottomLeft &boundary, int numberOfMTPs, double *singleWidthOut, double *singleHeightOut)
{
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
if (!boundary.isValid() || numberOfMTPs <= 0)
{
FATAL_LOG(logger) << "ERROR, data invalid=";
return {};
}
// should also work for numberOfMTPs == 1
// ----------- ---
// | | | | he
// | x | x | x | ig
// | | | | th
// x----------- ---
// ^
// |____ cornerBottomLeft
//
// | width |
// | complWidth|
// ------------------------> x
// prepare:
const double completeWidth = boundary.m_width; // x axis, meter
const double singleWidth = completeWidth / numberOfMTPs; // x axis, meter
const QPointF &startPoint = QPointF(singleWidth / 2.0, boundary.m_height / 2.0);
// Build center line:
QVector<QPointF> poly;
poly.reserve(numberOfMTPs);
for (int i = 0; i < numberOfMTPs; ++i)
poly.push_back(startPoint + QPointF((i * singleWidth), 0));
// rotate
poly = QTransform().rotate(-boundary.m_rotation_deg).map(poly); // QTransform::rotate - counterclockwise
// set out values:
// Nachbilden der Schusstafel:
// - Höhe der MTP-Fläche nach dem Wirkfeld ausrichten
if (singleHeightOut)
*singleHeightOut = boundary.m_height;
// - Für die Breite quasi Höhe halbieren, aber maximal singleWidth: (std::min)
if (singleWidthOut)
*singleWidthOut = std::min(singleWidth, boundary.m_height / 2.0);
return poly;
}
QVector<QPointF> explosionClusterRadialToMtpsRelativeToBottomLeft(const AreaBottomLeft &boundary,
int numberOfMTPs,
const LtsCoordTrans::UtmCoordinate &firingPos,
const LtsCoordTrans::UtmCoordinate &circleCenter,
double *singleWidthOut,
double *singleHeightOut)
{
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
if (!boundary.isValid() || numberOfMTPs <= 0)
{
FATAL_LOG(logger) << "ERROR, data invalid=";
return {};
}
// prepare:
const double completeWidth = boundary.m_width; // x axis, meter
const double singleWidth = completeWidth / numberOfMTPs; // x axis, meter
const QPointF &startPoint = QPointF(singleWidth / 2.0, boundary.m_height / 2.0);
QLineF lineFromFiringPosToCenter = QLineF(QPointF(firingPos.x, firingPos.y), QPointF(circleCenter.x, circleCenter.y));
// Build center line:
QVector<QPointF> poly;
poly.reserve(numberOfMTPs);
for (int i = 0; i < numberOfMTPs; ++i)
poly.push_back(startPoint + QPointF(i * singleWidth, 0));
// rotate
double angle = boundary.m_rotation_deg + lineFromFiringPosToCenter.angle() + 90.0;
QTransform t;
t.translate(boundary.m_width / 2, boundary.m_height / 2.0);
t.rotate(-angle);
t.translate(-(boundary.m_width / 2), -(boundary.m_height / 2.0));
poly = t.map(poly);
// set out values:
// Nachbilden der Schusstafel:
// - Höhe der MTP-Fläche nach dem Wirkfeld ausrichten
if (singleHeightOut)
*singleHeightOut = boundary.m_height;
// - Für die Breite quasi Höhe halbieren, aber maximal singleWidth: (std::min)
if (singleWidthOut)
*singleWidthOut = std::min(singleWidth, boundary.m_height / 2.0);
return poly;
}
qint64 explosionClusterCalculatedFlightDuration2DMSec(const Database::AesExplosionCluster &explosionCluster, int timeOffsetMSec)
{
// Vorgabe ist:
// 1) peak
// 2) duration , LowOrHigh
// case 1 - flightDuration [msec]
if (explosionCluster.flightDuration > 0)
return explosionCluster.flightDuration + timeOffsetMSec; // msec
//
// case 2
//
const LtsCoordTrans::UtmCoordinate &impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(explosionCluster.impactPosition);
const LtsCoordTrans::UtmCoordinate &originUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(explosionCluster.firingCenterPosition.toPointF(), impactPositionUtm.zone);
const double distance = LtsCoordTrans::distance(impactPositionUtm, originUtm);
//
// On a planar area, the flightDuration ist independent from distance!!! See UnitTestTrajectoryHelper::test_calcDuration_diffrentDistances
//
// So we can use the impactPosition direct - adding max of impactLength and impactWidth is not helpful
//
// simple approach to get the max distance:
// // distance += std::max(explosionCluster.impactLength, explosionCluster.impactWidth);
// 2nd calculate flightduration
LtsUtils::QPoint3D start(0, 0, 0);
LtsUtils::QPoint3D end(distance, 0, 0);
qint64 durationMsec = TrajectoryHelper::flightDurationSec(start, end, explosionCluster.peakHeight) * 1000;
LOGGER_T logger = getLogger("Utils.ExplosionCluster");
DEBUG_LOG(logger) << " TrajectoryHelper::flightDurationSec: "
<< ", start = " << start.toPointF() << ", end = " << end.toPointF() << ", explosionCluster.peakHeight = " << explosionCluster.peakHeight
<< ", durationMsec = " << durationMsec;
return durationMsec + timeOffsetMSec;
}
QPolygonF mineCorridorSegment(const Database::AesMineCorridorSegment &start, const Database::AesMineCorridorSegment &end, double width)
{
LtsCoordTrans::UtmCoordinate startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(start.position);
LtsCoordTrans::UtmCoordinate endPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(end.position, startPositionUtm.zone);
QLineF lengthVector(QPointF(startPositionUtm.x, startPositionUtm.y), QPointF(endPositionUtm.x, endPositionUtm.y));
QPolygonF segment = QPolygonF({QPointF(0, width), QPointF(lengthVector.length(), width), QPointF(lengthVector.length(), 0), QPointF(0, 0)});
segment.translate(QPointF(0, -(width / 2)));
// LengthVector.angle() is mathematical!
segment = fromRotation(-lengthVector.angle()).map(segment);
return utmToGeogrphic(segment, startPositionUtm);
}
QPolygonF gasPipeSegment(const Database::AesGasPipeSegment &start, const Database::AesGasPipeSegment &end, double width)
{
LtsCoordTrans::UtmCoordinate startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(start.position);
LtsCoordTrans::UtmCoordinate endPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(end.position, startPositionUtm.zone);
QLineF lengthVector(QPointF(startPositionUtm.x, startPositionUtm.y), QPointF(endPositionUtm.x, endPositionUtm.y));
QPolygonF segment = QPolygonF({QPointF(0, width), QPointF(lengthVector.length(), width), QPointF(lengthVector.length(), 0), QPointF(0, 0)});
segment.translate(QPointF(0, -(width / 2)));
// LengthVector.angle() is mathematical!
segment = fromRotation(-lengthVector.angle()).map(segment);
return utmToGeogrphic(segment, startPositionUtm);
}
QPolygonF mineCorridor(const QVector<Database::AesMineCorridorSegment> &segments, double width)
{
QPolygonF overallPolygon;
if (segments.count() < 2)
return overallPolygon;
QPointF lasttopright, lastbottomright;
for (int i = 0; i < (segments.count() - 1); ++i)
{
QPolygonF segmentPoly = mineCorridorSegment(segments[i], segments[i + 1], width);
overallPolygon = overallPolygon.united(segmentPoly);
if (i > 0)
{
QPolygonF transitionPoly({segmentPoly[0], lasttopright, lastbottomright, segmentPoly[3]});
overallPolygon = overallPolygon.united(transitionPoly);
}
lasttopright = segmentPoly[1];
lastbottomright = segmentPoly[2];
}
DEBUG_LOG(getLogger("Utils.MineCorridor2")) << string4poly(overallPolygon);
return overallPolygon;
}
QPolygonF gasPipe(const QVector<Database::AesGasPipeSegment> &segments, double width)
{
QPolygonF overallPolygon;
if (segments.count() < 2)
return overallPolygon;
QPointF lasttopright, lastbottomright;
for (int i = 0; i < (segments.count() - 1); ++i)
{
QPolygonF segmentPoly = gasPipeSegment(segments[i], segments[i + 1], width);
overallPolygon = overallPolygon.united(segmentPoly);
if (i > 0)
{
QPolygonF transitionPoly({segmentPoly[0], lasttopright, lastbottomright, segmentPoly[3]});
overallPolygon = overallPolygon.united(transitionPoly);
}
lasttopright = segmentPoly[1];
lastbottomright = segmentPoly[2];
}
DEBUG_LOG(getLogger("Utils.gasPipe")) << string4poly(overallPolygon);
return overallPolygon;
}
QVector<QPolygonF> mineCorridorSingleSegments(const QVector<Database::AesMineCorridorSegment> &segments, double width)
{
QVector<QPolygonF> singleSegments;
if (segments.count() < 2)
return singleSegments;
singleSegments.reserve(segments.count() - 1);
for (int i = 0; i < (segments.count() - 1); ++i)
singleSegments << mineCorridorSegment(segments[i], segments[i + 1], width);
return singleSegments;
}
int getSequelNumber(const Database::AesExplosionArea &explosionArea)
{
const int impactCount = (explosionArea.cannonCount * explosionArea.cannonRounds);
if (impactCount <= 0 || explosionArea.effectStart.isNull())
return 0;
return getSequelNumber(explosionArea.id, impactCount, explosionArea.originalEffectDuration, explosionArea.effectDuration);
}
int getSequelNumber(int startSequelNumber, int impactCount, qint64 originalEffectDurationMs, qint64 effectDurationMs)
{
const double delta_t = double(originalEffectDurationMs) / impactCount; // msecs between impacts
const int impactsTriggered = impactCount - qRound(effectDurationMs / delta_t);
for (int i = 1; i < impactsTriggered; ++i)
startSequelNumber = (Math::AZ * startSequelNumber + Math::CZ) % Math::MZ;
return startSequelNumber;
}
QRectF getMinAreaRect(const QVector<QPointF> &points, double &outAngleDegrees)
{
const QVector<QPointF> hull = calculateConvexHull(points);
double minArea = std::numeric_limits<double>::max();
QRectF result;
for (int i = 0; i < hull.size(); ++i)
{
const QPointF p1 = hull[i];
const QPointF p2 = hull[(i + 1) % hull.size()];
// Winkel der aktuellen Kante
const double angle = std::atan2(p2.y() - p1.y(), p2.x() - p1.x());
const double cosA = std::cos(-angle);
const double sinA = std::sin(-angle);
double minX = std::numeric_limits<double>::max(), maxX = -minX;
double minY = std::numeric_limits<double>::max(), maxY = -minY;
// Projektion aller Punkte auf das rotierte Koordinatensystem
for (const auto &p : hull)
{
const double rx = p.x() * cosA - p.y() * sinA;
const double ry = p.x() * sinA + p.y() * cosA;
minX = std::min(minX, rx);
maxX = std::max(maxX, rx);
minY = std::min(minY, ry);
maxY = std::max(maxY, ry);
}
double area = (maxX - minX) * (maxY - minY);
if (area < minArea)
{
minArea = area;
result.setWidth(maxX - minX);
result.setHeight(maxY - minY);
outAngleDegrees = 360 - (angle * LtsCoordTrans::rad2deg());
// Rückrotation des "unteren linken" Punktes in Weltkoordinaten
const double blX = minX * std::cos(angle) - minY * std::sin(angle);
const double blY = minX * std::sin(angle) + minY * std::cos(angle);
result.moveBottomLeft(QPointF(blX, blY));
}
}
return result;
}
QVector<QPointF> calculateConvexHull(const QVector<QPointF> &points)
{
const int n = points.size();
if (n <= 2)
return points;
QVector<QPointF> pts = points;
std::sort(pts.begin(), pts.end(), [](const QPointF &a, const QPointF &b) { return a.x() < b.x() || (a.x() == b.x() && a.y() < b.y()); });
auto cross = [](const QPointF &o, const QPointF &a, const QPointF &b) { return (a.x() - o.x()) * (b.y() - o.y()) - (a.y() - o.y()) * (b.x() - o.x()); };
QVector<QPointF> hull;
for (int i = 0; i < n; ++i)
{
while (hull.size() >= 2 && cross(hull[hull.size() - 2], hull.last(), pts[i]) <= 0)
hull.pop_back();
hull.push_back(pts[i]);
}
for (int i = n - 2, t = hull.size() + 1; i >= 0; i--)
{
while (hull.size() >= t && cross(hull[hull.size() - 2], hull.last(), pts[i]) <= 0)
hull.pop_back();
hull.push_back(pts[i]);
}
hull.pop_back();
return hull;
}
QPointF bottomLeft2CenterPoint(QPointF bottomLeft, double width, double height, double rotation)
{
QRectF rect(QPointF(), QSizeF(width, height));
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
rect = fromRotation(-rotation).mapRect(rect);
LtsCoordTrans::UtmCoordinate bottomLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(bottomLeft);
return bottomLeftUtm.geographic(rect.center());
}
QPointF centerPoint2BottomLeft(QPointF centerPoint, double width, double height, double rotation)
{
QRectF rect(QPointF(), QSizeF(width, height));
rect.moveCenter(QPointF());
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
QPointF bl = fromRotation(-rotation).map(rect.topLeft());
LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerPoint);
return centerUtm.geographic(bl);
}
QPixmap pixmapFromPolygon(const QPolygonF &poly, const QColor &color, const int &width, const Qt::BrushStyle &fillPattern, bool isLine, const QColor &bgcolor)
{
const QTransform mTrans(QTransform::fromTranslate(-poly.boundingRect().bottomLeft().x(), -poly.boundingRect().bottomLeft().y()));
qreal w = poly.boundingRect().width();
qreal h = poly.boundingRect().height();
double scale = w > h ? width / w : width / h;
w *= scale;
h *= scale;
scale *= 0.96;
QPolygonF mPoly = mTrans.map(poly);
const QTransform mScale(QTransform::fromScale(scale, -scale));
mPoly = mScale.map(mPoly);
const QTransform mTransSmall(QTransform::fromTranslate(0.02 * width + (width - w) / 2, 0.02 * width + (width - h) / 2));
mPoly = mTransSmall.map(mPoly);
const auto dpr = qApp->devicePixelRatio(); // better would be to use the dpr from the widget where the pixmap drawn on later
const QSize s(QSize(width, width) * dpr);
QPixmap pix(s);
pix.fill(bgcolor);
pix.setDevicePixelRatio(dpr);
QPainter p(&pix);
QPen pen(color);
QBrush b(color, fillPattern);
pen.setWidth(2);
p.setPen(pen);
p.setBrush(b);
if (isLine)
{
QPainterPath path(mPoly.first());
for (int i = 1; i < mPoly.size(); ++i)
path.lineTo(mPoly.at(i));
p.drawPath(path);
}
else
p.drawPolygon(mPoly);
p.end();
return pix;
}
QPolygonF dangerArea(const QVector<QPointF> &segments)
{
QPolygonF overallPolygon(segments);
overallPolygon.append(segments.first());
DEBUG_LOG(getLogger("Utils.Danger Area")) << string4poly(overallPolygon);
return overallPolygon;
}
QPolygonF borderline(const QVector<QPointF> &segments)
{
QPolygonF overallPolygon(segments);
DEBUG_LOG(getLogger("Utils.borderline")) << string4poly(overallPolygon);
return overallPolygon;
}
QPolygonF gasPipe(const QVector<QPointF> &segments)
{
QPolygonF overallPolygon(segments);
DEBUG_LOG(getLogger("Utils.gasPipe")) << string4poly(overallPolygon);
return overallPolygon;
}
QVector<double> trajectory(const double &distance, const double &height, double angle, int steps)
{
const double coefLinear = tan(Math::deg2rad(angle));
const double coefSquare = (height - distance * coefLinear) / pow(distance, 2.);
const double delta = distance / steps;
double x = 0.;
QVector<double> y;
y.reserve(steps);
y << 0.0;
for (int i = 0; i < steps; ++i)
{
x += delta;
y << (coefLinear * x + coefSquare * pow(x, 2.));
} // for
return y;
}
QPolygonF offRouteMineArea(const Database::AesOffRouteMine &ea)
{
QPolygonF polyUtm = QPolygonF({QPointF(0, 0), QPointF(ea.width, 0), QPointF(ea.width, ea.length), QPointF(0, ea.length)});
// Compass, not mathematical! "90-" left out as Polygon already is rotated by 90 degree
polyUtm = fromRotation(-ea.rotationAngle).map(polyUtm);
LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(ea.impactPosition);
const QPolygonF poly(utmToGeogrphic(polyUtm, impactPositionUtm));
DEBUG_LOG(getLogger("Utils.AesOffRouteMine")) << string4poly(poly);
return poly;
}
QPolygonF offRouteMineAreaImpact(const Database::AesOffRouteMine &ea, int steps)
{
// aahhh, die Koordinatenumrechnung sollte OHNE MGRS-String durchgeführt werden....
// QPointF center= ea.impactPosition;
// // calculate center pos (middle of area width (define as legnth))
// {
// QPolygonF polyUtm = QPolygonF({QPointF(0,ea.width/2)});
// polyUtm = QTransform().rotate(-ea.rotationAngle).map(polyUtm);
// LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(ea.impactPosition);
// const QPolygonF poly(utmToGeogrphic(polyUtm, impactPositionUtm));
// QString mgrsCoord;
// if (!poly.isEmpty()){
// mgrsCoord = LtsCoordTrans::UtmCoordinate::fromGeographic(poly.at(0), LtsCoordTrans::UtmCoordinate::fromGeographic(poly.first()).zone).mgrs();
// center = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrsCoord).geographic();
// //out += mgrsCoord+QStringLiteral(" p(%1;%2) -- ").arg(centerPos.x()).arg(centerPos.y());
// }
// }
LtsCoordTrans::UtmCoordinate bottomLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(ea.impactPosition);
QTransform trans;
trans.rotate(-ea.rotationAngle); // QTransform::rotate - counterclockwise
// calculate cornerBottomLeft
QPointF point(ea.width / 2.0, 0);
point = trans.map(point); // point entsprechend rotieren
const QPointF &center = bottomLeftUtm.geographic(point); // offset :-)
// get polygon circle
QPolygonF poly;
{
qreal radius = ea.impactRadius;
LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(center);
if (steps < 6)
steps = 6;
poly.reserve(steps);
for (int i = 0; i <= 360; i += (360 / steps))
{
double angle = Math::compass2rad(double(i));
poly << centerUtm.geographic(QPointF(cos(angle) * radius, sin(angle) * radius));
}
}
return poly;
}
// getCircleSectorPolygon - For BoobyTraps
// copied from Lts/Viewer/SituationMap/MathUtils/simple_helper.cpp
QPolygonF getCircleSectorPolygon(const QPointF &centerPoint, qreal radius, double rotationAngleDeg, double centralAngleDeg)
{
// | <-rotationAngle
// | <StartPoint
//
// \ | /
// \ /
// \ | /
// \ /
rotationAngleDeg = 90 - rotationAngleDeg;
// rotate point with ~5 degree steps:
int steps = centralAngleDeg / 5.0;
if (steps < 3) // at least 3 steps
steps = 3;
const double singleIncrementDegree = centralAngleDeg / steps;
QPolygonF poly;
poly.reserve(steps + 1);
// CenterPunkt erstellen und einfügen;
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerPoint);
poly << centerUtm.geographic(QPointF(0, 0));
// Startwinkel:
const double angle = rotationAngleDeg - (centralAngleDeg / 2.0); // start angle ->.\ | /
const double angleRad = angle * LtsCoordTrans::deg2rad();
// relPosition des StartPoint (point on circle)
const QPointF position(cos(angleRad) * radius, sin(angleRad) * radius);
// StartPoint einfügen
poly << centerUtm.geographic(position);
// calculate all steps, inclusive EndPos
for (int step = 1; step <= steps; ++step)
{
double curAngle = angle + (step * singleIncrementDegree);
double curAngleRad = curAngle * LtsCoordTrans::deg2rad();
poly << centerUtm.geographic(QPointF(cos(curAngleRad) * radius, sin(curAngleRad) * radius));
} // for
return poly;
}
QPolygonF boobyTrapCake(const Database::AesBoobyTrap &boobyTrap, qreal radius)
{
// which type: fullCircle or circleSector
const bool bIsSector = (boobyTrap.centralAngle > 0);
if (bIsSector)
return getCircleSectorPolygon(boobyTrap.impactPosition, radius, boobyTrap.rotationAngle, boobyTrap.centralAngle);
// else circle
return circle(boobyTrap.impactPosition, radius);
}
LtsUtils::QPoint3D calculateFiringUnitCenterNonRecursive(const std::shared_ptr<ParticipantManagement::Interface::Dtc> &ppmDtc,
int unitParticipantId,
std::vector<Core::GeoPosition> &singlePositionsOut,
NGL::INGLLogger &logger)
{
TRACE_LOG(logger) << LOGFUNCTION << " START, participantId=" << unitParticipantId;
static const LtsUtils::QPoint3D s_invalidPos(std::nan(""), std::nan(""), std::nan(""));
// do this similar to UnitPositionCalculator, but use positions even if they are outdated:
// See also GUEZE-2309
if (!ppmDtc)
{
ERROR_LOG(logger) << LOGFUNCTION << " ERROR, no ppmDtc";
return s_invalidPos;
}
ppmDtc->waitForHistoricalDataParticipant(1000);
const ParticipantManagement::Participant &partData = ppmDtc->participant(unitParticipantId);
if (partData.id() != unitParticipantId)
{
ERROR_LOG(logger) << LOGFUNCTION << " ERROR, participant failed for id =" << unitParticipantId;
return s_invalidPos;
}
ppmDtc->waitForHistoricalDataParticipantPosition(1000);
const std::vector<int> &childIds = partData.inferiorIds();
singlePositionsOut.clear();
singlePositionsOut.reserve(childIds.size());
int utmZone = -1;
int childCount = 0;
LtsUtils::QPoint3D pos;
for (int childId : qAsConst(childIds))
{
const ParticipantManagement::ParticipantPosition &childPos = ppmDtc->participantPosition(childId);
if (childPos.participantId() != childId)
{
// may happen in real life: so ERROR_LOG to DEBUG_LOG
DEBUG_LOG(logger) << LOGFUNCTION << " ERROR, participantPosition failed for id =" << childId;
return s_invalidPos;
}
const Core::GeoPosition &childGeoPos = childPos.pos();
singlePositionsOut.emplace_back(childGeoPos);
const auto utm = LtsCoordTrans::UtmCoordinate::fromGeographic(childGeoPos.lon(), childGeoPos.lat(), utmZone);
if (utmZone == -1)
utmZone = utm.zone;
pos += LtsUtils::QPoint3D(utm.x, utm.y, childGeoPos.z());
++childCount;
} // for
LtsUtils::QPoint3D centerPosToReturn = s_invalidPos;
if (childCount > 0)
{
pos /= childCount;
const LtsCoordTrans::UtmCoordinate utm(utmZone, pos.x(), pos.y(), true); // northp_ = true ?? Süd-Halbkugel???
const QPointF lonLat = utm.geographic();
centerPosToReturn = LtsUtils::QPoint3D(lonLat.x(), lonLat.y(), pos.z());
}
DEBUG_LOG(logger) << "Participant " << unitParticipantId << ": Mean position: " << centerPosToReturn << " #relevant children: " << childCount;
TRACE_LOG(logger) << LOGFUNCTION << " END, participantId=" << unitParticipantId;
return centerPosToReturn;
}
qint64 explosionClusterEstimatedMaxFlightDuration2DMSec(qint64 calculatedFlightDuration, int calculationOffsetMSec)
{
return (calculatedFlightDuration * 1.1) + calculationOffsetMSec;
}
std::vector<Core::GeoPosition2D> dangerAreaDefaultSegments(const QPointF &centerPos, const int &radius)
{
QPolygonF polyUtm;
polyUtm.append(QPointF(-radius, radius)); // upper left
polyUtm.append(QPointF(-radius, -radius)); // lower left
polyUtm.append(QPointF(radius, -radius)); // lower right
polyUtm.append(QPointF(radius, radius)); // upper right
std::vector<Core::GeoPosition2D> segments;
segments.reserve(polyUtm.size());
// polyUtm = QTransform().rotate(0).map(polyUtm);
LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerPos);
const QPolygonF poly(utmToGeogrphic(polyUtm, impactPositionUtm));
for (const auto &p : qAsConst(poly))
segments.push_back(Core::GeoPosition2D(p.x(), p.y()));
return segments;
}
QVector<LtsCoordTrans::UtmCoordinate> geoVecToUtm(const std::vector<Core::GeoPosition2D> &vecPos, int utmZone)
{
QVector<LtsCoordTrans::UtmCoordinate> utmVec;
utmVec.reserve(vecPos.size());
for (const auto &pos : vecPos)
{
const auto utm = LtsCoordTrans::UtmCoordinate::fromGeographic(pos.lon(), pos.lat(), utmZone);
if (utmZone == -1)
utmZone = utm.zone;
utmVec.append(utm);
}
return utmVec;
}
QVector<QPointF> geoVecToUtmPointF(const std::vector<Core::GeoPosition2D> &vecPos, int utmZone)
{
QVector<QPointF> points;
points.reserve(vecPos.size());
for (const auto &pos : vecPos)
{
const auto utm = LtsCoordTrans::UtmCoordinate::fromGeographic(pos.lon(), pos.lat(), utmZone);
if (utmZone == -1)
utmZone = utm.zone;
points.append(QPointF(utm.x, utm.y));
}
return points;
}
} // Namespace Polygons
} // Namespace AreaEffectSimulation