/** * @RDE_HEADER@ * LTS - logging facility * $Id$ * $HeadURL$ * $Author$ * Created by Norbert Thoden, SLTE, 2023-11-08 */ #include "trajectory_generator.h" #include "trajectory_generator_vulcano.h" #include #include #include #include #ifdef NEVER // for csv file # include # include # include #endif // NEVER #include namespace AreaEffectSimulation { static TrajectoryGenerator *s_trajectoryGenerator = nullptr; TrajectoryGenerator::TrajectoryGenerator() : m_logger(getLogger(QStringLiteral("TrajectoryGenerator"))) { } TrajectoryGenerator *TrajectoryGenerator::getInstance(int exerciseId, const std::shared_ptr &exmDtc, const std::shared_ptr &asmDtc) { // Be aware: ONE TrajectoryGenerator per process, not per exercise // That feature has to be added! // In that case, have a look at LtsElevationManager::createInstance() and createInstanceForTrainingAreaId() if (!s_trajectoryGenerator) { LOGGER_T logger = getLogger(QStringLiteral("TrajectoryGenerator")); s_trajectoryGenerator = new TrajectoryGenerator(); qAddPostRoutine(&TrajectoryGenerator::cleanup); // assign LtsElevationManager: LtsElevation::LTS_ELEVATION_MANAGER_TYPE ltsElevationManager = LtsElevation::LtsElevationManager::createInstance(exerciseId, exmDtc, asmDtc); #ifndef LTS_DEBUG Q_ASSERT_X(ltsElevationManager, "LtsElevationManager", "createInstance failed"); #endif // LTS_DEBUG if (ltsElevationManager) { ltsElevationManager->waitForInitialization(); DEBUG_LOG(logger) << LOGFUNCTION << ", ltsElevationManager->waitForInitialization() ... DONE, isValid = " << ltsElevationManager->isValid(); } s_trajectoryGenerator->initElevation(ltsElevationManager); } return s_trajectoryGenerator; } TrajectoryGenerator *TrajectoryGenerator::getInstance() { if (!s_trajectoryGenerator) { s_trajectoryGenerator = new TrajectoryGenerator(); qAddPostRoutine(&TrajectoryGenerator::cleanup); } return s_trajectoryGenerator; } void TrajectoryGenerator::cleanup() { delete s_trajectoryGenerator; } void TrajectoryGenerator::initElevation(LtsElevation::LtsElevationManager *ltsElevationManager) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; m_ltsElevationManager = ltsElevationManager; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } LtsElevation::LtsElevationManager *TrajectoryGenerator::getElevationManager() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; return m_ltsElevationManager; } bool TrajectoryGenerator::hasElevationManager() const { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; return m_ltsElevationManager; } // Ideal ballistic curve, but with time values: bool TrajectoryGenerator::generateTrajectoryBiedziak(const LtsUtils::QPoint3D &originatorPosition_geo, const LtsUtils::QPoint3D &idealHit_geo, double elevation_angle_degree, TrajectoryGeoPosition &trajectoryOut) { return generateTrajectoryBiedziak>(originatorPosition_geo, idealHit_geo, elevation_angle_degree, trajectoryOut); } // // Vulcano // bool TrajectoryGenerator::generateTrajectoryVulcano(const QPoint3D &originatorPosition_geo, const QPoint3D &idealHit_geo, double v0, double betaRad, double alphaRad, double tapTimeSec, TrajectoryGeneratorVulcano::Options options, TrajectoryGeoPositionRelTime &trajectoryTimeAwareOut, Core::GeoPosition &idealTapAbsoluteGeoOut, QString *reasonOut) { LOGGER_T logger = getLogger(QStringLiteral("TrajectoryGenerator")); // use zone of idealHit: INFO_LOG(logger) << " orgPos geo " << originatorPosition_geo; INFO_LOG(logger) << " iHPos geo " << idealHit_geo; const LtsCoordTrans::UtmCoordinate &idealHitUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(idealHit_geo.toPointF()); const LtsCoordTrans::UtmCoordinate &originUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(originatorPosition_geo.toPointF(), idealHitUtm.zone); INFO_LOG(logger) << " originUtm " << originUtm.mgrs(); INFO_LOG(logger) << " idealHitUtm " << idealHitUtm.mgrs(); TRAJECTORY_METRIC trajectoryMetricOut; const QPoint3D origin_metric (originUtm.x, originUtm.y, originatorPosition_geo.z()); const QPoint3D idealHit_metric(idealHitUtm.x, idealHitUtm.y, idealHit_geo.z()); double vGlidingOut = 1; double vFallingOut = 1; TrajectoryGeneratorVulcano::getSpeedPerPhase(v0, betaRad, vGlidingOut, vFallingOut); QString localReasonOut; QPoint3D idealTapAbsoluteOut; const bool success = TrajectoryGeneratorVulcano::trajectoryVulcano(origin_metric, idealHit_metric, v0, betaRad, vGlidingOut, vFallingOut, alphaRad, tapTimeSec, options, trajectoryMetricOut, idealTapAbsoluteOut, &localReasonOut); if (!localReasonOut.isEmpty()) WARNING_LOG(logger) << " trajectoryVulcano failed, reasonOut = '" << localReasonOut << "'"; // // for documentation, it may be helpful to write the trajectoryMetric to a csv file // #ifdef NEVER { static QString filePath(QStringLiteral("/tmp/trajectory_dump.csv")); QFile data(filePath); QTextStream output(&data); if (data.open(QFile::WriteOnly | QIODevice::Append |QFile::Truncate)) { output.setRealNumberPrecision(3); output.setRealNumberNotation(QTextStream::RealNumberNotation::FixedNotation); output << "TrajectoryVulcano at: " << QDateTime::currentDateTime().toString(QStringLiteral("yyyy.MM.dd hh:mm::ss")) << "\n"; output << "v0: " << v0 << "\n"; output << "betaDeg: " << betaRad*LtsCoordTrans::rad2deg() << "\n"; output << "vGlidingOut: " << vGlidingOut << "\n"; output << "vFallingOut: " << vFallingOut << "\n"; output << "alphaDeg: " << alphaRad*LtsCoordTrans::rad2deg() << "\n"; output << "tapTimeSec: " << tapTimeSec << "\n"; const double distance = (origin_metric - idealHit_metric).length(); output << "distance: " << distance << "\n"; output << "x_rel ; y_rel ; z ; relativeTime" << "\n"; if (!trajectoryMetricOut.isEmpty()) { QPoint4D startPoint = trajectoryMetricOut.first(); for (const QPoint4D &p4D : qAsConst(trajectoryMetricOut)) { output << p4D.x() - startPoint.x() << ";" << p4D.y() - startPoint.y() << ";" << p4D.z() << ";" << p4D.time() << ";" << "\n"; } // for } } } #endif // NEVER // // // // convert metric to geo/wgs84 { LtsCoordTrans::UtmCoordinate tapUtm(idealHitUtm.zone, idealTapAbsoluteOut.x(), idealTapAbsoluteOut.y()); const QPointF &tapGeo = tapUtm.geographic(); idealTapAbsoluteGeoOut = Core::GeoPosition(tapGeo.x(), tapGeo.y(), idealTapAbsoluteOut.z()); } trajectoryTimeAwareOut.clear(); trajectoryTimeAwareOut.reserve(trajectoryMetricOut.size()); for (const QPoint4D& pnt4d : qAsConst(trajectoryMetricOut)) { LtsCoordTrans::UtmCoordinate pntUtm(idealHitUtm.zone, pnt4d.x(), pnt4d.y()); const QPointF &pntGeo = pntUtm.geographic(); // !!! relativeTime is in msec!!! trajectoryTimeAwareOut.emplace_back( Core::GeoPositionRelativeTime(pntGeo.x(), pntGeo.y(), pnt4d.z(), pnt4d.time()*1000.0) ); } if (reasonOut) *reasonOut = localReasonOut; return success; } // // real implementation // inline void addItemTo(std::vector &cont, double x, double y, double z) { cont.emplace_back(Core::GeoPosition(x, y, z)); } template bool TrajectoryGenerator::generateTrajectoryBiedziak(const LtsUtils::QPoint3D &originatorPosition_geo, const LtsUtils::QPoint3D &impactPosition_geo, double elevation_angle_degree, CONT_T &trajectoryOut) { // use zone of impact: INFO_LOG(m_logger) << " oPosition geo " << originatorPosition_geo.x() << " - " << originatorPosition_geo.y(); INFO_LOG(m_logger) << " iPosition geo " << impactPosition_geo.x() << " - " << impactPosition_geo.y(); const LtsCoordTrans::UtmCoordinate &iPosition = LtsCoordTrans::UtmCoordinate::fromGeographic(impactPosition_geo.toPointF()); const LtsCoordTrans::UtmCoordinate &oPosition = LtsCoordTrans::UtmCoordinate::fromGeographic(originatorPosition_geo.toPointF(), iPosition.zone); INFO_LOG(m_logger) << " oPosition " << oPosition.mgrs(); INFO_LOG(m_logger) << " iPosition " << iPosition.mgrs(); const double distance = sqrt(pow(iPosition.x - oPosition.x, 2.) + pow(iPosition.y - oPosition.y, 2.)); double oHeight = originatorPosition_geo.z(); double iHeight = impactPosition_geo.z(); if (std::isnan(oHeight) && m_ltsElevationManager) { oHeight = m_ltsElevationManager ? m_ltsElevationManager->elevation(originatorPosition_geo.x(), originatorPosition_geo.y()) : 0; if (oHeight < 300) oHeight = 100; INFO_LOG(m_logger) << " oPosition " << oPosition << " has no zValue provided (NaN), update via ltsElevationManager..."; } if (std::isnan(iHeight) && m_ltsElevationManager) { iHeight = m_ltsElevationManager ? m_ltsElevationManager->elevation(impactPosition_geo.x(), impactPosition_geo.y()) : 0; if (iHeight < 300) iHeight = 100; INFO_LOG(m_logger) << " iPosition " << iPosition << " has no zValue provided (NaN), update via ltsElevationManager.. "; } INFO_LOG(m_logger) << " oPosition " << oPosition << " ; oHeight=" << oHeight; INFO_LOG(m_logger) << " iPosition " << iPosition << " ; iHeight=" << iHeight; // z-Values: const QVector &zValues = this->trajectoryBiedziak(distance, iHeight - oHeight, elevation_angle_degree); if (zValues.isEmpty()) return false; trajectoryOut.clear(); const QPointF &oPositionGeo = oPosition.geographic(); addItemTo(trajectoryOut, oPositionGeo.x(), oPositionGeo.y(), oHeight); const QPointF &step = (QPointF(iPosition.x, iPosition.y) - QPointF(oPosition.x, oPosition.y)) / (zValues.size() - 1); QPointF runner; for (int i = 1; i < zValues.size(); ++i) { runner += step; const QPointF &p2D = oPosition.geographic(runner); addItemTo(trajectoryOut, p2D.x(), p2D.y(), oHeight + zValues[i]); // x, y, z } // for //// DEBUG_LOG(m_logger) << string4poly(geo); return true; } QVector TrajectoryGenerator::trajectoryBiedziak(const double &distance, const double &heightOffset, double elevation_angle_degree, int steps) { const double coefLinear = tan(elevation_angle_degree * LtsCoordTrans::deg2rad()); const double coefSquare = (heightOffset - distance * coefLinear) / pow(distance, 2.0); const double delta = distance / steps; double x = 0.; QVector yValues; // later interpreted as z-Values yValues.reserve(steps); yValues << 0.0; for (int i = 0; i < steps; ++i) { x += delta; yValues << (coefLinear * x + coefSquare * pow(x, 2.)); } // for return yValues; } const QString TrajectoryGenerator::string4poly(const QPair > &pair) { if (pair.first.isEmpty()) return (QStringLiteral("Empty Polygon!")); int zone = LtsCoordTrans::UtmCoordinate::fromGeographic(pair.first[0]).zone; QStringList pointSl; pointSl.reserve(pair.first.size()); for (int i = 0; i < pair.first.size(); ++i) pointSl << QString(QStringLiteral("utm=%1 h=%2")).arg(LtsCoordTrans::UtmCoordinate::fromGeographic(pair.first[i], zone).mgrs(), QString::number(pair.second[i], 'f', 1)); return QStringLiteral("%1 Values (%2)").arg(pair.first.count()).arg(pointSl.join(QStringLiteral(" # "))); } //const QString TrajectoryGenerator::string4poly(const TrajectoryPoint3D &trajectory) //{ // if (trajectory.isEmpty()) // return (QStringLiteral("Empty Polygon!")); // const Point3D &p3D = trajectory.first(); // int zone = LtsCoordTrans::UtmCoordinate::fromGeographic(p3D.toPointF()).zone; // QStringList pointSl; // pointSl.reserve(trajectory.size()); // for (int i = 0; i < trajectory.size(); ++i) // pointSl << QString(QStringLiteral("utm=%1 h=%2")).arg(LtsCoordTrans::UtmCoordinate::fromGeographic(p3D.toPointF(), zone).mgrs(), QString::number(p3D.z(), 'f', 1)); // return QStringLiteral("%1 Values (%2)").arg(trajectory.count()).arg(pointSl.join(QStringLiteral(" # "))); //} } // namespace AreaEffectSimulation