489 lines
16 KiB
C++
Executable File
489 lines
16 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* Handle situation display in thp
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
*/
|
|
|
|
#include "thp_situation_building_base.h"
|
|
#include "ui_thp_situation_building_base.h"
|
|
|
|
#include "thp_situation_filter_aes.h"
|
|
#include "thp_situation_filter_symbol_appearance.h"
|
|
#include "thp_situation_filter_collapse_expand.h"
|
|
#include "thp_situation_filter_types.h"
|
|
#include "thp_plugin_provider.h"
|
|
|
|
#include <QFileInfo>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonParseError>
|
|
#include <QTimer>
|
|
#include <LtsScriptTimeline/track_entry.h>
|
|
#include <LtsUtilsCore/qpoint3d.h>
|
|
|
|
#include <SituationReplay/handler_situation_replay_sqlite.h>
|
|
#include <PlanOverlays/Gui/plan_overlay_model.h>
|
|
|
|
#include <LtsCoordTrans/lts_coord_trans.h>
|
|
#include <ngltools/ngltools_dtg.h>
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
|
|
namespace THP
|
|
{
|
|
|
|
using namespace SituationReplay;
|
|
|
|
SituationBuildingBase::SituationBuildingBase(const char *loggerName,
|
|
QWidget *parent)
|
|
: QFrame(parent)
|
|
, m_playTimer(new QTimer(this))
|
|
, m_ui(new Ui::SituationBuildingBase)
|
|
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(loggerName))
|
|
{
|
|
m_ui->setupUi(this);
|
|
m_ui->zoomSlider->hide();
|
|
m_ui->sldPosition->setMinimum(0);
|
|
m_ui->sldPosition->setMaximum(s_sliderMax);
|
|
|
|
m_playTimer->setSingleShot(true);
|
|
connect(m_playTimer, &QTimer::timeout, this, &SituationBuildingBase::onPlay);
|
|
|
|
connect(m_ui->btnSlower, &QToolButton::clicked, this, &SituationBuildingBase::onSlower);
|
|
connect(m_ui->btnFaster, &QToolButton::clicked, this, &SituationBuildingBase::onFaster);
|
|
connect(m_ui->btn100pct, &QToolButton::clicked, this, &SituationBuildingBase::on100pct);
|
|
connect(m_ui->btnPlay, &QToolButton::clicked, this, &SituationBuildingBase::onPlay);
|
|
connect(m_ui->btnStop, &QToolButton::clicked, this, &SituationBuildingBase::onStop);
|
|
|
|
connect(m_ui->sldPosition, &QSlider::sliderPressed, this, &SituationBuildingBase::onSliderPressed);
|
|
connect(m_ui->sldPosition, &QSlider::sliderMoved, this, &SituationBuildingBase::onSliderPositionChanged);
|
|
connect(m_ui->sldPosition, &QSlider::sliderReleased, this, &SituationBuildingBase::onSliderReleased);
|
|
|
|
connect(m_ui->cbPlaybackParts, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
this, &SituationBuildingBase::onCbPlaybackPartsChanged);
|
|
}
|
|
|
|
SituationBuildingBase::~SituationBuildingBase()
|
|
{
|
|
delete m_ui;
|
|
}
|
|
|
|
bool SituationBuildingBase::init(SituationReplay::MainHandlerSqlite *replayHandler)
|
|
{
|
|
if (m_replayHandler)
|
|
m_replayHandler->disconnect(this);
|
|
m_replayHandler = replayHandler;
|
|
connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::initialReplaySpeed,
|
|
this, &SituationBuildingBase::setSpeed);
|
|
connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::applyCurentFilter,
|
|
this, &SituationBuildingBase::applyCurentFilter);
|
|
connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::applyNewOverlay,
|
|
this, &SituationBuildingBase::applyNewOverlay);
|
|
connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::removeOverlay,
|
|
this, &SituationBuildingBase::removeOverlay);
|
|
connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::fileContentAvailable,
|
|
this, &SituationBuildingBase::onFileContentAvailable);
|
|
updatePositionLabel(LtsUtils::QPoint3D(), false);
|
|
updateSpeedLabel();
|
|
return true;
|
|
}
|
|
|
|
bool SituationBuildingBase::showSituation(const QString &sitReplayDescription,
|
|
const QString &basePath)
|
|
{
|
|
// read ReplayInformation from sitmap.json (key 'replays')
|
|
|
|
QFile f(sitReplayDescription); // ...Exercise_123//n/m/sitmap.json
|
|
if (!f.open(QIODevice::ReadOnly))
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not open file '" << sitReplayDescription << "': " << f.errorString();
|
|
return false;
|
|
}
|
|
|
|
QFileInfo fi(f);
|
|
m_scriptPath = fi.absolutePath();
|
|
|
|
QJsonParseError jError;
|
|
const QByteArray ba = f.readAll();
|
|
QJsonDocument doc(QJsonDocument::fromJson(ba, &jError));
|
|
if (jError.error)
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not parse json document: " << jError.errorString();
|
|
return false;
|
|
}
|
|
const QJsonObject rootObj = doc.object();
|
|
const QJsonArray arrReplays(rootObj[QStringLiteral("replays")].toArray());
|
|
QVector<ReplayInformation> replayInformation;
|
|
replayInformation.reserve(arrReplays.size());
|
|
for (const auto &val : arrReplays)
|
|
{
|
|
const QJsonObject obj = val.toObject();
|
|
const QJsonObject objPb = obj[QStringLiteral("playbackTime")].toObject();
|
|
const QJsonObject objSqlite = obj[QStringLiteral("replayInformation")].toObject();
|
|
const QJsonObject objTip = objSqlite[QStringLiteral("timeInPresentation")].toObject();
|
|
ReplayInformation info;
|
|
info.id = obj[QStringLiteral("id")].toInt();
|
|
info.timeSpan = TimeSpan::fromJson(objPb);
|
|
info.relativeTimeSpan = TimeSpan::fromJson(objTip);
|
|
const int sqliteFolderId = objSqlite[QStringLiteral("id")].toInt();
|
|
info.databaseFilePath = QStringLiteral("%1/%2/sitreplay.sqlite").arg(basePath).arg(sqliteFolderId);
|
|
info.replayConfigPath = QStringLiteral("%1/%2/sitreplay.json").arg(basePath).arg(sqliteFolderId);
|
|
if (info.relativeTimeSpan.begin < 0 ||
|
|
info.relativeTimeSpan.end <= info.relativeTimeSpan.begin ||
|
|
sqliteFolderId == 0)
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not parse json document: " << QJsonDocument(obj).toJson();
|
|
return false;
|
|
}
|
|
replayInformation.push_back(std::move(info));
|
|
}
|
|
const QJsonObject attributes(rootObj[QStringLiteral("attributes")].toObject());
|
|
const QJsonArray arrFilters1(attributes[QStringLiteral("Filters")].toArray());
|
|
QVector<FilterInformation> filterInformation;
|
|
QVector<FilterInformation> overlayInformation;
|
|
|
|
for (const auto &arrVal : arrFilters1)
|
|
{
|
|
// const QJsonArray arrFilters2(arrVal.toArray());
|
|
// for (const auto &val : arrFilters2)
|
|
// {
|
|
FilterInformation filter;
|
|
const QJsonObject obj = arrVal.toObject();
|
|
LtsScriptTimeline::TrackEntry te;
|
|
te.fromJson(obj);
|
|
if (te.m_topicId <= 0)
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not load filter information from '" << QJsonDocument(obj).toJson() << "'";
|
|
continue;
|
|
}
|
|
|
|
if (te.m_type != LtsScriptTimeline::TrackEntryType::SituationMapFilter &&
|
|
te.m_type != LtsScriptTimeline::TrackEntryType::TacticalPlan)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Unknown Filter type " << te.m_type << " - ignoring: " << QJsonDocument(obj).toJson() << "'";
|
|
continue;
|
|
}
|
|
|
|
filter.id = te.m_topicId;
|
|
filter.relativeTimeSpan.begin = te.m_startTime * 1000LL;
|
|
filter.relativeTimeSpan.end = te.m_length * 1000LL + filter.relativeTimeSpan.begin;
|
|
const QJsonObject details = obj[QStringLiteral("detailData")].toObject();
|
|
if (te.m_type == LtsScriptTimeline::TrackEntryType::SituationMapFilter)
|
|
{
|
|
const QJsonObject filters = te.m_detailData[QStringLiteral("filters")].toObject();
|
|
filter.aesFilters = filters[QStringLiteral("AesFilterJson")].toObject();
|
|
filter.symbolSettings = filters[QStringLiteral("SymbolAppearanceJson")].toObject();
|
|
filter.buildingComponentSettings = filters[QStringLiteral("BuildingComponentSettingsJson")].toObject();
|
|
filter.mapSettings = details[QStringLiteral("settings")].toObject();
|
|
|
|
if (filter.relativeTimeSpan.begin < 0 || filter.aesFilters.isEmpty() || filter.symbolSettings.isEmpty())
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data " << QJsonDocument(obj).toJson();
|
|
return false;
|
|
}
|
|
if (filter.buildingComponentSettings.isEmpty())
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data 'BuildingComponentSettingsJson', json= " << QJsonDocument(obj).toJson();
|
|
// return false; // support older thps with this thpViewer
|
|
}
|
|
|
|
|
|
filterInformation.push_back(std::move(filter));
|
|
}
|
|
else if (te.m_type == LtsScriptTimeline::TrackEntryType::TacticalPlan)
|
|
{
|
|
if (filter.relativeTimeSpan.begin < 0)
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data " << QJsonDocument(obj).toJson();
|
|
return false;
|
|
}
|
|
|
|
// old version (before 2020/02)
|
|
filter.overlayId = details[QStringLiteral("FileId")].toInt(-1);
|
|
if (filter.overlayId >= 0)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Add overlay with id " << filter.overlayId;
|
|
overlayInformation.push_back(std::move(filter));
|
|
continue;
|
|
}
|
|
const QJsonArray arr = details[QStringLiteral("PlanItems")].toArray();
|
|
if (arr.isEmpty())
|
|
{
|
|
ERROR_LOG(m_logger) << "No PlanItems given - " << QJsonDocument(details).toJson();
|
|
}
|
|
for (const auto &ele : arr)
|
|
{
|
|
const auto pi = PlanOverlaysPlugin::PlanOverlayModel::PlanItem::fromJson(ele.toObject());
|
|
if (!pi.isValid() || pi.m_fileId <= 0)
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data " << QJsonDocument(ele.toObject()).toJson();
|
|
return false;
|
|
}
|
|
filter.overlayId = pi.m_fileId;
|
|
DEBUG_LOG(m_logger) << "Add overlay with id " << filter.overlayId;
|
|
overlayInformation.push_back(filter);
|
|
}
|
|
}
|
|
//}
|
|
}
|
|
m_replayInformation = replayInformation;
|
|
m_filterInformation = filterInformation;
|
|
m_overlayInformation = overlayInformation;
|
|
m_attributes = rootObj[QStringLiteral("attributes")].toObject();
|
|
return showSituationInternal();
|
|
}
|
|
|
|
bool SituationBuildingBase::showSituationInternal()
|
|
{
|
|
m_ui->cbPlaybackParts->setVisible(m_replayInformation.size() > 1);
|
|
if (m_replayInformation.isEmpty())
|
|
{
|
|
WARNING_LOG(m_logger) << "No replay information given...";
|
|
return false;
|
|
}
|
|
|
|
const QString sqliteDb = m_replayInformation.first().databaseFilePath;
|
|
if (sqliteDb.isEmpty())
|
|
{
|
|
WARNING_LOG(m_logger) << "No database given...";
|
|
return false;
|
|
}
|
|
QFileInfo fi(sqliteDb);
|
|
if (!fi.exists() || !fi.isFile() || !fi.isReadable())
|
|
{
|
|
Q_ASSERT(false);
|
|
WARNING_LOG(m_logger) << "Can not access sqlite database '" << fi.absoluteFilePath() << "'";
|
|
return false;
|
|
}
|
|
|
|
m_ui->cbPlaybackParts->clear();
|
|
m_ui->cbPlaybackParts->blockSignals(true);
|
|
for (int i = 0; i < m_replayInformation.size(); ++i)
|
|
{
|
|
const ReplayInformation &info = m_replayInformation.at(i);
|
|
m_ui->cbPlaybackParts->addItem(QStringLiteral("%1: %2 - %3").arg(i+1).arg(DTG::toStringMSec(info.timeSpan.begin, DTG::Long),
|
|
DTG::toStringMSec(info.timeSpan.end, DTG::Long)), i);
|
|
}
|
|
m_ui->cbPlaybackParts->setCurrentIndex(0);
|
|
m_ui->cbPlaybackParts->blockSignals(false);
|
|
onCbPlaybackPartsChanged(); // creates replayHandler for correct db etc.
|
|
|
|
return true;
|
|
}
|
|
|
|
void SituationBuildingBase::setSpeed(double newSpeed)
|
|
{
|
|
m_speedVal = qBound(0.0, newSpeed, 16.0);
|
|
if (m_replayHandler->handlerSrp())
|
|
m_replayHandler->handlerSrp()->onPaceChangeRequest(m_speedVal);
|
|
|
|
updateSpeedLabel();
|
|
}
|
|
|
|
void SituationBuildingBase::onSlower()
|
|
{
|
|
// cur speed = 9 -> new speed = 7
|
|
// cur speed = 4 -> new speed = 3
|
|
int diff = lrint(sqrt(m_speedVal) - 1);
|
|
if (diff < 1)
|
|
diff = 1;
|
|
setSpeed(m_speedVal - diff);
|
|
}
|
|
|
|
void SituationBuildingBase::onFaster()
|
|
{
|
|
int diff = lrint(sqrt(m_speedVal) - 1);
|
|
if (diff < 1)
|
|
diff = 1;
|
|
setSpeed(m_speedVal + diff);
|
|
}
|
|
|
|
void SituationBuildingBase::on100pct()
|
|
{
|
|
setSpeed(1.0);
|
|
}
|
|
|
|
void SituationBuildingBase::onPlay()
|
|
{
|
|
DEBUG_LOG(m_logger) << "OnPlay " << int(m_state);
|
|
switch (m_state)
|
|
{
|
|
case State::PS_STOP:
|
|
m_state = State::PS_PLAY;
|
|
if (m_replayHandler->handlerSrp())
|
|
m_replayHandler->handlerSrp()->onStartReplayRequest(m_speedVal);
|
|
break;
|
|
case State::PS_PAUSE:
|
|
m_state = State::PS_PLAY;
|
|
if (m_replayHandler->handlerSrp())
|
|
m_replayHandler->handlerSrp()->onResumeReplayRequest();
|
|
break;
|
|
case State::PS_PLAY:
|
|
m_state = State::PS_PAUSE;
|
|
if (m_replayHandler->handlerSrp())
|
|
m_replayHandler->handlerSrp()->onPauseReplayRequest();
|
|
break;
|
|
case State::PS_UNDEFINED:
|
|
// dürfte nicht vorkommen
|
|
break;
|
|
}
|
|
|
|
updateButtons();
|
|
}
|
|
|
|
void SituationBuildingBase::onStop()
|
|
{
|
|
auto srp = m_replayHandler->handlerSrp();
|
|
if (srp)
|
|
{
|
|
srp->onStopReplayRequest();
|
|
onUpdateTimeSlider(srp->beginTime());
|
|
}
|
|
m_state = State::PS_STOP;
|
|
updateButtons();
|
|
m_ui->sldPosition->setValue(0);
|
|
}
|
|
|
|
void SituationBuildingBase::onSliderPressed()
|
|
{
|
|
if (m_replayHandler->handlerSrp())
|
|
m_replayHandler->handlerSrp()->onPauseReplayRequest();
|
|
m_state = State::PS_PAUSE;
|
|
updateButtons();
|
|
}
|
|
|
|
void SituationBuildingBase::onSliderPositionChanged(int newPosition)
|
|
{
|
|
if (!m_replayHandler->handlerSrp())
|
|
return;
|
|
auto srp = m_replayHandler->handlerSrp();
|
|
const double len = (srp->endTime() - srp->beginTime());
|
|
const double ofs = newPosition * len / s_sliderMax;
|
|
m_sliderMoveTime = qRound64(srp->beginTime() + ofs);
|
|
|
|
m_currentTimeDTGString = DTG::toStringMSec(m_sliderMoveTime, DTG::Long);
|
|
m_ui->lblPosition->setText(m_currentTimeDTGString);
|
|
}
|
|
|
|
void SituationBuildingBase::onSliderReleased()
|
|
{
|
|
if (m_replayHandler->handlerSrp())
|
|
m_replayHandler->handlerSrp()->onStartReplayRequest(m_sliderMoveTime,
|
|
m_replayHandler->handlerSrp()->endTime(),
|
|
m_speedVal);
|
|
m_state = State::PS_PLAY;
|
|
updateButtons();
|
|
}
|
|
|
|
void SituationBuildingBase::onUpdateTimeSlider(qint64 curTime)
|
|
{
|
|
if (!m_replayHandler->handlerSrp() || m_state == State::PS_STOP)
|
|
return;
|
|
const auto srp = m_replayHandler->handlerSrp();
|
|
const double len = (srp->endTime() - srp->beginTime());
|
|
const double ofs = (curTime - srp->beginTime());
|
|
double pos = ofs / len * s_sliderMax;
|
|
if (pos < 0)
|
|
pos = 0;
|
|
if (pos >= s_sliderMax)
|
|
{
|
|
onStop();
|
|
return;
|
|
}
|
|
DEBUG_LOG(m_logger) << "onUpdateTimeSlider: " << curTime << ", pos: " << pos;
|
|
m_ui->sldPosition->setValue(qRound(pos));
|
|
|
|
m_currentTimeDTGString = DTG::toStringMSec(curTime, DTG::Long);
|
|
m_ui->lblPosition->setText(m_currentTimeDTGString);
|
|
emitPartitionTimeChanged(curTime);
|
|
}
|
|
|
|
void SituationBuildingBase::onCbPlaybackPartsChanged()
|
|
{
|
|
onStop();
|
|
int idx = m_ui->cbPlaybackParts->currentData().toInt();
|
|
if (idx < 0 || idx >= m_replayInformation.size())
|
|
return;
|
|
const ReplayInformation &info = m_replayInformation.at(idx);
|
|
if (!m_replayHandler->initDb(info, m_filterInformation, m_overlayInformation))
|
|
{
|
|
WARNING_LOG(m_logger) << "Can not initialize replay handler (" << info.databaseFilePath << ")";
|
|
return;
|
|
}
|
|
|
|
// currently after initDb since there handlerSrp is (re)created
|
|
connect(m_replayHandler->handlerSrp().get(), &SituationReplay::HandlerSituationReplaySqlite::replayTimeChanged,
|
|
this, &SituationBuildingBase::onUpdateTimeSlider, Qt::UniqueConnection);
|
|
}
|
|
|
|
void SituationBuildingBase::updateSpeedLabel()
|
|
{
|
|
QString speedStr = QStringLiteral("%1x").arg(m_speedVal, 4, 'f', 2);
|
|
m_ui->lblSpeed->setText(speedStr);
|
|
}
|
|
|
|
void SituationBuildingBase::updatePositionLabel(const LtsUtils::QPoint3D &geoPos, bool isValid)
|
|
{
|
|
if (isValid)
|
|
{
|
|
const LtsCoordTrans::UtmCoordinate coord(LtsCoordTrans::UtmCoordinate::fromGeographic(geoPos.x(), geoPos.y()));
|
|
m_ui->lblCursorPos->setText(coord.mgrs());
|
|
}
|
|
else
|
|
{
|
|
m_ui->lblCursorPos->setText(QString());
|
|
}
|
|
}
|
|
|
|
QString SituationBuildingBase::currentDTGTime() const
|
|
{
|
|
return m_currentTimeDTGString;
|
|
}
|
|
|
|
void SituationBuildingBase::updateButtons()
|
|
{
|
|
switch (m_state)
|
|
{
|
|
case State::PS_UNDEFINED:
|
|
m_ui->btnStop->setEnabled(false);
|
|
m_ui->btnPlay->setEnabled(false);
|
|
m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png")));
|
|
m_ui->sldPosition->setEnabled(false);
|
|
break;
|
|
case State::PS_STOP:
|
|
m_ui->btnStop->setEnabled(false);
|
|
m_ui->btnPlay->setEnabled(true);
|
|
m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png")));
|
|
m_ui->sldPosition->setEnabled(true);
|
|
break;
|
|
case State::PS_PLAY:
|
|
m_ui->btnStop->setEnabled(true);
|
|
m_ui->btnPlay->setEnabled(true);
|
|
m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-pause.png")));
|
|
m_ui->sldPosition->setEnabled(true);
|
|
break;
|
|
case State::PS_PAUSE:
|
|
m_ui->btnStop->setEnabled(true);
|
|
m_ui->btnPlay->setEnabled(true);
|
|
m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png")));
|
|
m_ui->sldPosition->setEnabled(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool SituationBuildingBase::stop()
|
|
{
|
|
onStop();
|
|
return true;
|
|
}
|
|
|
|
void SituationBuildingBase::onInitDone()
|
|
{
|
|
m_state = State::PS_STOP;
|
|
m_playTimer->start();
|
|
}
|
|
|
|
} // namespace THP
|