Port SymbologyImpl Esri processors to the ArcGIS 300 QFuture async API
TaskWatcher (searchSymbols/fetchSymbol/createSwatch + completion signals) -> QFuture (searchSymbolsAsync/fetchSymbolAsync/createSwatchAsync + QFutureWatcher finished/result getter); flat arcgis/ includes; arcgisversion.h shim (external SDK). advancedSymbology now builds+links vs 300 (0 errors); SymbologyXML CTest 6/6, 0 assertion failures. Migration-only vs legacy; one documented forced delta: 300 has no spec-only DictionarySymbolStyle. Summary: MIGRATION_SUMMARY_ARCGIS300_SYMBOLOGY.md.
This commit is contained in:
13 files changed
+512
-157
No files matched your search
+89
-52
@@ -27,14 +27,15 @@
|
||||
#include <SymbologyXML/xml_symbol_codec.h>
|
||||
|
||||
// ESRI
|
||||
#include <TaskWatcher.h>
|
||||
#include <Symbol.h>
|
||||
#include <SymbolStyleSearchParameters.h>
|
||||
#include <SymbolStyleSearchResultListModel.h>
|
||||
#include <SymbolStyleSearchResult.h>
|
||||
#include <MapTypes.h> // Esri::ArcGISRuntime::LoadStatus (300: enum class; Loadable.h only forward-declares it)
|
||||
#include <Error.h> // Esri::ArcGISRuntime::Error (complete type; 300 dropped Error::code())
|
||||
|
||||
|
||||
# include <arcgis/arcgisversion.h> // This file is created by RME, not ESRI
|
||||
# include <arcgisversion.h> // This file is created by RME, not ESRI (300: flat include layout, no arcgis/ subdir)
|
||||
|
||||
#if ACRGIS_VERSION >= 0x640100 // 100.1.0 / Update 1
|
||||
# define SYM_PROTECTION_AREAS_BECOMES_LINE
|
||||
@@ -58,6 +59,7 @@
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <QtCore/QFuture>
|
||||
#include <QtCore/QFutureWatcher>
|
||||
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
@@ -393,29 +395,31 @@ bool SymbolDictionaryImpl::init(SymbologyType symbologyType, const QString &symb
|
||||
m_esri_dictionary = Esri::ArcGISRuntime::DictionarySymbolStyle::createFromFile(m_symbologyResourcePath);
|
||||
|
||||
#else
|
||||
// QString symbologyResourcePath("/usr/share/arcgis/resources/symbols//mil2525d/mil2525d.stylx");
|
||||
// ArcGIS 300: the 100.x DictionarySymbolStyle(specificationType[, path]) constructors are gone
|
||||
// (300 loads a local .stylx via the static createFromFile(), as the ARCADE branch above).
|
||||
// The 100.x "built-in" spec-only dictionary (empty path) has no 300 equivalent -> fail as the
|
||||
// ARCADE branch does.
|
||||
if (symbologyResourcePath.isEmpty())
|
||||
{
|
||||
INFO_LOG(m_logger) << LOGFUNCTION << " create EsriDict:"
|
||||
<< " m_specificationType='" << m_specificationType << "'";
|
||||
m_esri_dictionary = new Esri::ArcGISRuntime::DictionarySymbolStyle(m_specificationType);
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " no symbologyResourcePath; 300 has no built-in spec-only DictionarySymbolStyle"
|
||||
<< " m_specificationType='" << m_specificationType << "'";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
QFile resFile(symbologyResourcePath);
|
||||
if (!resFile.exists())
|
||||
{
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " path does not exist: " << " symbologyResourcePath='" << symbologyResourcePath << "'";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
NOTICE_LOG(m_logger) << LOGFUNCTION << " create EsriDict:"
|
||||
<< " m_specificationType='" << m_specificationType << "'"
|
||||
<< " m_symbologyResourcePath='" << m_symbologyResourcePath << "'";
|
||||
m_esri_dictionary = new Esri::ArcGISRuntime::DictionarySymbolStyle(m_specificationType, m_symbologyResourcePath);
|
||||
QFile resFile(symbologyResourcePath);
|
||||
if (!resFile.exists())
|
||||
{
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " path does not exist: " << " symbologyResourcePath='" << symbologyResourcePath << "'";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
NOTICE_LOG(m_logger) << LOGFUNCTION << " create EsriDict:"
|
||||
<< " m_specificationType='" << m_specificationType << "'"
|
||||
<< " m_symbologyResourcePath='" << m_symbologyResourcePath << "'";
|
||||
m_esri_dictionary = Esri::ArcGISRuntime::DictionarySymbolStyle::createFromFile(m_symbologyResourcePath);
|
||||
|
||||
#endif // USE_ARCADE_SYMBOLOGY
|
||||
|
||||
if (m_esri_dictionary)
|
||||
@@ -485,11 +489,9 @@ bool SymbolDictionaryImpl::init(SymbologyType symbologyType, const QString &symb
|
||||
|
||||
void SymbolDictionaryImpl::connectSignals()
|
||||
{
|
||||
if (m_esri_dictionary)
|
||||
{
|
||||
connect(m_esri_dictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::fetchSymbolCompleted,
|
||||
this, &SymbolDictionaryImpl::onFetchSymbolCompleted);
|
||||
}
|
||||
// ArcGIS 300: the 100.x fetchSymbolCompleted signal / TaskWatcher is gone.
|
||||
// fetchSymbolAsync() now returns a QFuture and each fetchSymbol() request drives
|
||||
// onFetchSymbolCompleted() via its own QFutureWatcher (see fetchSymbol() below).
|
||||
}
|
||||
|
||||
|
||||
@@ -1350,7 +1352,16 @@ QSharedPointer<SymbolResult> SymbolDictionaryImpl::fetchSymbol(const SymbolDataT
|
||||
#define FETCH_SYMBOL_ETS_CONCURRENT
|
||||
#ifdef FETCH_SYMBOL_ETS_CONCURRENT
|
||||
// symDataToRender contains flag 'symDataToRender'
|
||||
symResult->setFuture(QtConcurrent::run(renderExtensions, extraImg, symDataToRender, colorToBeReplaced, symDataToRender.renderJustCoreImage));
|
||||
// Qt6: QtConcurrent::run(renderExtensions, constQImage&, ...) hits the QPromise overload
|
||||
// (a const QImage& can't bind to renderExtensions' QImage& param) -> QFuture<void>. Wrap in a
|
||||
// lambda that returns QImage so run() yields QFuture<QImage>. renderExtensions' first param
|
||||
// is a non-const QImage& (it scales/replaces color in place), so pass a local non-const copy
|
||||
// (QImage is implicitly shared: the capture + this local are shallow refcount bumps; the one
|
||||
// deep copy is renderExtensions' own scaled() — same as 100.x, which mutated run()'s stored copy).
|
||||
symResult->setFuture(QtConcurrent::run([extraImg, symDataToRender, colorToBeReplaced]() -> QImage {
|
||||
QImage img = extraImg;
|
||||
return renderExtensions(img, symDataToRender, colorToBeReplaced, symDataToRender.renderJustCoreImage);
|
||||
}));
|
||||
|
||||
#else
|
||||
QImage imageCopy = extraImg;
|
||||
@@ -1463,11 +1474,15 @@ QSharedPointer<SymbolResult> SymbolDictionaryImpl::fetchSymbol(const SymbolDataT
|
||||
const QVariantMap keys({{QStringLiteral("sidc"), SidcCodec::decodeRawSidc(sidc)}, // set sidc of symbol in case of MIL2525
|
||||
{QStringLiteral("sic"), SidcCodec::decodeRawSidc(sidc)}}); // set sidc of symbol in case of APP6
|
||||
|
||||
Esri::ArcGISRuntime::TaskWatcher tw = m_esri_dictionary->fetchSymbol(keys);
|
||||
// ArcGIS 300: no TaskWatcher / fetchSymbolCompleted — fetchSymbolAsync() returns a
|
||||
// QFuture<Symbol*>. Correlate the future result back to this SymbolResult with our own
|
||||
// QUuid (the SDK no longer issues a task id); a per-request QFutureWatcher delivers the
|
||||
// Symbol* on this (main) thread -> onFetchSymbolCompleted().
|
||||
QFuture<Esri::ArcGISRuntime::Symbol *> fut = m_esri_dictionary->fetchSymbolAsync(keys);
|
||||
QUuid fsTaskId;
|
||||
if (tw.isValid())
|
||||
if (fut.isValid())
|
||||
{
|
||||
fsTaskId = tw.taskId();
|
||||
fsTaskId = QUuid::createUuid();
|
||||
|
||||
if (doParanoiaChecks())
|
||||
if (fsTaskId.isNull())
|
||||
@@ -1479,11 +1494,21 @@ QSharedPointer<SymbolResult> SymbolDictionaryImpl::fetchSymbol(const SymbolDataT
|
||||
// and put into map to be able to inform customer if swatch is completed:
|
||||
m_findSymbol2symbolResult[fsTaskId] = symResult.toWeakRef();
|
||||
|
||||
DEBUG_LOG(m_logger) << LOGFUNCTION << " TaskWatcher valid: fsTaskId=" << fsTaskId;
|
||||
DEBUG_LOG(m_logger) << LOGFUNCTION << " fetchSymbolAsync future valid: fsTaskId=" << fsTaskId;
|
||||
|
||||
SymbolDictionaryImpl *const self = const_cast<SymbolDictionaryImpl *>(this); // this method is const
|
||||
QFutureWatcher<Esri::ArcGISRuntime::Symbol *> *watcher = new QFutureWatcher<Esri::ArcGISRuntime::Symbol *>(self);
|
||||
watcher->setFuture(fut);
|
||||
// 300/Qt6: connect to finished() and read the Symbol* via the result() getter.
|
||||
connect(watcher, &QFutureWatcher<Esri::ArcGISRuntime::Symbol *>::finished, watcher,
|
||||
[self, fsTaskId, watcher] {
|
||||
self->onFetchSymbolCompleted(fsTaskId, watcher->result());
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " TaskWatcher invalid"
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " fetchSymbolAsync future invalid"
|
||||
<< " ThreadId=" << QThread::currentThreadId();
|
||||
|
||||
if (doParanoiaChecks())
|
||||
@@ -1769,45 +1794,49 @@ void SymbolDictionaryImpl::onFetchSymbolCompleted(QUuid fsTaskId, Esri::ArcGISRu
|
||||
// retrieve Image :
|
||||
//
|
||||
{
|
||||
connect(symbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted,
|
||||
this, &SymbolDictionaryImpl::onSwatchCompleted);
|
||||
|
||||
// ArcGIS 300: no TaskWatcher / createSwatchCompleted — createSwatchAsync() returns a
|
||||
// QFuture<QImage>. Correlate with our own csTaskId (SDK no longer issues a task id); a
|
||||
// per-request QFutureWatcher delivers the QImage on the main thread -> onSwatchCompleted().
|
||||
#ifdef USE_ARCADE_SYMBOLOGY
|
||||
Esri::ArcGISRuntime::TaskWatcher tw;
|
||||
QFuture<QImage> fut;
|
||||
if (symResult->geometryType() == GeometryType::Polygon)
|
||||
// area/Polygon -> request rectangle, otherwise quadratic swatch:
|
||||
tw = symbol->createSwatch(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
|
||||
fut = symbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
|
||||
else
|
||||
tw = symbol->createSwatch(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
|
||||
fut = symbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
|
||||
#else
|
||||
Esri::ArcGISRuntime::TaskWatcher tw = symbol->createSwatch(CREATE_SWATCH_SCREEN_SCALE); // CREATE_SWATCH_SCREEN_SCALE
|
||||
QFuture<QImage> fut = symbol->createSwatchAsync(CREATE_SWATCH_SCREEN_SCALE); // CREATE_SWATCH_SCREEN_SCALE
|
||||
#endif // USE_ARCADE_SYMBOLOGY
|
||||
QUuid csTaskId;
|
||||
if (tw.isValid())
|
||||
|
||||
if (fut.isValid())
|
||||
{
|
||||
csTaskId = tw.taskId();
|
||||
DEBUG_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher valid: csTaskId=" << csTaskId;
|
||||
QUuid csTaskId = QUuid::createUuid();
|
||||
DEBUG_LOG(m_logger) << LOGFUNCTION << " createSwatchAsync future valid: csTaskId=" << csTaskId;
|
||||
|
||||
if (doParanoiaChecks())
|
||||
if (csTaskId.isNull())
|
||||
abort();
|
||||
|
||||
m_createSwatch2findSymbolTaskId[csTaskId] = fsTaskId;
|
||||
|
||||
QFutureWatcher<QImage> *watcher = new QFutureWatcher<QImage>(this);
|
||||
watcher->setFuture(fut);
|
||||
// 300/Qt6: connect to finished() and read the QImage via the result() getter.
|
||||
connect(watcher, &QFutureWatcher<QImage>::finished, watcher,
|
||||
[this, csTaskId, fsTaskId, symbol, watcher] {
|
||||
onSwatchCompleted(csTaskId, watcher->result(), symbol);
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid"
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " createSwatchAsync future invalid"
|
||||
<< " ThreadId=" << QThread::currentThreadId();
|
||||
|
||||
if (doParanoiaChecks())
|
||||
abort();
|
||||
|
||||
}
|
||||
|
||||
if (! csTaskId.isNull())
|
||||
m_createSwatch2findSymbolTaskId[csTaskId] = fsTaskId;
|
||||
else
|
||||
ERROR_LOG(m_logger) << LOGFUNCTION << " csTaskWatcher invalid";
|
||||
|
||||
} // retrieve Image
|
||||
|
||||
TRACE_LOG(m_logger) << LOGFUNCTION << " END"
|
||||
@@ -1816,7 +1845,7 @@ void SymbolDictionaryImpl::onFetchSymbolCompleted(QUuid fsTaskId, Esri::ArcGISRu
|
||||
<< " symbol= '" << symbol << "'";
|
||||
}
|
||||
|
||||
void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage &image)
|
||||
void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage &image, Esri::ArcGISRuntime::Symbol *symbol)
|
||||
{
|
||||
TRACE_LOG(m_logger) << LOGFUNCTION << " START"
|
||||
<< " symType= '" << this->symbologyType() << "'"
|
||||
@@ -1873,7 +1902,15 @@ void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage
|
||||
// start computation like:
|
||||
// QFuture<T> QtConcurrent::run(Function function, arg1, arc2...)
|
||||
// ^^^^^^^^^^ without()
|
||||
symResult->setFuture(QtConcurrent::run(renderExtensions, image, symToRender, QColor(), symToRender.renderJustCoreImage));
|
||||
// Qt6: same as above — the const image can't bind to renderExtensions' QImage& param via the
|
||||
// raw-pointer form; wrap in a lambda returning QImage so run() yields QFuture<QImage>.
|
||||
// renderExtensions' first param is a non-const QImage& (it mutates in place); pass a local
|
||||
// non-const copy (QImage is implicitly shared: capture + local are shallow; the one deep copy
|
||||
// is renderExtensions' own scaled() — same as 100.x, which mutated run()'s stored copy).
|
||||
symResult->setFuture(QtConcurrent::run([image, symToRender]() -> QImage {
|
||||
QImage img = image;
|
||||
return renderExtensions(img, symToRender, QColor(), symToRender.renderJustCoreImage);
|
||||
}));
|
||||
|
||||
#else // CONCURRENT
|
||||
|
||||
@@ -1915,7 +1952,7 @@ void SymbolDictionaryImpl::onSwatchCompleted(const QUuid &csTaskId, const QImage
|
||||
|
||||
// see also: "SymbolDictionary - findSymbolCompleted without taskId"
|
||||
// https://earlyadopter.esri.com/project/forum/thread.html?cap=FA3A32558952489280FB472D26FA8192&forid={A612A2F3-E344-4BD0-A1EA-0CE2DA5E694E}&topid={432F3A6B-E323-4628-BF39-77DB7BAAC9A1}
|
||||
Esri::ArcGISRuntime::Symbol *symbol = qobject_cast<Esri::ArcGISRuntime::Symbol *>(sender());
|
||||
// ArcGIS 300: the symbol is passed in (the 100.x code recovered it via sender()).
|
||||
if (symbol)
|
||||
symbol->deleteLater();
|
||||
|
||||
@@ -2349,7 +2386,7 @@ QStringList SymbolDictionaryImpl::textFieldNamesInternal() const
|
||||
void SymbolDictionaryImpl::onErrorDictionary(const Esri::ArcGISRuntime::Error &error)
|
||||
{
|
||||
FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: "
|
||||
<< " code=" << error.code()
|
||||
<< " code=" << error.arcGISServerErrorCode().value_or(-1) // 300: Error::code() removed
|
||||
<< ", domain=" << static_cast<int>(error.domain())
|
||||
<< ", extendedErrorType=" << static_cast<int>(error.extendedErrorType())
|
||||
<< ", message='" << error.message() << "'"
|
||||
|
||||
Reference in new issue
Block a user