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:
berrux-dev committed 2026-10-07 06:24:35 +02:00
1 parent 78253c60f4
commit 2b957d1edb
13 files changed
+512 -157

No files matched your search

@@ -12,7 +12,7 @@
#include "situation_map_types.h"
#include <Common/situation_map_logging.h>
#include <arcgis/arcgisversion.h> // This file is created by RME, not ESRI
#include <arcgisversion.h> // This file is created by RME, not ESRI
// Qt
@@ -18,7 +18,7 @@
#include <Rubberbands/event_mouse_navigator.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include <arcgis/arcgisversion.h> // This file is created by RME, not ESRI
#include <arcgisversion.h> // This file is created by RME, not ESRI
#include <QtCore/QCoreApplication>
#include <QtCore/QJsonDocument>
@@ -12,13 +12,13 @@
// Esri
#include <arcgis/CoreTypes.h> // for LicenseStatus
#include <arcgis/MapTypes.h>
#include <arcgis/Viewpoint.h>
#include <arcgis/Camera.h>
#include <CoreTypes.h> // for LicenseStatus
#include <MapTypes.h>
#include <Viewpoint.h>
#include <Camera.h>
#include <arcgis/arcgisversion.h> // This file is created by RME, not ESRI
#include <arcgisversion.h> // This file is created by RME, not ESRI
#if ACRGIS_VERSION >= 0x640600 // 100.6.0 / Update 6
#define HAS_SURFACEPLACEMENT_RELATIVETOSCENE
@@ -13,8 +13,8 @@
// arcgis
#include <arcgis/SceneViewTypes.h> // SurfacePlacement
#include <arcgis/Error.h>
#include <SceneViewTypes.h> // SurfacePlacement
#include <Error.h>
// Qt
@@ -26,7 +26,7 @@ class QValidator;
#ifdef BUILD_ARCGIS
# 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 >= 0x640600 // 100.6.0 / Update 6
//# define USE_ARCADE_SYMBOLOGY
@@ -18,8 +18,9 @@
// arcgis
#include <TaskWatcher.h>
#include <DictionarySymbolStyle.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())
// Qt
@@ -31,6 +32,9 @@
#include <utility>
#include <QMimeData>
#include <QtCore/QTimer>
#include <QtCore/QFuture>
#include <QtCore/QFutureWatcher>
#include <QtGui/QImage>
namespace AdvancedSymbology
@@ -159,7 +163,7 @@ QString BaseSymbolItem::symbolSet()
{
if (m_symbolSet.isEmpty())
{
SidcDefinitions::SymbolSet symbolSet = SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt() // Qt6: leftRef removed);
SidcDefinitions::SymbolSet symbolSet = SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt()); // Qt6: leftRef(2) removed
m_symbolSet = QString(QStringLiteral("%1 - ")).arg(static_cast<int>(symbolSet)) + AdvancedSymbology::SidcDefinitions::toString(symbolSet);
}
return m_symbolSet;
@@ -167,7 +171,7 @@ QString BaseSymbolItem::symbolSet()
int BaseSymbolItem::symbolSetAsInt()
{
return static_cast<int>(SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt() // Qt6: leftRef removed));
return static_cast<int>(SidcDefinitions::castToSymbolSet(m_symbolProperties.originalKey().left(2).toUInt())); // Qt6: leftRef(2) removed
}
@@ -222,23 +226,32 @@ QImage BaseSymbolItem::getSearchSwatch()
// qCritical() << " SEARCH: id=" << m_id << " create swatch now";
// qCritical() << " SEARCH: id=" << m_esriSearch.m_esriSymbol->toJson();
connect(m_esriSearch.m_esriSymbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted,
this, &BaseSymbolItem::onEsriSearchSymbolSwatchCompleted, Qt::UniqueConnection);
const Esri::ArcGISRuntime::TaskWatcher &tw = m_esriSearch.m_esriSymbol->createSwatch(SWATCH_SIZE, SWATCH_SIZE, 1);
if (tw.isValid())
m_esriSearch.m_esriTaskUuid = tw.taskId();
// ArcGIS 300: no TaskWatcher / createSwatchCompleted — createSwatchAsync() returns a
// QFuture<QImage>; a per-request QFutureWatcher delivers the QImage on the main thread.
const QFuture<QImage> fut = m_esriSearch.m_esriSymbol->createSwatchAsync(SWATCH_SIZE, SWATCH_SIZE, 1);
if (fut.isValid())
{
m_esriSearch.m_esriTaskUuid = QUuid::createUuid(); // in-flight guard (was the 100.x task id)
QFutureWatcher<QImage> *watcher = new QFutureWatcher<QImage>(this);
watcher->setFuture(fut);
// 300/Qt6: the 'result' signal is name-collision-ambiguous with the result() getter (QOverload
// can't disambiguate); connect to the unambiguous finished() signal and read the value via the
// result() getter (the future is finished when finished() fires).
connect(watcher, &QFutureWatcher<QImage>::finished, watcher,
[this, watcher] {
onEsriSearchSymbolSwatchCompleted(watcher->result());
watcher->deleteLater();
});
}
else
qCritical() << " SEARCH: id=" << m_id << " taskId invalid";
qCritical() << " SEARCH: id=" << m_id << " createSwatchAsync future invalid";
}
return m_esriSearch.m_esriSwatch;
}
void BaseSymbolItem::onEsriSearchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image)
void BaseSymbolItem::onEsriSearchSymbolSwatchCompleted(const QImage &image)
{
if (m_esriSearch.m_esriTaskUuid != taskId)
return;
// ArcGIS 300: the dedicated QFutureWatcher makes the (removed) taskId stale-check unnecessary.
m_esriSearch.m_esriSwatch = image;
emit dataChanged(m_id);
@@ -251,9 +264,6 @@ QImage BaseSymbolItem::getFetchSwatch() const
if (m_esriFetch.m_esriSwatch.isNull() && m_esriFetch.m_esriFetchTaskIdUuid.isNull())
{
connect(m_esriDictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::fetchSymbolCompleted,
this, &BaseSymbolItem::onEsriFetchCompleted, Qt::UniqueConnection);
// from key8 to sidc (20 digits)
const QString &key8 = m_symbolProperties.key().left(8);
@@ -272,39 +282,57 @@ QImage BaseSymbolItem::getFetchSwatch() const
const QVariantMap keys({{QStringLiteral("sidc"), m_esriFetch.m_sidc}, // SidcCodec::decodeRawSidc ; set sidc of symbol in case of MIL2525
{QStringLiteral("sic"), m_esriFetch.m_sidc}}); // SidcCodec::decodeRawSidc , set sidc of symbol in case of APP6
const Esri::ArcGISRuntime::TaskWatcher &tw = m_esriDictionary->fetchSymbol(keys);
if (tw.isValid())
m_esriFetch.m_esriFetchTaskIdUuid = tw.taskId();
// ArcGIS 300: no TaskWatcher / fetchSymbolCompleted — fetchSymbolAsync() returns a
// QFuture<Symbol*>; a per-request QFutureWatcher delivers the Symbol* on the main thread.
const QFuture<Esri::ArcGISRuntime::Symbol *> fut = m_esriDictionary->fetchSymbolAsync(keys);
if (fut.isValid())
{
m_esriFetch.m_esriFetchTaskIdUuid = QUuid::createUuid(); // in-flight guard (was the 100.x task id)
BaseSymbolItem *const self = const_cast<BaseSymbolItem *>(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 (see above).
connect(watcher, &QFutureWatcher<Esri::ArcGISRuntime::Symbol *>::finished, watcher,
[self, watcher] {
self->onEsriFetchCompleted(watcher->result());
watcher->deleteLater();
});
}
else
qCritical() << " FETCH: id=" << m_id << " m_esriFetchTaskIdUuid invalid";
qCritical() << " FETCH: id=" << m_id << " fetchSymbolAsync future invalid";
}
return m_esriFetch.m_esriSwatch;
}
void BaseSymbolItem::onEsriFetchCompleted(const QUuid &taskId, Esri::ArcGISRuntime::Symbol *symbol)
void BaseSymbolItem::onEsriFetchCompleted(Esri::ArcGISRuntime::Symbol *symbol)
{
if (m_esriFetch.m_esriFetchTaskIdUuid != taskId)
return;
// ArcGIS 300: the dedicated QFutureWatcher makes the (removed) taskId stale-check unnecessary.
// save intermediate result:
m_esriFetch.m_esriSymbol = symbol;
// start creating swatch of fetched symbol:
connect(m_esriFetch.m_esriSymbol, &Esri::ArcGISRuntime::Symbol::createSwatchCompleted,
this, &BaseSymbolItem::onEsriFetchSymbolSwatchCompleted, Qt::UniqueConnection);
const Esri::ArcGISRuntime::TaskWatcher &tw = m_esriFetch.m_esriSymbol->createSwatch(SWATCH_SIZE, SWATCH_SIZE, 1);
if (tw.isValid())
m_esriFetch.m_esriSwatchTaskUuid = tw.taskId();
// start creating swatch of fetched symbol (createSwatchAsync -> QFuture<QImage>):
const QFuture<QImage> fut = m_esriFetch.m_esriSymbol->createSwatchAsync(SWATCH_SIZE, SWATCH_SIZE, 1);
if (fut.isValid())
{
m_esriFetch.m_esriSwatchTaskUuid = QUuid::createUuid(); // in-flight guard (was the 100.x task id)
QFutureWatcher<QImage> *watcher = new QFutureWatcher<QImage>(this);
watcher->setFuture(fut);
// 300/Qt6: connect to finished() and read the QImage via the result() getter (see above).
connect(watcher, &QFutureWatcher<QImage>::finished, watcher,
[this, watcher] {
onEsriFetchSymbolSwatchCompleted(watcher->result());
watcher->deleteLater();
});
}
else
qCritical() << " FETCH: id=" << m_id << " taskId invalid";
qCritical() << " FETCH: id=" << m_id << " createSwatchAsync future invalid";
}
void BaseSymbolItem::onEsriFetchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image)
void BaseSymbolItem::onEsriFetchSymbolSwatchCompleted(const QImage &image)
{
if (m_esriFetch.m_esriSwatchTaskUuid != taskId)
return;
// ArcGIS 300: the dedicated QFutureWatcher makes the (removed) taskId stale-check unnecessary.
m_esriFetch.m_esriSwatch = image;
// qCritical() << " FETCH: id=" << m_id << " image " << image.size();
@@ -426,7 +454,7 @@ void BaseSymbolModel::onLoadStatusChanged(Esri::ArcGISRuntime::LoadStatus loadSt
void BaseSymbolModel::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() << "'"
@@ -456,13 +484,15 @@ void BaseSymbolModel::initModel(SymbolDictionary *symbolDictionary,
// spend time to create (expensive) Esri::ArcGISRuntime::Symbol?
#ifdef LTS_DEBUG
Esri::ArcGISRuntime::Symbol *esriSymbol = nullptr;
Esri::ArcGISRuntime::Symbol *esriSymbol = nullptr; // 300: filled asynchronously (see below)
const bool showAll = QGuiApplication::keyboardModifiers().testFlag(Qt::KeyboardModifier::ControlModifier);
if (showAll)
esriSymbol = searchResult.symbol(this); // symbol() has to be called from mainThread, is expensive / time consuming
// 300: symbol() (const, sync, mainThread) is gone; fetchSymbolAsync() is non-const + async.
// When showAll, the Symbol is fetched after the item exists and attached via
// setEsriSearchSymbol() when the future completes.
#else
// release mode: save time, won´t be visible
Esri::ArcGISRuntime::Symbol *esriSymbol = nullptr;
const bool showAll = false;
#endif // LTS_DEBUG
@@ -470,6 +500,29 @@ void BaseSymbolModel::initModel(SymbolDictionary *symbolDictionary,
bsi->setSymbolDictionary(symbolDictionary);
#ifdef LTS_DEBUG
// 300: fetch the Symbol asynchronously and attach it to the item when the future completes.
// fetchSymbolAsync() is non-const; copy the (const) search result to call it.
if (showAll)
{
Esri::ArcGISRuntime::SymbolStyleSearchResult sr(searchResult);
QFuture<Esri::ArcGISRuntime::Symbol *> fut = sr.fetchSymbolAsync(this);
if (fut.isValid())
{
QFutureWatcher<Esri::ArcGISRuntime::Symbol *> *watcher = new QFutureWatcher<Esri::ArcGISRuntime::Symbol *>(this);
watcher->setFuture(fut);
// 300/Qt6: connect to finished() and read the Symbol* via the result() getter (see above).
connect(watcher, &QFutureWatcher<Esri::ArcGISRuntime::Symbol *>::finished, watcher,
[bsi, watcher] {
Esri::ArcGISRuntime::Symbol *symbol = watcher->result();
if (symbol)
bsi->setEsriSearchSymbol(symbol);
watcher->deleteLater();
});
}
}
#endif // LTS_DEBUG
bsi->m_symbolProperties.setTags(searchResult.tags());
bsi->m_symbolProperties.setName(searchResult.name());
bsi->m_symbolProperties.setSymbolClass(searchResult.symbolClass());
@@ -141,11 +141,11 @@ Q_SIGNALS:
public Q_SLOTS:
// SEARCH_ICON:
void onEsriSearchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image);
void onEsriSearchSymbolSwatchCompleted(const QImage &image);
// FETCH_ICON:
void onEsriFetchCompleted(const QUuid &taskId, Esri::ArcGISRuntime::Symbol *symbol);
void onEsriFetchSymbolSwatchCompleted(const QUuid &taskId, const QImage &image);
void onEsriFetchCompleted(Esri::ArcGISRuntime::Symbol *symbol);
void onEsriFetchSymbolSwatchCompleted(const QImage &image);
};
//------------------------------------------------------------------------------
@@ -14,7 +14,6 @@
// ESRI
#include <TaskWatcher.h>
#include <Symbol.h>
#include <SymbolStyleSearchParameters.h>
#include <SymbolStyleSearchResultListModel.h>
@@ -25,6 +24,7 @@
#include <QtConcurrent>
#include <utility>
#include <QtCore/QFuture>
#include <QtCore/QFutureWatcher>
#define CREATE_ESRI_SYMBOL_ON_DEMAND // save time on startup
// REGG-3483 - EsriDictionaryProcessor::syncTableWithinMainThread verzögert Applikations-Start um mehrere Sekunden
@@ -46,6 +46,18 @@ EsriDictionaryProcessor::EsriDictionaryProcessor(SymbologyType symbologyType)
: m_logger(getLogger("EsriDictionaryProcessor")),
m_baseSymbolCodeToSymbolPropertyTable(symbologyType, SidcCodec::SidcType::Base)
{
// ArcGIS 300: the 100.x searchSymbolsCompleted signal / TaskWatcher is gone.
// searchSymbolsAsync() returns a QFuture; m_searchSymbolsWatcher delivers its result on
// this (main) thread via its 'result' signal -> onSearchSymbolsCompletedPropertyTable().
// 300/Qt6: the 'result' signal is name-collision-ambiguous with the result() getter (QOverload can't
// disambiguate); connect to the unambiguous finished() signal and read the value via the result()
// getter (the future is finished when finished() fires).
connect(&m_searchSymbolsWatcher,
&QFutureWatcher<Esri::ArcGISRuntime::SymbolStyleSearchResultListModel*>::finished,
this, [this] {
onSearchSymbolsCompletedPropertyTable(m_searchSymbolsWatcher.result());
});
TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " ThreadId=" << QThread::currentThreadId();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
@@ -94,16 +106,16 @@ void EsriDictionaryProcessor::searchSymbols()
// 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();
// ArcGIS 300: no TaskWatcher / searchSymbolsCompleted — searchSymbolsAsync() returns a
// QFuture; m_searchSymbolsWatcher (connected in the constructor) delivers the result on the
// main thread via its 'result' signal -> onSearchSymbolsCompletedPropertyTable().
m_searchSymbolsWatcher.setFuture(m_esri_dictionary->searchSymbolsAsync(sssp));
if (m_searchSymbolsWatcher.future().isValid())
DEBUG_LOG(m_logger) << LOGFUNCTION << " searchSymbolsAsync future valid";
else
ERROR_LOG(m_logger) << LOGFUNCTION << " TaskWatcher invalid" << " ThreadId=" << QThread::currentThreadId();
ERROR_LOG(m_logger) << LOGFUNCTION << " searchSymbolsAsync future invalid" << " ThreadId=" << QThread::currentThreadId();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
@@ -115,7 +127,8 @@ void EsriDictionaryProcessor::startBaseSymbolCodeToPropertyTable()
// A-sync:
// (slot is a pure virtual method)
m_baseSymbolCodeToSymbolPropertyTableWatcher = QtConcurrent::run(this, &EsriDictionaryProcessor::processBaseSymbolCodeToPropertyTable);
// Qt6: QtConcurrent::run(context, &method) is gone; pass the member fn first, then the receiver.
m_baseSymbolCodeToSymbolPropertyTableWatcher = QtConcurrent::run(&EsriDictionaryProcessor::processBaseSymbolCodeToPropertyTable, this);
// Sync: // processBaseSymbolCodeToPropertyTable();
@@ -129,7 +142,8 @@ void EsriDictionaryProcessor::startModifierTable()
// A-sync:
// (slot is a pure virtual method)
m_modifierTableWatcher = QtConcurrent::run(this, &EsriDictionaryProcessor::processModifierTable);
// Qt6: QtConcurrent::run(context, &method) is gone; pass the member fn first, then the receiver.
m_modifierTableWatcher = QtConcurrent::run(&EsriDictionaryProcessor::processModifierTable, this);
// Sync: // processModifierTable();
@@ -184,9 +198,9 @@ bool EsriDictionaryProcessor::allJobsDone() const
// Job n
}
void EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable(QUuid, Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults)
void EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable(Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults)
{
// ssTaskId can be ignored during initializing phase:
// ArcGIS 300: the 100.x ssTaskId (task uuid) no longer exists; the future is 1:1 with this call.
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
// save:
@@ -196,9 +210,7 @@ void EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable(QUuid, Esri:
// cleanup:
searchResults->deleteLater();
// disconnect:
disconnect(m_esri_dictionary, &Esri::ArcGISRuntime::DictionarySymbolStyle::searchSymbolsCompleted,
this, &EsriDictionaryProcessor::onSearchSymbolsCompletedPropertyTable);
// (no disconnect needed: 300 has no dict signal; the QFutureWatcher fires once per setFuture)
// method 'symbol' has to be called (created) within mainThread:
syncTableWithinMainThread();
@@ -330,45 +342,91 @@ QSharedPointer<SymbolResult> EsriDictionaryProcessor::symbolPropertyImage(QShare
if (eSym.swatchImage.isNull())
{
// ArcGIS 300: createSwatch needs a Symbol. With CREATE_ESRI_SYMBOL_ON_DEMAND the Symbol is
// fetched on demand; fetchSymbolAsync() (100.x: symbol()) is async, so fetch it first and only
// then create the swatch. When the Symbol is already available, create the swatch directly.
// Both paths funnel into startSwatchCreation() (the createSwatchAsync part, unchanged from the
// 100.x logic): a per-request QFutureWatcher delivers the QImage on this (main) thread ->
// onEsriSymbolSwatchCompleted(), correlated with our own QUuid (the SDK no longer issues one).
EsriDictionaryProcessor *const self = const_cast<EsriDictionaryProcessor *>(this); // this method is const
auto startSwatchCreation = [self, symResult, originalKey]() {
auto it = self->m_originalKeyToEsriSymbol.find(originalKey);
if (it == self->m_originalKeyToEsriSymbol.end())
return;
const EsriSymbol &eSym = it.value();
#ifdef USE_ARCADE_SYMBOLOGY
QFuture<QImage> fut;
auto it2 = self->m_baseSymbolCodeToSymbolPropertyTable.find(originalKey);
if (it2 != self->m_baseSymbolCodeToSymbolPropertyTable.end() && (it2->geometryType() == GeometryType::Polygon) )
// area/Polygon -> request rectangle, otherwise quadratic swatch:
fut = eSym.esriSymbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_width, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
else
fut = eSym.esriSymbol->createSwatchAsync(CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_height, CREATE_SWATCH_PARAMETER.m_screenScale);
#else
QFuture<QImage> fut = eSym.esriSymbol->createSwatchAsync(CREATE_SWATCH_SCREEN_SCALE);
#endif // USE_ARCADE_SYMBOLOGY
if (fut.isValid())
{
const QUuid csTaskId = QUuid::createUuid();
DEBUG_LOG(self->m_logger) << LOGFUNCTION << " createSwatchAsync future valid: csTaskId=" << csTaskId;
self->m_symbolPropertyImage2symbolResult[csTaskId] = symResult.toWeakRef();
QFutureWatcher<QImage> *swatchWatcher = new QFutureWatcher<QImage>(self);
swatchWatcher->setFuture(fut);
// 300/Qt6: connect to finished() and read the QImage via the result() getter.
connect(swatchWatcher, &QFutureWatcher<QImage>::finished, swatchWatcher,
[self, csTaskId, swatchWatcher] {
self->onEsriSymbolSwatchCompleted(csTaskId, swatchWatcher->result());
swatchWatcher->deleteLater();
});
}
else
{
ERROR_LOG(self->m_logger) << LOGFUNCTION << " createSwatchAsync future invalid"
<< " ThreadId=" << QThread::currentThreadId();
}
};
#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???
// 300: symbol() (const, sync) is gone; fetchSymbolAsync() is non-const + async. Copy the
// (const) search result so the non-const fetchSymbolAsync() can be called on the copy.
Esri::ArcGISRuntime::SymbolStyleSearchResult sr(eSym.m_sssResult);
QFuture<Esri::ArcGISRuntime::Symbol *> fut = sr.fetchSymbolAsync(self);
if (fut.isValid())
{
QFutureWatcher<Esri::ArcGISRuntime::Symbol *> *fetchWatcher = new QFutureWatcher<Esri::ArcGISRuntime::Symbol *>(self);
fetchWatcher->setFuture(fut);
// 300/Qt6: connect to finished() and read the Symbol* via the result() getter.
connect(fetchWatcher, &QFutureWatcher<Esri::ArcGISRuntime::Symbol *>::finished, fetchWatcher,
[self, originalKey, startSwatchCreation, fetchWatcher] {
Esri::ArcGISRuntime::Symbol *symbol = fetchWatcher->result();
if (symbol)
{
auto it = self->m_originalKeyToEsriSymbol.find(originalKey);
if (it != self->m_originalKeyToEsriSymbol.end())
it.value().esriSymbol = symbol;
startSwatchCreation();
}
fetchWatcher->deleteLater();
});
}
else
{
ERROR_LOG(self->m_logger) << LOGFUNCTION << " fetchSymbolAsync future invalid"
<< " ThreadId=" << QThread::currentThreadId();
}
}
#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);
{
startSwatchCreation();
}
#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";
startSwatchCreation();
#endif // CREATE_ESRI_SYMBOL_ON_DEMAND
}
else
@@ -16,12 +16,14 @@
// Qt
#include <QtGui/QImage>
#include <QtCore/QFutureWatcher>
#include <QUuid> // QUuid used in slot signatures / map keys (Qt6: no transitive include)
// Esri
#include <DictionarySymbolStyle.h>
#include <SymbolStyleSearchResult.h> // Esri::ArcGISRuntime::SymbolStyleSearchResult used below (no transitive include)
#include <SymbolStyleSearchResultListModel.h> // complete type for the QFutureWatcher member (no transitive include)
@@ -158,7 +160,7 @@ Q_SIGNALS:
protected Q_SLOTS:
void onSearchSymbolsCompletedPropertyTable(QUuid /*ssTaskId*/, Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults);
void onSearchSymbolsCompletedPropertyTable(Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults);
void onEsriSymbolSwatchCompleted(const QUuid &csTaskId, const QImage &image);
@@ -177,6 +179,11 @@ protected:
QList<Esri::ArcGISRuntime::SymbolStyleSearchResult> m_searchResultList;
QList<Esri::ArcGISRuntime::SymbolStyleSearchResult> m_searchResultListValid;
// ArcGIS 300: async symbol search — replaces the removed 100.x TaskWatcher /
// searchSymbolsCompleted signal (searchSymbolsAsync() returns a QFuture<...>; the watcher
// delivers the result on this object's (main) thread).
QFutureWatcher<Esri::ArcGISRuntime::SymbolStyleSearchResultListModel*> m_searchSymbolsWatcher;
// originalKey
struct EsriSymbol
{
@@ -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() << "'"
@@ -226,7 +226,7 @@ protected Q_SLOTS:
void onFetchSymbolCompleted(QUuid fsTaskId, Esri::ArcGISRuntime::Symbol *symbol);
void onSwatchCompleted(const QUuid &csTaskId, const QImage &image);
void onSwatchCompleted(const QUuid &csTaskId, const QImage &image, Esri::ArcGISRuntime::Symbol *symbol);
private:
Esri::ArcGISRuntime::DictionarySymbolStyle *m_esri_dictionary = nullptr;