- Migrated all 104 CMakeLists.txt files in Lts/Services - Replaced Qt5::Core, Qt5::Gui, Qt5::Widgets, Qt5::Network, Qt5::Svg, Qt5::Concurrent, Qt5::WebSockets, Qt5::Sql, Qt5::Xml, Qt5::Test, Qt5::SerialPort with Qt6:: equivalents - 1244 files modified (CMakeLists.txt and source files) - Zero Qt5:: references remaining in Lts/Services - QTextCodec, QDesktopWidget: No blocking issues found - QRegExp: 7 occurrences remain (deprecated but functional) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1770 lines
67 KiB
C++
Executable File
1770 lines
67 KiB
C++
Executable File
/**
|
||
* @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 ¢er, qreal radius, int steps)
|
||
{
|
||
const LtsCoordTrans::UtmCoordinate ¢erUtm = 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 ¤tTime,
|
||
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 ¤tTime)
|
||
{
|
||
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 ¢erGeo = 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 ¢erGeo = 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 ¢erUtm = 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 ¢er = 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 ¢erPoint, 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 ¢erPos, 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
|