Files
qt5-legatus-legacy/Lts/Viewer/SituationMap/SymbologyImpl/esri_dictionary_processor.cpp
T
2026-08-13 18:33:10 +02:00

395 lines
13 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Interface-Implementation of EsriDictionaryProcessor
* $Id$
* $HeadURL$
* $Author$
*/
// Own
#include "esri_dictionary_processor.h"
#include "symbol_dictionary.h" // Interface (enums)
#include <Common/situation_map_logging.h>
// ESRI
#include <arcgis/TaskWatcher.h>
#include <arcgis/Symbol.h>
#include <arcgis/SymbolStyleSearchParameters.h>
#include <arcgis/SymbolStyleSearchResultListModel.h>
#include <arcgis/SymbolStyleSearchResult.h>
// Qt
#include <QtConcurrent>
#include <QFuture>
#define CREATE_ESRI_SYMBOL_ON_DEMAND // save time on startup
// REGG-3483 - EsriDictionaryProcessor::syncTableWithinMainThread verzögert Applikations-Start um mehrere Sekunden
QImage AdvancedSymbology::scaleImage(const QImage &image, int sizeL)
{
if (sizeL == 100)
return image;
double factor = sizeL / 100.0;
const QSize &newSize = image.size() * factor;
return image.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
namespace AdvancedSymbology
{
EsriDictionaryProcessor::EsriDictionaryProcessor(SymbologyType symbologyType)
: m_logger(getLogger("EsriDictionaryProcessor")),
m_baseSymbolCodeToSymbolPropertyTable(symbologyType, SidcCodec::SidcType::Base)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " ThreadId=" << QThread::currentThreadId();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
EsriDictionaryProcessor::~EsriDictionaryProcessor()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
this->cancel();
this->waitForFinished();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void EsriDictionaryProcessor::cancel()
{
NOTICE_LOG(m_logger) << LOGFUNCTION << " canceling ...";
m_baseSymbolCodeToSymbolPropertyTableWatcher.cancel(); // Job 1
m_modifierTableWatcher.cancel(); // Job 2
// Job n
NOTICE_LOG(m_logger) << LOGFUNCTION << " canceling ... done";
}
void EsriDictionaryProcessor::waitForFinished()
{
NOTICE_LOG(m_logger) << LOGFUNCTION << " waitForFinished ...";
m_baseSymbolCodeToSymbolPropertyTableWatcher.waitForFinished(); // Job 1
m_modifierTableWatcher.waitForFinished(); // Job 2
// Job n
NOTICE_LOG(m_logger) << LOGFUNCTION << " waitForFinished ... done";
}
void EsriDictionaryProcessor::init(Esri::ArcGISRuntime::DictionarySymbolStyle *loaded_esri_dictionary)
{
m_esri_dictionary = loaded_esri_dictionary;
}
void EsriDictionaryProcessor::prepare()
{
searchSymbols();
}
void EsriDictionaryProcessor::searchSymbols()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
//
// search for all results (and store them in member):
//
connect(m_esri_dictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::searchSymbolsCompleted,
this, &EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable, Qt::UniqueConnection);
Esri::ArcGISRuntime::SymbolStyleSearchParameters sssp;
Esri::ArcGISRuntime::TaskWatcher tw = m_esri_dictionary->searchSymbols(sssp);
if (tw.isValid())
DEBUG_LOG(m_logger) << LOGFUNCTION << " TaskWatcher valid: ssTaskId=" << tw.taskId().toString();
else
ERROR_LOG(m_logger) << LOGFUNCTION << " TaskWatcher invalid" << " ThreadId=" << QThread::currentThreadId();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void EsriDictionaryProcessor::startBaseSymbolCodeToPropertyTable()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
// A-sync:
// (slot is a pure virtual method)
m_baseSymbolCodeToSymbolPropertyTableWatcher = QtConcurrent::run(this, &EsriDictionaryProcessor::processBaseSymbolCodeToPropertyTable);
// Sync: // processBaseSymbolCodeToPropertyTable();
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
}
void EsriDictionaryProcessor::startModifierTable()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
// A-sync:
// (slot is a pure virtual method)
m_modifierTableWatcher = QtConcurrent::run(this, &EsriDictionaryProcessor::processModifierTable);
// Sync: // processModifierTable();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
QStringList EsriDictionaryProcessor::symbolModifierKeys() const
{
return m_modifierTable.keys();
} // QStringList EsriDictionaryProcessor::symbolModifierKeys() const
bool EsriDictionaryProcessor::symbolModifierData(const QString &key, ModifierData &data) const
{
if (!m_modifierTable.contains(key)) return false;
data = m_modifierTable[key];
return true;
} // bool EsriDictionaryProcessor::symbolModifierData(const QString & key, SymbolDictionary::ModifierData & data) const
QVector<ModifierData> EsriDictionaryProcessor::symbolModifierList(SidcDefinitions::SymbolSet symbolSet, unsigned int modifierSector) const
{
// check modifierSector, must be 1 or 2
if ((modifierSector < 1) || (modifierSector > 2))
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Illegal modifierSector \"" << modifierSector << "\". Must be 1 or 2.";
return QVector<ModifierData>();
}
QVector<ModifierData> resultList;
resultList.reserve(m_modifierTable.size());
QMapIterator<QString, ModifierData> hIt(m_modifierTable);
while (hIt.hasNext())
{
hIt.next();
if ((hIt.value().m_symbolSet == symbolSet) && (hIt.value().m_modifierSector == modifierSector))
resultList << hIt.value();
}
return resultList;
} // QVector<SymbolDictionary::ModifierData> EsriDictionaryProcessor::symbolModifierList(const QString & baseSymbolCode, unsigned int modifierSector) const
bool EsriDictionaryProcessor::allJobsDone() const
{
return
m_baseSymbolCodeToSymbolPropertyTableFilled && // Job 1
m_modifierTableFilled; // Job 2
// Job n
}
void EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable(QUuid, Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults)
{
// ssTaskId can be ignored during initializing phase:
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
// save:
m_searchResultList = searchResults->searchResults();
// m_searchResultList << searchResults->searchResults().first(); // speed up for valgrind
// cleanup:
searchResults->deleteLater();
// disconnect:
disconnect(m_esri_dictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::searchSymbolsCompleted,
this, &EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable);
// method 'symbol' has to be called (created) within mainThread:
syncTableWithinMainThread();
//
// trigger single jobs:
//
startBaseSymbolCodeToPropertyTable(); // Job 1
startModifierTable(); // Job 2
// Job n
TRACE_LOG(m_logger) << LOGFUNCTION << " END, m_searchResultList.size()=" << m_searchResultList.size();
}
void EsriDictionaryProcessor::onEsriSymbolSwatchCompleted(const QUuid &csTaskId, const QImage &image)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START"
<< " csTaskId= '" << csTaskId << "'"
<< " image.isNull= '" << image.isNull() << "'"
<< " image.colorTable().size()= '" << image.colorTable().size() << "'"
<< " ThreadId=" << QThread::currentThreadId();
auto it = m_symbolPropertyImage2symbolResult.find(csTaskId);
if (it == m_symbolPropertyImage2symbolResult.end())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " no taskIdMapping"
<< " csTaskId= '" << csTaskId << "'";
return;
}
DEBUG_LOG(m_logger) << LOGFUNCTION << " SymbolResult found..." << " csTaskId= '" << csTaskId << "'";
QSharedPointer<SymbolResult> symResult = it.value().toStrongRef();
if (symResult)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " SymbolResult found and still valid..."
<< " csTaskId= '" << csTaskId << "'";
const SymbolDataToRender &symDataToRender = symResult->symbolDataToRender();
// save in 'cache':
const QString &originalKey = symDataToRender.spiOriginalKey;
const int sizeL = symDataToRender.spiSizeL;
auto itCache = m_originalKeyToEsriSymbol.find(originalKey);
if (itCache == m_originalKeyToEsriSymbol.end())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " save to cache, not found, " << " originalKey= '" << originalKey << "'"
<< " csTaskId= '" << csTaskId << "'";
}
else
itCache.value().swatchImage = image;
// return value
SymbolImage symImage;
const QImage &scaledImg = scaleImage(image, sizeL);
if (scaledImg.isNull())
{
ERROR_LOG(m_logger) << LOGFUNCTION
<< " image.isNull() = " << image.isNull()
<< " scaledImg.isNull() = " << scaledImg.isNull()
<< " sizeL=" << sizeL;
}
symImage.setImage(scaledImg);
symImage.setValid(true);
symResult->setSymbolImage(symImage);
symResult->fireResultReady(); // ToDo: maybe async!?!?
}
else
{
INFO_LOG(m_logger) << LOGFUNCTION << " SymbolResult has already been deleted"
<< " csTaskId= '" << csTaskId << "'";
}
// cleanup
m_symbolPropertyImage2symbolResult.erase(it);
}
void EsriDictionaryProcessor::checkIfAllAreDone()
{
if (allJobsDone())
emit allIsDone();
}
void EsriDictionaryProcessor::syncTableWithinMainThread()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
// for ALL searchResuls (for valid baseSymbolCodes, for modifierTable...)
for (const Esri::ArcGISRuntime::SymbolStyleSearchResult &resultItem : qAsConst(m_searchResultList))
{
const QString &originalKey = resultItem.key();
// store esriSymbol for rendering issues:
EsriSymbol eSym;
#ifdef CREATE_ESRI_SYMBOL_ON_DEMAND
eSym.m_sssResult =resultItem;
#else
QObject *parent = this;
eSym.esriSymbol = resultItem.symbol(parent); // resultItem.symbol() has to be called from mainThread???
#endif // CREATE_ESRI_SYMBOL_ON_DEMAND
m_originalKeyToEsriSymbol[originalKey] = eSym;
}
TRACE_LOG(m_logger) << LOGFUNCTION << " END, m_originalKeyToEsriSymbol.size=" << m_originalKeyToEsriSymbol.size();
}
bool EsriDictionaryProcessor::symbolPropertyImageContains(const QString &originalKey) const
{
return m_originalKeyToEsriSymbol.contains(originalKey);
}
QSharedPointer<SymbolResult> EsriDictionaryProcessor::symbolPropertyImage(QSharedPointer<SymbolResult> symResult, const QString &originalKey) const
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
auto it = m_originalKeyToEsriSymbol.find(originalKey);
if (it == m_originalKeyToEsriSymbol.end())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " not found, " << " originalKey= '" << originalKey << "'";
return symResult;
}
const EsriSymbol &eSym = it.value();
if (eSym.swatchImage.isNull())
{
#ifdef CREATE_ESRI_SYMBOL_ON_DEMAND
if (!eSym.esriSymbol)
{
QObject *parent = const_cast<EsriDictionaryProcessor *>(this); // cast const away
eSym.esriSymbol = eSym.m_sssResult.symbol(parent); // resultItem.symbol() has to be called from mainThread???
}
#endif // CREATE_ESRI_SYMBOL_ON_DEMAND
connect(eSym.esriSymbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted,
this, &EsriDictionaryProcessor::onEsriSymbolSwatchCompleted, Qt::UniqueConnection);
#ifdef USE_ARCADE_SYMBOLOGY
Esri::ArcGISRuntime::TaskWatcher tw;
auto it = m_baseSymbolCodeToSymbolPropertyTable.find(originalKey);
if (it != m_baseSymbolCodeToSymbolPropertyTable.end() && (it->geometryType() == GeometryType::Polygon) )
// area/Polygon -> request rectangle, otherwise quadratic swatch:
tw = eSym.esriSymbol->createSwatch(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
else
tw = eSym.esriSymbol->createSwatch(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
#else
Esri::ArcGISRuntime::TaskWatcher tw = eSym.esriSymbol->createSwatch(CREATE_SWATCH_SCREEN_SCALE);
#endif // USE_ARCADE_SYMBOLOGY
QUuid csTaskId;
if (tw.isValid())
{
csTaskId = tw.taskId();
DEBUG_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher valid: csTaskId=" << csTaskId;
}
else
{
ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid"
<< " ThreadId=" << QThread::currentThreadId();
}
if (! csTaskId.isNull())
m_symbolPropertyImage2symbolResult[csTaskId] = symResult.toWeakRef();
else
ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid";
}
else
{
// use from 'cache':
SymbolImage symImage;
const SymbolDataToRender &symDataToRender = symResult->symbolDataToRender();
int sizeL = symDataToRender.sizeL;
const QImage &scaledImg = scaleImage(eSym.swatchImage, sizeL);
symImage.setImage(scaledImg);
symImage.setValid(true);
symResult->setSymbolImage(symImage);
symResult->fireResultReady(); // ToDo: maybe async!?!?
}
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
return symResult;
}
} // namespace AdvancedSymbology