From def51576de88c44596b6ccd8aa424893d3330775 Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Tue, 24 Jan 2017 20:58:54 +0100 Subject: [PATCH] refs #865, added log functions to interpolator * updated HTML template * parts use now string builder --- resources/share/html/swifttemplate.html | 10 +- src/blackmisc/aviation/aircraftparts.cpp | 25 +-- src/blackmisc/simulation/interpolator.cpp | 198 +++++++++++++++++++++- src/blackmisc/simulation/interpolator.h | 69 +++++++- 4 files changed, 279 insertions(+), 23 deletions(-) diff --git a/resources/share/html/swifttemplate.html b/resources/share/html/swifttemplate.html index 035dc0f92..13c99b636 100644 --- a/resources/share/html/swifttemplate.html +++ b/resources/share/html/swifttemplate.html @@ -9,7 +9,7 @@ body { background-color: white; font-family: arial; - font-size: 12px; + font-size: 0.75em; } tr:nth-child(even) { background-color: #c0c0c0; @@ -23,6 +23,9 @@ th { color:#fff; white-space: nowrap; } +.small { + font-size: 0.5em; +} .mouseoverdisplay { background-color: #eeeeee; position: absolute; @@ -35,6 +38,11 @@ th { a:hover + .mouseoverdisplay { display: block; } + +/** classes for interpolation log **/ +.old { color: blue; } +.new { color: green; } +.cur { color: red; } diff --git a/src/blackmisc/aviation/aircraftparts.cpp b/src/blackmisc/aviation/aircraftparts.cpp index 42cd607c0..77dd0e21d 100644 --- a/src/blackmisc/aviation/aircraftparts.cpp +++ b/src/blackmisc/aviation/aircraftparts.cpp @@ -11,6 +11,7 @@ #include "blackmisc/comparefunctions.h" #include "blackmisc/stringutils.h" +#include "QStringBuilder" #include using namespace BlackMisc; @@ -21,18 +22,18 @@ namespace BlackMisc { QString CAircraftParts::convertToQString(bool i18n) const { - QString s; - s += m_lights.toQString(i18n); - s += " gear down: "; - s += BlackMisc::boolToYesNo(m_gearDown); - s += " flaps pct: "; - s += QString::number(m_flapsPercentage); - s += " spoilers out: "; - s += BlackMisc::boolToYesNo(m_spoilersOut); - s += " engines on: "; - s += m_engines.toQString(i18n); - s += " on ground: "; - s += BlackMisc::boolToYesNo(m_isOnGround); + const QString s = + m_lights.toQString(i18n) % + " gear down: " % + BlackMisc::boolToYesNo(m_gearDown) % + " flaps pct: " % + QString::number(m_flapsPercentage) % + " spoilers out: " % + BlackMisc::boolToYesNo(m_spoilersOut) % + " engines on: " % + m_engines.toQString(i18n) % + " on ground: " % + BlackMisc::boolToYesNo(m_isOnGround); return s; } diff --git a/src/blackmisc/simulation/interpolator.cpp b/src/blackmisc/simulation/interpolator.cpp index 471072338..3ac068257 100644 --- a/src/blackmisc/simulation/interpolator.cpp +++ b/src/blackmisc/simulation/interpolator.cpp @@ -8,11 +8,18 @@ */ #include "interpolator.h" +#include "blackconfig/buildconfig.h" #include "blackmisc/simulation/interpolationhints.h" #include "blackmisc/aviation/callsign.h" #include "blackmisc/pq/length.h" +#include "blackmisc/logmessage.h" +#include "blackmisc/worker.h" +#include "blackmisc/directoryutils.h" #include +#include +using namespace BlackConfig; +using namespace BlackMisc; using namespace BlackMisc::Aviation; using namespace BlackMisc::PhysicalQuantities; @@ -28,6 +35,9 @@ namespace BlackMisc this->setObjectName(objectName); } + IInterpolator::~IInterpolator() + { } + BlackMisc::Aviation::CAircraftSituation IInterpolator::getInterpolatedSituation( const CCallsign &callsign, qint64 currentTimeSinceEpoc, const CInterpolationHints &hints, InterpolationStatus &status) const @@ -37,15 +47,14 @@ namespace BlackMisc status.reset(); Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign"); - auto currentSituation = this->getInterpolatedSituation(this->remoteAircraftSituations(callsign), currentTimeSinceEpoc, hints, status); + auto currentSituation = this->getInterpolatedSituation(callsign, this->remoteAircraftSituations(callsign), currentTimeSinceEpoc, hints, status); currentSituation.setCallsign(callsign); // make sure callsign is correct return currentSituation; } - CAircraftParts IInterpolator::getInterpolatedParts(const CAircraftPartsList &parts, qint64 currentTimeMsSinceEpoch, IInterpolator::PartsStatus &partsStatus) const + CAircraftParts IInterpolator::getInterpolatedParts(const CCallsign &callsign, const CAircraftPartsList &parts, qint64 currentTimeMsSinceEpoch, IInterpolator::PartsStatus &partsStatus) const { partsStatus.reset(); - if (currentTimeMsSinceEpoch < 0) { currentTimeMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); } // find the first parts not in the correct order, keep only the parts before that one @@ -70,7 +79,9 @@ namespace BlackMisc const auto soonestLanding = std::find_if(partsNewer.begin(), partsNewer.end(), [](auto && p) { return p.isOnGround(); }); // our clairvoyance is limited by the time offset - const double significantPast = 5.0; // \fixme 20170121 KB would it make sense to centrally define the update time (5secs), in case it changes. I see a lot of 5.0 hardcoded here + const double significantPast = 5.0; + // \fixme 20170121 KB would it make sense to centrally define the update time (5secs), in case it changes. I see a lot of 5.0 hardcoded here + // I also wonder if the time is const (interim updates) const double predictableFuture = soonestLanding == partsNewer.end() ? 5.0 : std::min(5.0, static_cast(soonestLanding->getTimeOffsetMs()) / 1000.0); const double secondsSinceTakeoff = latestTakeoff == partsOlder.end() ? 5.0 : (currentTimeMsSinceEpoch - latestTakeoff->getAdjustedMSecsSinceEpoch()) / 1000.0; @@ -82,6 +93,17 @@ namespace BlackMisc const double landingFactor = secondsUntilLanding / predictableFuture; const double airborneFactor = std::min(std::min(takeoffFactor, landingFactor), 1.0); currentParts.setOnGroundInterpolated(1.0 - smootherStep(airborneFactor)); + + + const CInterpolationAndRenderingSetup setup = this->getInterpolatorSetup(); + if (setup.getLogCallsigns().contains(callsign)) + { + PartsLog log; + log.timestamp = currentTimeMsSinceEpoch; + log.parts = currentParts; + IInterpolator::logParts(log); + } + return currentParts; } @@ -92,21 +114,171 @@ namespace BlackMisc partsStatus.setSupportsParts(this->isRemoteAircraftSupportingParts(callsign)); if (!partsStatus.isSupportingParts()) { return {}; } - return this->getInterpolatedParts(this->remoteAircraftParts(callsign, -1), currentTimeMsSinceEpoch, partsStatus); + return this->getInterpolatedParts(callsign, this->remoteAircraftParts(callsign, -1), currentTimeMsSinceEpoch, partsStatus); } void IInterpolator::setInterpolatorSetup(const CInterpolationAndRenderingSetup &setup) { - QWriteLocker l(&m_lock); + QWriteLocker l(&m_lockSetup); m_setup = setup; } + CWorker *IInterpolator::writeLogInBackground() + { + // make sure logging is stopped + { + QWriteLocker l(&m_lockSetup); + m_setup.clearInterpolatorLogCallsigns(); + } + + QList interpolation; + QList parts; + { + QReadLocker l(&m_lockLogs); + interpolation = m_interpolationLogs; + parts = m_partsLogs; + } + + CWorker *worker = CWorker::fromTask(this, "WriteInterpolationLog", [interpolation, parts]() + { + const CStatusMessage msg = IInterpolator::writeLogFile(interpolation, parts); + CLogMessage::preformatted(msg); + }); + return worker; + } + + CStatusMessage IInterpolator::writeLogFile(const QList &interpolation, const QList &parts) + { + if (parts.isEmpty() && interpolation.isEmpty()) { return CStatusMessage(static_cast(nullptr)).warning("No data for log"); } + const QString htmlInterpolation = IInterpolator::getHtmlInterpolationLog(interpolation); + const QString htmlParts = IInterpolator::getHtmlPartsLog(parts); + const QString html = htmlParts % QLatin1Literal("\n\n") % htmlInterpolation; + const QString htmlTemplate = CFileUtils::readFileToString(CBuildConfig::getHtmlTemplateFileName()); + + const QString ts = QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmss"); + const QString fn = CFileUtils::appendFilePaths(CDirectoryUtils::getLogDirectory(), QString("%1 interpolation.html").arg(ts)); + const bool s = CFileUtils::writeStringToFile(htmlTemplate.arg(html), fn); + if (s) + { + return CStatusMessage(static_cast(nullptr)).info("Written log file '%1'") << fn; + } + else + { + return CStatusMessage(static_cast(nullptr)).error("Failed to write log file '%1'") << fn; + } + } + CInterpolationAndRenderingSetup IInterpolator::getInterpolatorSetup() const { - QReadLocker l(&m_lock); + QReadLocker l(&m_lockSetup); return m_setup; } + void IInterpolator::logInterpolation(const IInterpolator::InterpolationLog &log) const + { + QWriteLocker l(&m_lockLogs); + m_interpolationLogs.append(log); + } + + void IInterpolator::logParts(const IInterpolator::PartsLog &parts) const + { + QWriteLocker l(&m_lockLogs); + m_partsLogs.append(parts); + } + + QString IInterpolator::getHtmlInterpolationLog(const QList &logs) + { + if (logs.isEmpty()) { return {}; } + QString tableRows; + const QString tableHeader = + QLatin1Literal("") % + QLatin1Literal("CSVTOLtimestamp") % + QLatin1Literal("ts oldts newts cur") % + QLatin1Literal("ΔtΔt fr.fraction") % + QLatin1Literal("lat.oldlat.newlat.cur") % + QLatin1Literal("lng.oldlng.newlng.cur") % + QLatin1Literal("alt.oldalt.newalt.cur") % + QLatin1Literal("elv.oldelv.newelv.cur") % + QLatin1Literal("gnd.factor") % + QLatin1Literal("onGnd.oldonGnd.newonGnd.cur") % + QLatin1Literal("\n"); + + static const CLengthUnit ft = CLengthUnit::ft(); + for (const InterpolationLog &log : logs) + { + // concatenating in multiple steps, otherwise C4503 warnings + tableRows += + QLatin1Literal("") % + QLatin1Literal("") % log.callsign.asString() % QLatin1Literal("") % + QLatin1Literal("") % boolToYesNo(log.vtolAircraft) % QLatin1Literal("") % + QLatin1Literal("") % msSinceEpochToTime(log.timestamp) % QLatin1Literal("") % + + QLatin1Literal("") % msSinceEpochToTime(log.oldSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.oldSituation.getTimeOffsetMs()) % QLatin1Literal("") % + QLatin1Literal("") % msSinceEpochToTime(log.newSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.newSituation.getTimeOffsetMs()) % QLatin1Literal("") % + QLatin1Literal("") % msSinceEpochToTime(log.currentSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.currentSituation.getTimeOffsetMs()) % QLatin1Literal("") % + + QLatin1Literal("") % QString::number(log.deltaTimeMs) % QLatin1Literal("") % + QLatin1Literal("") % QString::number(log.deltaTimeFractionMs) % QLatin1Literal("") % + QLatin1Literal("") % QString::number(log.simulationTimeFraction) % QLatin1Literal(""); + + tableRows += + QLatin1Literal("") % log.oldSituation.latitudeAsString() % QLatin1Literal("") % + QLatin1Literal("") % log.newSituation.latitudeAsString() % QLatin1Literal("") % + QLatin1Literal("") % log.currentSituation.latitudeAsString() % QLatin1Literal("") % + + QLatin1Literal("") % log.oldSituation.longitudeAsString() % QLatin1Literal("") % + QLatin1Literal("") % log.newSituation.longitudeAsString() % QLatin1Literal("") % + QLatin1Literal("") % log.currentSituation.longitudeAsString() % QLatin1Literal(""); + + tableRows += + QLatin1Literal("") % log.oldSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1Literal("") % + QLatin1Literal("") % log.newSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1Literal("") % + QLatin1Literal("") % log.currentSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1Literal("") % + + QLatin1Literal("") % log.oldSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1Literal("") % + QLatin1Literal("") % log.newSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1Literal("") % + QLatin1Literal("") % log.currentSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1Literal("") % + + QLatin1Literal("") % QString::number(log.groundFactor) % QLatin1Literal("") % + QLatin1Literal("") % log.oldSituation.getOnGroundInfo() % QLatin1Literal("") % + QLatin1Literal("") % log.newSituation.getOnGroundInfo() % QLatin1Literal("") % + QLatin1Literal("") % log.currentSituation.getOnGroundInfo() % QLatin1Literal("") % + QLatin1Literal("\n"); + } + + return QLatin1Literal("\n") % tableHeader % tableRows % QLatin1Literal("
\n"); + } + + QString IInterpolator::getHtmlPartsLog(const QList &logs) + { + if (logs.isEmpty()) { return {}; } + QString tableRows; + const QString tableHeader = + QLatin1Literal("") % + QLatin1Literal("CStimestamp") % + QLatin1Literal("parts") % + QLatin1Literal("\n"); + + for (const PartsLog &log : logs) + { + // concatenating in multiple steps, otherwise C4503 warnings + tableRows += + QLatin1Literal("") % + QLatin1Literal("") % log.callsign.asString() % QLatin1Literal("") % + QLatin1Literal("") % msSinceEpochToTime(log.timestamp) % QLatin1Literal("") % + QLatin1Literal("") % log.parts.toQString() % QLatin1Literal(""); + } + + return QLatin1Literal("\n") % tableHeader % tableRows % QLatin1Literal("
\n"); + } + + void IInterpolator::clearLog() + { + QWriteLocker l(&m_lockLogs); + this->m_partsLogs.clear(); + this->m_interpolationLogs.clear(); + } + void IInterpolator::setGroundElevationFromHint(const CInterpolationHints &hints, CAircraftSituation &situation) { if (situation.hasGroundElevation()) { return; } @@ -157,6 +329,18 @@ namespace BlackMisc situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); } + QString IInterpolator::msSinceEpochToTime(qint64 ms) + { + static const QString dateFormat("hh:mm:ss.zzz"); + return QDateTime::fromMSecsSinceEpoch(ms).toString(dateFormat); + } + + QString IInterpolator::msSinceEpochToTime(qint64 t1, qint64 t2, qint64 t3) + { + if (t3 < 0) return QString("%1 %2").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2)); + return QString("%1 %2 %3").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2), msSinceEpochToTime(t3)); + } + bool IInterpolator::InterpolationStatus::allTrue() const { return m_interpolationSucceeded && m_changedPosition; diff --git a/src/blackmisc/simulation/interpolator.h b/src/blackmisc/simulation/interpolator.h index ab8cb532f..7c9ab257b 100644 --- a/src/blackmisc/simulation/interpolator.h +++ b/src/blackmisc/simulation/interpolator.h @@ -16,6 +16,7 @@ #include "blackmisc/blackmiscexport.h" #include "blackmisc/aviation/aircraftpartslist.h" #include "blackmisc/aviation/aircraftsituation.h" +#include "blackmisc/aviation/aircraftpartslist.h" #include "blackmisc/simulation/remoteaircraftprovider.h" #include @@ -24,6 +25,7 @@ namespace BlackMisc { + class CWorker; namespace Aviation { class CCallsign; } namespace Simulation { @@ -38,7 +40,7 @@ namespace BlackMisc public: //! Virtual destructor - virtual ~IInterpolator() {} + virtual ~IInterpolator(); //! Log category static QString getLogCategory() { return "swift.interpolator"; } @@ -121,6 +123,12 @@ namespace BlackMisc //! \threadsafe void setInterpolatorSetup(const CInterpolationAndRenderingSetup &setup); + //! Write a log in background + BlackMisc::CWorker *writeLogInBackground(); + + //! Clear log file + void clearLog(); + /*! * Takes input between 0 and 1 and returns output between 0 and 1 smoothed with an S-shaped curve. * @@ -134,6 +142,29 @@ namespace BlackMisc } protected: + //! Log for interpolation + struct InterpolationLog + { + qint64 timestamp = -1; //!< current timestamp + double groundFactor = -1; //!< current ground factor + double vtolAircraft = false; //!< VTOL aircraft + double deltaTimeMs = 0; //!< delta time to last situation + double simulationTimeFraction = -1; //!< time fraction, normally 0..1 + double deltaTimeFractionMs = -1; //!< delta time fraction + BlackMisc::Aviation::CCallsign callsign; //!< current callsign + BlackMisc::Aviation::CAircraftSituation oldSituation; //!< old situation + BlackMisc::Aviation::CAircraftSituation newSituation; //!< new situation + BlackMisc::Aviation::CAircraftSituation currentSituation; //!< interpolated situation + }; + + //! Log for parts + struct PartsLog + { + qint64 timestamp = -1; //!< current timestamp + BlackMisc::Aviation::CCallsign callsign; //!< current callsign + BlackMisc::Aviation::CAircraftParts parts; //!< parts to be logged + }; + //! Constructor IInterpolator(BlackMisc::Simulation::IRemoteAircraftProvider *provider, const QString &objectName, QObject *parent); @@ -141,14 +172,46 @@ namespace BlackMisc //! \threadsafe CInterpolationAndRenderingSetup getInterpolatorSetup() const; + //! Log interpolation, only stores in memory, for performance reasons + //! \remark const to allow const interpolator functions + //! \threadsafe + void logInterpolation(const InterpolationLog &log) const; + + //! Log parts, only stores in memory, for performance reasons + //! \remark const to allow const interpolator functions + //! \threadsafe + void logParts(const PartsLog &parts) const; + + //! Get log as HTML table + //! \threadsafe + static QString getHtmlInterpolationLog(const QList &logs); + + //! Get log as HTML table + //! \threadsafe + static QString getHtmlPartsLog(const QList &logs); + //! Set the ground elevation from hints, if possible and not already set static void setGroundElevationFromHint(const CInterpolationHints &hints, BlackMisc::Aviation::CAircraftSituation &situation); //! Set on ground flag static void setGroundFlagFromInterpolator(const CInterpolationHints &hints, double groundFactor, BlackMisc::Aviation::CAircraftSituation &situation); - CInterpolationAndRenderingSetup m_setup; //!< allows to disable debug messages - mutable QReadWriteLock m_lock; //!< lock interpolator + CInterpolationAndRenderingSetup m_setup; //!< allows to enable/disable debug/log messages + + private: + //! Write log to file + static CStatusMessage writeLogFile(const QList &interpolation, const QList &parts); + + //! Create readable time + static QString msSinceEpochToTime(qint64 ms); + + //! Create readable time + static QString msSinceEpochToTime(qint64 t1, qint64 t2, qint64 t3 = -1); + + mutable QReadWriteLock m_lockSetup; //!< lock setup + mutable QReadWriteLock m_lockLogs; //!< lock logging + mutable QList m_partsLogs; //!< logs of parts + mutable QList m_interpolationLogs; //!< logs of interpolation }; } // namespace } // namespace