/** * @RDE_HEADER@ * $Id$ * $HeadURL$ * $Author$ */ #include "polygons.h" #include "tne_algorithmen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // // #define NEVER // Note: All polygones are open! std::ostream &operator<<(std::ostream &stream, const QVector &value) { stream << "QVector(" << value.size() << "): "; for (const QPointF &item : value) stream << item << ";"; return stream; } std::ostream &operator<<(std::ostream &stream, const QVector &value) { stream << "QVector(" << value.size() << "): "; for (const LtsCoordTrans::UtmCoordinate &item : value) stream << item << ";"; return stream; } namespace AreaEffectSimulation { namespace Math { template static inline T compass2rad(T degree) { static_assert(!std::is_integral::value, "Floating Point Value Required! Integer may be in Centidegree?!?"); return (M_PI / 2.0) - (degree * M_PI / 180.0); } template static inline T rad2compass(T rad) { return 90.0 - (rad * 180.0 / M_PI); } template static inline T deg2rad(T degree) { static_assert(!std::is_integral::value, "Floating Point Value Required! Integer may be in Centidegree?!?"); return degree * M_PI / 180.0; } template 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> &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 effectClusterSingleExplosionsGAUSS(const Database::AesEffectCluster &effectCluster) { QVector 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 &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 effectClusterSingleExplosionsGAUSS_Mtp(const Database::AesEffectCluster &effectCluster, const QHash &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 &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 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 &impactsGAUSS = TNE_ALGORITHMEN::areaExlosionsGAUSS_aroundCenter(randomNumber, singleWidthOut, singleHeightOut, impactCount); // breite , tiefe QVector 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 mineAreaSingleSegments(const QVector &segments, double width, bool bIsCenterLine) { QVector singleSegments; singleSegments.reserve(segments.size()); for (const Database::AesMineAreaSegment &segment : segments) singleSegments.push_back(mineAreaSegment(segment, width, bIsCenterLine)); return singleSegments; } QPolygonF mineArea(const QVector &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 minePositions(const Database::AesMineArea &area, const QVector &segments) { QVector vec; if (segments.isEmpty()) return vec; int totalMinesYet = 0; // Summe der Minen in den Segmenten 1 - (i-1) quint16 randomNumber = static_cast(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 explosionAreaSingleExplosions(const Database::AesExplosionArea &explosionArea) { QVector 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 explosionClusterSingleExplosionsGAUSS(const Database::AesExplosionCluster &explosionCluster) { QVector 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 &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 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 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 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 &impactsGAUSS = TNE_ALGORITHMEN::areaExlosionsGAUSS_aroundCenter(randomNumber, singleWidthOut, singleHeightOut, impactCount); // breite , tiefe QVector 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 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 vec = explosionClusterAreaToMtpsRelativeToBottomLeft(boundary, numberOfMTPs, singleWidthOut, singleHeightOut); if (vec.size() != numberOfMTPs) return {}; // move to desired location QMutableVectorIterator it(vec); while (it.hasNext()) { QPointF &ref = it.next(); ref += boundary.m_cornerBottomLeft; } return vec; } QVector 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 vec = explosionClusterRadialToMtpsRelativeToBottomLeft(boundary, numberOfMTPs, firingPos, circleCenter, singleWidthOut, singleHeightOut); if (vec.size() != numberOfMTPs) return {}; QMutableVectorIterator it(vec); while (it.hasNext()) { QPointF &ref = it.next(); ref += boundary.m_cornerBottomLeft; } return vec; } QVector 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 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 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 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 &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 &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 mineCorridorSingleSegments(const QVector &segments, double width) { QVector 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 &points, double &outAngleDegrees) { const QVector hull = calculateConvexHull(points); double minArea = std::numeric_limits::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::max(), maxX = -minX; double minY = std::numeric_limits::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 calculateConvexHull(const QVector &points) { const int n = points.size(); if (n <= 2) return points; QVector 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 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 &segments) { QPolygonF overallPolygon(segments); overallPolygon.append(segments.first()); DEBUG_LOG(getLogger("Utils.Danger Area")) << string4poly(overallPolygon); return overallPolygon; } QPolygonF borderline(const QVector &segments) { QPolygonF overallPolygon(segments); DEBUG_LOG(getLogger("Utils.borderline")) << string4poly(overallPolygon); return overallPolygon; } QPolygonF gasPipe(const QVector &segments) { QPolygonF overallPolygon(segments); DEBUG_LOG(getLogger("Utils.gasPipe")) << string4poly(overallPolygon); return overallPolygon; } QVector 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 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 // | .\ | / 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 &ppmDtc, int unitParticipantId, std::vector &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 &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 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 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 geoVecToUtm(const std::vector &vecPos, int utmZone) { QVector 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 geoVecToUtmPointF(const std::vector &vecPos, int utmZone) { QVector 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