/** * @RDE_HEADER@ * Handle situation display in thp * $Id$ * $HeadURL$ * $Author$ */ #include "thp_situation_widget.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_situation_filter_building_components.h" #include "thp_plugin_provider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace THP { class MySitMapIinit final : public SituationMap::SituationMapInit { Q_OBJECT public: using SituationMap::SituationMapInit::SituationMapInit; bool trainingAreaExists() const override { return true; } SituationMap::MapSettingsJson getMapSettingsFromService() const override { return m_mapSettingsJson; } void setMapSettingsJson(const SituationMap::MapSettingsJson &settings) { m_mapSettingsJson = settings; } const SituationMap::MapSettingsJson & getMapSettingsJson() const { return m_mapSettingsJson; } private: SituationMap::MapSettingsJson m_mapSettingsJson; }; using namespace SituationReplay; SituationWidget::SituationWidget(QWidget *parent) : SituationBuildingBase("THP.SituationWidget", parent) , m_pluginProvider(new SitMapPluginProvider) , m_situationMapInit(new MySitMapIinit(nullptr)) { QMenu *m = new QMenu(m_ui->btnFilter); m->addAction(tr("AES Filter"), this, &SituationWidget::onFilterAes); // 29.09.2022, Norbert Thoden // SWPÄ 2:22 GUEZE-1477 Kartenfilter für Komponenten: // Es wurde der Komponenten Filter eingeführt. // in der LiveLage werden allerdings nur MountingPoints angezeigt, die auch eine // zugeordnetes Asset haben. Die Zuordnug befindet sich in der Tabelle ism.sim_mounting_point_asset. // ism.sim_mounting_point_asset wird derzeit nicht in die sitreplay.sqlite // überführt. Es koennen also keine Komponenten angezeigt / gefiltert werden... // FilterDialog nicht anzeigen: // m->addAction(tr("Building / Component"), this, &SituationWidget::onFilterBuildingComponent); m->addAction(tr("Symbol Appearance"), this, &SituationWidget::onFilterSymbolAppearance); m->addAction(tr("Collpase/Expand"), this, &SituationWidget::onFilterCollapseExpand); m->addAction(tr("Type filter"), this, &SituationWidget::onFilterTypes); m_ui->btnFilter->setMenu(m); } SituationWidget::~SituationWidget() { if (m_replayHandler->handlerSrp()) m_replayHandler->handlerSrp()->onStopReplayRequest(); delete m_situationMapInit; delete m_pluginProvider; delete m_replayHandler; } bool SituationWidget::init(const QString &homePath, int exerciseId) { m_pluginProvider->setExerciseId(exerciseId); delete m_replayHandler; m_replayHandler = new SituationReplay::MainHandlerSqlite; if (!SituationBuildingBase::init(m_replayHandler)) return false; loadLayerMappings(homePath); SituationReplay::ConnectionParams connParams; connParams.domainId = 1; connParams.partitionId = 1; if (!m_replayHandler->initDds(connParams)) return false; if (!m_pluginProvider->init(m_replayHandler->ddsReplay())) return false; if (!m_situationMapInit->initPluginProvider(m_pluginProvider)) return false; QPointer movementTrackMgr = QPointer(new SituationMap::MovementTrackManager(m_situationMapInit)); FATAL_LOG(m_logger) << LOGFUNCTION << " XXXX initMap...."; if (!m_situationMapInit->initMap(false, SituationMap::Mode::Mode_2D)) return false; if (!m_situationMapInit->initAdapter(movementTrackMgr, false)) return false; SituationMap::Map *map = m_situationMapInit->getWidget(); if (!map) return false; connect(map, &SituationMap::Map::mouseGeoPositionChanged, this, &SituationWidget::updatePositionLabel, Qt::UniqueConnection); connect(map, &SituationMap::Map::extentChanged, this, &SituationWidget::updateZoomSlider); map->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_ui->vLayout->insertWidget(0, map); m_aesDisplayFilter = m_pluginProvider->fltAesIfc()->aesFilterForOperator(0, QString()); m_symbolAppaearanceSettings = m_pluginProvider->fltParticipantIfc()->symbolAppearanceForOperator(0, QString()); m_buildingComponentSettings = m_pluginProvider->fltBuildingComponentIfc()->buildingComponentSettingsForOperator(0, QString()); return true; } bool SituationWidget::showSituationInternal() { // cleanup 'old' map: // remove old overlays from former session: { SituationMap::Map *map = m_situationMapInit->getWidget(); if (map) map->overlayHandler().removeAllOverlays(); } m_ui->cbPlaybackParts->setVisible(m_replayInformation.size() > 1); // MAYBE there are filter to be applied: applyFilterFromReplayConfigFile(); 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. SituationMap::Map *map = m_situationMapInit->getWidget(); if (map->isReady()) QTimer::singleShot(0, this, &SituationWidget::onInitDone); else connect(map, &SituationMap::Map::mapReady, this, &SituationWidget::onInitDone, Qt::QueuedConnection); return true; } bool SituationWidget::applyFilterFromReplayConfigFile() { // Maybe the Situation topic has own Filter (AesFilter, SymbolAppearance; OperatorMapSettings) // Read and apply them: if (m_replayInformation.isEmpty()) { WARNING_LOG(m_logger) << "No replay information given..."; return false; } const QString replayConfigFile = m_replayInformation.first().replayConfigPath; // sitreplay.json if (replayConfigFile.isEmpty()) { WARNING_LOG(m_logger) << "No replayConfigFile given..."; return false; } QFileInfo fi(replayConfigFile); if (!fi.exists() || !fi.isFile() || !fi.isReadable()) { WARNING_LOG(m_logger) << "Can not access replayConfigFile '" << fi.absoluteFilePath() << "'"; return false; } QFile f(replayConfigFile); if (!f.open(QIODevice::ReadOnly)) { WARNING_LOG(m_logger) << "Can not open replayConfigFile '" << fi.absoluteFilePath() << "': " << f.errorString(); return false; } 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: " << ", replayConfigFile '" << fi.absoluteFilePath() << "'" << ", errorString= " << jError.errorString(); return false; } const QJsonObject &rootObj = doc.object(); const QJsonObject &filters = rootObj[QStringLiteral("filters")].toObject(); // optional!!! if (filters.isEmpty()) { WARNING_LOG(m_logger) << "no 'filters' provided at " << " replayConfigFile '" << fi.absoluteFilePath() << "'"; return false; } DEBUG_LOG(m_logger) << LOGFUNCTION << " XXXX replayConfigFile='" << fi.absoluteFilePath() << "'" << " doc.toJson = " << doc.toJson(); // fine, put together FilterInformation filterInfo; filterInfo.aesFilters = filters[QStringLiteral("AesFilterJson")].toObject(); filterInfo.symbolSettings = filters[QStringLiteral("SymbolAppearanceJson")].toObject(); filterInfo.buildingComponentSettings = filters[QStringLiteral("BuildingComponentSettingsJson")].toObject(); filterInfo.mapSettings = rootObj[QStringLiteral("settings")].toObject(); applyCurentFilter(filterInfo); return true; } void SituationWidget::applyCurentFilter(const SituationReplay::FilterInformation &filter) { m_aesDisplayFilter->setFilterFromData(filter.aesFilters); m_symbolAppaearanceSettings->setSettings(filter.symbolSettings, FilterManagement::SymbolAppearanceSettings::Part::All); m_buildingComponentSettings->setBldCmpSettings(filter.buildingComponentSettings); SituationMap::OperatorMapSettings oms; oms.fromJsonObject(filter.mapSettings); if (oms.isEmpty() || oms.mode == SituationMap::Mode::Undefined) return; if (oms.mode != m_situationMapInit->getWidget()->mode()) { INFO_LOG(m_logger) << " Switching to new mode : " << oms.mode; SituationMap::MapSettingsJson savedMapSettingsJson; if (m_situationMapInit) { savedMapSettingsJson = m_situationMapInit->getMapSettingsJson(); // containing path from layerMappping.json m_situationMapInit->stopDisplay(); m_situationMapInit->clearObjects(); } delete m_situationMapInit; m_situationMapInit = new MySitMapIinit(nullptr); m_situationMapInit->setMapSettingsJson(savedMapSettingsJson); m_situationMapInit->initPluginProvider(m_pluginProvider); m_situationMapInit->initMap(false, oms.mode); QPointer movementTrackMgr = QPointer(new SituationMap::MovementTrackManager(m_situationMapInit)); if (!m_situationMapInit->initAdapter(movementTrackMgr, false)) return; SituationMap::Map *map = m_situationMapInit->getWidget(); if (map) { connect(map, &SituationMap::Map::mouseGeoPositionChanged, this, &SituationWidget::updatePositionLabel, Qt::UniqueConnection); connect(map, &SituationMap::Map::extentChanged, this, &SituationWidget::updateZoomSlider); map->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_ui->vLayout->insertWidget(0, map); static bool s_waitFlag = true; QPointer mapNotifier = map->createMapNotifier(); if (mapNotifier) { connect(mapNotifier.data(), &SituationMap::MapNotifier::mapReadyAndSymbolDictionaryLoaded, this, [this, oms]() { s_waitFlag = false; if (!restoreOperatorMapSettingsOverlays(oms)) WARNING_LOG(m_logger) << "restoreOperatorMapSettingsOverlays failed"; }); mapNotifier->run(); // snyc wait :-( { DEBUG_LOG(m_logger) << " WAIT_SYNC BEGIN"; int i=0; while(i<100 && s_waitFlag) // 100 * 50ms = 5000ms { qApp->processEvents(QEventLoop::ExcludeUserInputEvents|QEventLoop::ExcludeSocketNotifiers, 50); QThread::msleep(50); ++i; DEBUG_LOG(m_logger) << " WAIT_SYNC ..., i=" << i; } DEBUG_LOG(m_logger) << " WAIT_SYNC ...DONE, i=" << i; DEBUG_LOG(m_logger) << " WAIT_SYNC, m_situationMapInit->getWidget()->mode()" << m_situationMapInit->getWidget()->mode(); m_situationMapInit->startDisplay(); } } // if (mapNotifier) } } else { if (!restoreOperatorMapSettingsOverlays(oms)) WARNING_LOG(m_logger) << "restoreOperatorMapSettingsOverlays failed"; } } bool SituationWidget::restoreOperatorMapSettingsOverlays(const SituationMap::OperatorMapSettings &oms) { if (!m_situationMapInit || !m_situationMapInit->getWidget() || oms.isEmpty()) return false; if (!m_situationMapInit->getWidget()->restoreOperatorMapSettings(oms)) WARNING_LOG(m_logger) << LOGFUNCTION << " restoreOperatorMapSettings failed"; // continue!!! maybe just LayerOrder failed.... ToDo // 2. handle Overlays : // SituationMap can`t restore the overlays on it´s own: // So we have to do it here: const QList &orderedOverlayIds = oms.orderedOverlays; DEBUG_LOG(m_logger) << LOGFUNCTION << " restore OverlayIds..."; for (auto overlayId : qAsConst(orderedOverlayIds)) { DEBUG_LOG(m_logger) << LOGFUNCTION << " applyNewOverlay , overlayId=" << overlayId; applyNewOverlay(overlayId); } return true; } void SituationWidget::applyNewOverlay(int overlayId) { const QString filename = QStringLiteral("%1/%2.TacticalPlan").arg(m_scriptPath).arg(overlayId); QFile f(filename); if (!f.open(QIODevice::ReadOnly)) { WARNING_LOG(m_logger) << "Can not open tactical plan '" << filename << "': " << f.errorString(); return; } SituationMap::OverlayHandler &overlayHandler = m_situationMapInit->getWidget()->overlayHandler(); if (!overlayHandler.setOverlay(overlayId, tr("Plan '%1'").arg(overlayId), f.readAll(), false)) WARNING_LOG(m_logger) << "can not display overlay " << filename; } void SituationWidget::removeOverlay(int overlayId) { SituationMap::OverlayHandler &overlayHandler = m_situationMapInit->getWidget()->overlayHandler(); overlayHandler.removeOverlay(overlayId); } void SituationWidget::onFilterAes() { if (!m_filterAesDialog) m_filterAesDialog = new SituatationFilterAes(m_aesDisplayFilter, m_pluginProvider, this); m_filterAesDialog->show(); } void SituationWidget::onFilterSymbolAppearance() { if (!m_filterSymbolAppearanceDialog) m_filterSymbolAppearanceDialog = new SituatationFilterSymbolAppearance(m_symbolAppaearanceSettings, m_pluginProvider, this); m_filterSymbolAppearanceDialog->show(); } void SituationWidget::onFilterCollapseExpand() { if (!m_filterCollapseExpandDialog) m_filterCollapseExpandDialog = new SituatationFilterCollapseExpand(m_symbolAppaearanceSettings, m_pluginProvider, this); m_filterCollapseExpandDialog->show(); } void SituationWidget::onFilterTypes() { if (!m_filterTypesDialog) m_filterTypesDialog = new SituatationFilterTypes(m_symbolAppaearanceSettings, m_pluginProvider, this); m_filterTypesDialog->show(); } void SituationWidget::onFilterBuildingComponent() { if (!m_filterBuildingComponentDialog) m_filterBuildingComponentDialog = new SituationFilterBuildingComponent(m_buildingComponentSettings, m_pluginProvider, this); m_filterBuildingComponentDialog->show(); } void SituationWidget::emitPartitionTimeChanged(qint64 curTime) { m_pluginProvider->emitPartitionTimeChanged(curTime); } void SituationWidget::updateZoomSlider() { // this is not really what we want to display... but currently we hide the slider anyway m_ui->zoomSlider->setZoomValue(m_situationMapInit->getWidget()->scale() / 1000); } QString SituationWidget::centerPointString() const { const QPointF center = m_situationMapInit->getWidget()->centerPoint(); LtsCoordTrans::UtmCoordinate coord(LtsCoordTrans::UtmCoordinate::fromGeographic(center.x(), center.y())); return coord.mgrs(); } QRectF SituationWidget::extentMgrs() const { const QRectF r = m_situationMapInit->getWidget()->extent(); LtsCoordTrans::UtmCoordinate topLeft(LtsCoordTrans::UtmCoordinate::fromGeographic(r.left(), r.top())); LtsCoordTrans::UtmCoordinate bottomRight(LtsCoordTrans::UtmCoordinate::fromGeographic(r.right(), r.bottom(), topLeft.zone)); return QRectF(QPointF(topLeft.x, topLeft.y), QPointF(bottomRight.x, bottomRight.y)); } QImage SituationWidget::grabFrameBuffer() const { return m_situationMapInit->getWidget()->grab().toImage(); } void SituationWidget::onInitDone() { SituationBuildingBase::onInitDone(); m_situationMapInit->startDisplay(); } void SituationWidget::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_F10: onMgrsGrid(); break; case Qt::Key_PageUp: onRotate(10); break; case Qt::Key_PageDown: onRotate(-10); break; default: QFrame::keyPressEvent(event); break; } } void SituationWidget::onMgrsGrid() { SituationMap::Map *m = m_situationMapInit->getWidget(); m->grid().setVisible(!m->grid().isVisible()); } void SituationWidget::onRotate(int degrees) { SituationMap::Map *m = m_situationMapInit->getWidget(); m->setRotation(m->rotation() + degrees); } void SituationWidget::onFileContentAvailable(const FileManagement::Database::FlmFileContent &data) { if (data.fileTypeId != FileManagement::FileType::OperatorDefinedSymbol || data.fileStatusId != FileManagement::FileStatus::Valid || data.content.size() == 0) return; QColor colorToBeReplaced; bool bIsXml = false; if (!data.properties.isEmpty()) { QJsonParseError parseError; const QJsonDocument jDoc(QJsonDocument::fromJson(data.properties.toUtf8(), &parseError)); const QJsonObject obj(jDoc.object()); const QLatin1String imageFormatKey("imageFormat"); bIsXml = obj.value(imageFormatKey).toString() == QLatin1String("xml"); if (!bIsXml) { // partyColor const QLatin1String partyColorStr("partyColor"); if (obj.contains(partyColorStr)) { const QJsonObject partyColor = obj[partyColorStr].toObject(); if (partyColor.size() == 3) // r, g, b { // update map: colorToBeReplaced.setRgb( partyColor[QLatin1String("r")].toInt(), partyColor[QLatin1String("g")].toInt(), partyColor[QLatin1String("b")].toInt()); } // if (partyColor.size() == 3) } // if (obj.contains(partyColorStr)) } } AdvancedSymbology::SymbologyType symType = AdvancedSymbology::SymbolDictionary::defaultSymbologyType(); AdvancedSymbology::SymbolDictionary &dict = AdvancedSymbology::SymbolDictionary::dictionary(symType); if (bIsXml) { dict.setXmlSymbol(data.id, data.name, QStringLiteral("OperatorName"), 0, QString::fromUtf8(data.content), AdvancedSymbology::SymbolProperties()); } else { QImage image; QSvgRenderer svgRenderer; if (!svgRenderer.load(data.content)) return; // prepare Image: constexpr int size = 200; // pixel QImage imageOut(size, size, QImage::Format_ARGB32); imageOut.fill(Qt::transparent); // render QPainter painter(&imageOut); svgRenderer.render(&painter); painter.end(); AdvancedSymbology::SymbolProperties symProps; symProps.setKey(data.name); symProps.setSidc(AdvancedSymbology::SidcCodec::encodeCivilEts(data.name)); symProps.setName(data.name); symProps.setSymbolClass(QLatin1String("3")); // 18.10.2016, N.Thoden: 3 == PointSymbol (mil2525d.stylx - QuartzRelease) symProps.setGeometryType(AdvancedSymbology::GeometryType::Point); // ETS-Symbols are always Points symProps.setTags({data.name}); symProps.setColorToBeReplaced(colorToBeReplaced); dict.setEtsSymbol(data.id, data.name, symProps, image); } } void SituationWidget::loadLayerMappings(const QString &homePath) { const QString mappingsFile = homePath + QStringLiteral("/layerMapping.json"); QFile f(mappingsFile); if (!f.open(QIODevice::ReadOnly)) { fprintf(stderr, "Could not load layer mappings from '%s' - %s\n", mappingsFile.toStdString().c_str(), f.errorString().toStdString().c_str()); WARNING_LOG(m_logger) << "Could not load layer mappings from " << mappingsFile << ": " << f.errorString(); return; } m_layerMapping.clear(); const QJsonObject obj = QJsonDocument::fromJson(f.readAll()).object(); SituationMap::MapSettingsJson settings; if (!settings.fromJsonObject(obj)) { WARNING_LOG(m_logger) << "Could not parse layer mappings from " << mappingsFile; return; } settings.setMapUrls(fixupUrls(settings.getMapUrls(), homePath)); settings.setSceneUrls(fixupUrls(settings.getSceneUrls(), homePath)); settings.setSceneObjectUrls(fixupUrls(settings.getSceneObjectUrls(), homePath)); settings.setElevationUrls(fixupUrls(settings.getElevationUrls(), homePath)); m_situationMapInit->setMapSettingsJson(settings); } QVector SituationWidget::fixupUrls(const QVector &in, const QString &homePath) { QVector out; for (auto url : in) { const QString fn = QStringLiteral("%1/Maps/%2").arg(homePath, url.url()); if (!QFile::exists(fn)) { fprintf(stderr, "Can not find map data file '%s'\n", fn.toStdString().c_str()); QMessageBox::warning(this, tr("Can not find map data"), tr("Can not find map data file '%1' - can't start application").arg(fn)); QCoreApplication::quit(); return out; } url.setUrl(QUrl::fromLocalFile(fn).toString()); out.push_back(std::move(url)); } return out; } } // namespace THP #include "thp_situation_widget.moc"