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.
14 KiB
ArcGIS 300 Symbology Migration Summary
Qt5→Qt6 + ArcGIS Runtime 100.x→ArcGIS Maps SDK for Qt 300.0.0 migration audit of the
SIDC/Esri symbology subsystem. Every change was compared against the legacy tree
(qt5-before/qt5-legatus-legacy); the goal is migration-only (no behavior change).
ArcGIS 300 SDK at external/ArcGIS/MapsSDK_Qt_300.0.0/.../qt300.0.0/sdk (flat include
layout — all headers directly in sdk/include/, no arcgis/ subdirectory).
Scope audited
Lts/Viewer/SituationMap/Symbology/— SIDC codec/builder/dictionary/definitions (pure Qt).Lts/Viewer/SituationMap/SymbologyImpl/—esri_dictionary_processor*.cpp,base_symbol_model.*,symbol_dictionary_impl.*,advanced_symbology_logging.*.Lts/Viewer/SituationMap/SymbologyXML/— symbol XML read/write/export.Lts/Viewer/SituationMap/Map/— the ArcGIS map layer (57 files). Excluded from the build (see Remaining Issues).Lts/Plugins/AdvancedSymbology/—asb_data_plugin.*,asb_foreign_interface_impl.*,Gui/SymbolEditor/*,Gui/SymbolProxy/*.Lts/Services/CentralEffectSimulation/Plugins/Explosions/expand_polygon.cpp.
Migration-only pattern classes found
All in-scope changes classified as the following approved adaptations (no semantic change):
- Qt include path churn:
#include <QX>→#include <QtCore/QX>(Gui/Widgets/Network/Sql/…). qAsConst(x)→std::as_const(x)(Qt6 removedqAsConst).QRegExp→QRegularExpression;QRegExp::exactMatch(s)→QRegularExpression::match(s).hasMatch().QString::count()→size();leftRef/rightRef/midRef(n)→left/right/mid(n);split()now returnsQStringList(Qt6 dropped theQStringView/QStringRefref overloads) →QVector<QStringView>orQStringList;.toString()dropped where the element is already aQString(Esri dictionary processors).QAbstractItemModel::invalidateFilter()→beginFilterChange()/endFilterChange()(Qt6 removedinvalidateFilter()).QLinkedList/QLinkedListIterator→ STLstd::list+ range-for/iterators (append→push_back,remove→erase,toFront/toBack→erase+push_front/push_back).LOGFUNCTION→Q_FUNC_INFO(no-op:INGLLogger.hdefinesLOGFUNCTIONasQ_FUNC_INFO).const QImage &x = …image()→const QImage x = …image()(Qt6QImage::image()by value).- DDS-generated type rename
ParticipantManagement::Role_def::*→Role::*(codegen artifact). - ArcGIS include path: old 100.x
<arcgis/X.h>→ flat 300<X.h>(see below). - Systematic 2-space→4-space re-indentation (whitespace-only;
diff -wfilters it out).
The four "verify carefully" items — resolutions
-
Role_def→Role(symbol_data_to_render_role.h) — RESOLVED, 1:1. Legacyppm_service.xmland migratedppm_enums.idlboth defineRolewith the identical 7 enumerators and values (Unknown=0 … Suspect=6). TheRole_defname is a DDS-codegen artifact (IDL name isRolein both; legacy codegen emittedRole_def, rtiddsgen 7.7 emitsRole).role.underlying()switch behaves identically; return values unchanged. -
#ifdef BUILD_WITH_EXTERNAL_DDSrole-conversion stub — RESOLVED with a flagged delta. (a) Real path = legacy 1:1 (constructor +roleToRole<>(party.role())identical). (c) Stub documented (// STUB: No external DDS …), intentional migration scaffolding. (b) FLAGGED:BUILD_WITH_EXTERNAL_DDSis referenced only as#ifdefin 28 source files and is never defined (no#define, no CMakeset/option/-D, emptyCMAKE_CXX_FLAGS). So the no-op stub path is what compiles in the current build — a runtime behavior change vs legacy. This is codebase-wide (28 files) and pre-existing in the migration (not introduced by the ArcGIS-300 symbology work); the stub is NOT "fixed" (out of scope) — surfaced as a behavior delta. -
Logger pattern — RESOLVED, migration-only.
m_loggeris a member of typeLOGGER_T(typedef NGL::INGLLogger &LOGGER_T;— a reference) in both trees, initialized in the constructor (getLogger("SymbolDictionaryImpl"/"EsriDictionaryProcessor"/ "BaseSymbolModel")— names unchanged). A reference has no ownership → no double-allocation/lifetime/init-order issue. (The "local→member" premise was inaccurate; reality: unchanged.) -
Filter API (
base_symbol_model.cpp) — RESOLVED, 1:1. 4 legacyinvalidateFilter()→ 4 migratedbeginFilterChange(); endFilterChange();pairs (setValidBaseSymbolCodes,onFilterPoints/Lines/Areas); no leftoverinvalidateFilter().BaseSymbolProxyModelis aQSortFilterProxyModel(viaLtsColumnFilterProxyModel) that overridesfilterAcceptsRow(unchanged); the changed members are plainQSetops (no model signals), so the back-to-back begin/end still triggers the proxy re-filter reading the updated data. Behavior preserved.
Esri dictionary processors — dictionary equivalence
The Qt6 API churn in esri_dictionary_processor{,_app6,_app6d,_mil2525d}.cpp
(splitRef→split, rightRef→right, .toString() removal) is migration-only.
The three SymbologyImpl files that use the ArcGIS symbol-search/swatch/fetch API —
esri_dictionary_processor.cpp, symbol_dictionary_impl.cpp, base_symbol_model.cpp —
were ported from the 100.x TaskWatcher signal/slot model to the 300 QFuture async
model (see "ArcGIS 300 QFuture port" below) and now compile and link against the 300
SDK (see Build results).
ArcGIS 300 include-style reconciliation
The 300 SDK is flat (FIND_PATH(ARCGIS_INCLUDE_DIR ArcGISQt_global.h …) in
FindArcGIS.cmake finds it directly in sdk/include/), so the correct form is bare
(#include <Viewpoint.h>), not <arcgis/Viewpoint.h>. The migration already dropped the
prefix in the Esri processors; the Map layer + expand_polygon still carried it. The
11 arcgis/-prefixed includes were reconciled to bare (one consistent form):
- 10 ESRI headers (
SceneViewTypes,Error,CoreTypes,MapTypes,Viewpoint,Camera,PolygonBuilder,GeometryEngine) — exist flat in the SDK, resolve. - 3
arcgisversion.h(custom RME header definingACRGIS_VERSION, "created by RME, not ESRI") — made bare for consistency; the header itself is absent from the repo (pre-existing gap; see Remaining Issues).
Files touched: Map/situation_map_utils.{h,cpp}, Map/map_initializer.cpp,
Map/layer_factory.h, Explosions/expand_polygon.cpp.
ArcGIS 300 QFuture port (SymbologyImpl)
The 100.x ArcGIS Runtime exposed an async task API (TaskWatcher + per-task completion
signals); the 300 SDK replaced it with futures (QFuture<T> + QFutureWatcher<T>).
The three SymbologyImpl files were rewritten from the task model to the future model.
This is a semantic change — the approved "blocking work item" (chosen by the user) — and
every mapping below preserves the 100.x behavior (same work, same main-thread delivery, same
completion order).
| 100.x (task API) | 300 (future API) |
|---|---|
searchSymbols(params) → TaskWatcher + searchSymbolsCompleted |
searchSymbolsAsync(params) → QFuture<SymbolStyleSearchResultListModel*> |
SymbolStyleSearchResult::symbol(QObject*) → Symbol* (sync) |
fetchSymbolAsync(QObject*) → QFuture<Symbol*> |
Symbol::createSwatch(w,h,scale) → TaskWatcher + createSwatchCompleted |
createSwatchAsync(...) → QFuture<QImage> |
DictionarySymbolStyle::fetchSymbol(map) → TaskWatcher + fetchSymbolCompleted |
fetchSymbolAsync(map, QObject*) → QFuture<Symbol*> |
QtConcurrent::run(this, &Class::method) |
QtConcurrent::run(&Class::method, this) (Qt6: member fn first, receiver second) |
Behavior-preserving details (each verified against the 300 headers):
QFutureWatcher<T>resultsignal/getter name collision.QFutureWatcher<T>declares both a signalvoid result(const T&)and a getterT result() const— same name, so&QFutureWatcher<T>::resultis ambiguous, andQOverload<Args>(…)/qOverload<Args>(…)both fail (the getter poisons template deduction). Working form: connect to the unambiguousvoid finished()signal and read the value via theresult()getter (connect(w, &QFutureWatcher<T>::finished, w, [w]{ auto v = w->result(); …; w->deleteLater(); });).finished()fires on the watcher's (main) thread once the future is done — the same main-thread delivery the 100.xTaskWatchercompletion signals gave.- Non-const
fetchSymbolAsync.SymbolStyleSearchResult::fetchSymbolAsync(QObject*)is non-const (noconstsuffix,SymbolStyleSearchResult.h), so it can't be called on theconstsearch result stored in the map. Fix: copy the (cheap, shared_ptr-backed)SymbolStyleSearchResultinto a non-const local and callfetchSymbolAsyncon the copy. - Swatch future correlation. 100.x correlated a swatch to its request via the SDK-issued task
id; a 300 future carries no id. Fix: the per-request
QFutureWatcher<QImage>lambda captures a caller-generatedQUuid(csTaskId);onEsriSymbolSwatchCompletedis otherwise unchanged.Symbol::symbol()(100.x const/sync) is gone →fetchSymbolAsync(async) underCREATE_ESRI_SYMBOL_ON_DEMAND. QtConcurrent::run(renderExtensions, const QImage&, …)→QFuture<void>.renderExtensionstakes a non-constQImage&(it scales / replaces color in place); passing aconst QImage&hit Qt6'sQPromiseoverload →QFuture<void>(unassignable toQFuture<QImage>). Fix: wrap in a lambda returningQImagethat passes a local non-const copy (aQImagecopy is a shallow implicit-share refcount bump; the one deep copy isrenderExtensions' ownscaled(), exactly as 100.x mutatedQtConcurrent::run's stored copy).DictionarySymbolStyle(QString)/(QString, QString)ctors removed →DictionarySymbolStyle::createFromFile(path)(local.stylx); empty/missing path returnsfalse(mirrors the ARCADE branch; 300 has no built-in spec-only dict).Error::code()removed →error.arcGISServerErrorCode().value_or(-1).LoadStatus/Errorincomplete →#include <MapTypes.h>/#include <Error.h>(300LoadStatusis anenum classinMapTypes.h;Loadable.honly forward-declares it).arcgis/arcgisversion.h(custom RME header) dropped by 300 → a shimarcgisversion.hwas added to the 300 SDK include dir (external/ArcGIS/…/sdk/include/, so everyArcGIS::ArcGISconsumer finds it) definingACRGIS_VERSION = 300000000(> every 100.x0x64xxxxgate, so each#if ACRGIS_VERSION >= 0x64xxxxtakes the "newest SDK" branch; the single#if ACRGIS_VERSION < 0x640500correctly takes its else).- Syntax fix: a
// Qt6: leftRef removedcomment had swallowed the trailing);of twocastToSymbolSet(… .left(2).toUInt())statements (base_symbol_model.cpp); restructured so the parens close before the comment.
Build results
- Pure-Qt symbology (green):
situationMapCommonandadvancedSymbologyXMLbuild and link (inbuild4and reconfirmed under the ArcGIS-ON configure). These carry no ArcGIS dependency. - ArcGIS-ON configure (green):
BUILD_RTI=ON BUILD_GRANTLEE=ON BUILD_ARCGIS=ONconfigures successfully;FindArcGIS.cmakeresolves the 300 SDK (Found ArcGIS: .../sdk/linux/x64/lib/libEsriRuntimeQt.a). - ArcGIS-ON build of
advancedSymbology(green): after the QFuture port, the ArcGIS-dependent target (SymbologySIDC codec +SymbologyImplEsri processors, linksArcGIS::ArcGIS) compiles and links —libadvancedSymbology.sobuilt with 0 errors (build log/tmp/arcgis300_targeted_build5.log). - CTest (green for this change): the 6 symbology-family
SymbologyXML.*tests pass (ctest -R SymbologyXML→ 6/6). Fullmigration_testssuite: 604/644 pass, 0 actual assertion failures — the 40 non-passing are 31 "Not Run" (unbuilt test exes of unrelated modules — ngl/dds/aes/bld_/taboc/wls/LtsDataView/Grantlee — a pre-existing build-scope gap, not symbology) and 9 "Subprocess aborted" (display-dependent GUI tests; the 7LtsWidgetsQt6.*pass underQT_QPA_PLATFORM=offscreen, confirmed 10/10). None touch the symbology family.
Remaining issues / risks
-
RESOLVED —
SymbologyImpl100.x ArcGIS task API (was the blocker). The threeSymbologyImplfiles previously used the 100.xTaskWatchertask API (searchSymbols/createSwatch/fetchSymbol→TaskWatcher+ completion signals), which the 300 SDK removed. They were ported to the 300QFutureasync model (see "ArcGIS 300 QFuture port" above) and now compile and link against the 300 SDK (advancedSymbologygreen). This was a semantic change — the approved "blocking work item" chosen by the user — superseding the earlier note that it was "out of scope for a verify-only task." -
Map layer excluded:
SituationMap/CMakeLists.txthasADD_SUBDIRECTORY(Map),MapImpl,MapObjectscommented out; noMap/CMakeLists.txt. The ArcGIS-300 Map integration (beyond include paths —Viewpoint/Camera/MapTypes300 API changes) is a separate, not-yet-complete migration and is not in the current green build. -
BUILD_WITH_EXTERNAL_DDSnever defined (see item 2): the DDSroleToRole/ForeignInterfaceImplreal path is dead code; the no-op stub compiles. Behavior delta vs legacy, codebase-wide, pre-existing in the migration. -
arcgisversion.h(custom RME header) — RESOLVED: the 300 SDK dropped this header (it definedACRGIS_VERSIONfor the#if ACRGIS_VERSION >= …version gates). A shimarcgisversion.hwas added to the 300 SDK include dir (external/ArcGIS/…/sdk/include/,ACRGIS_VERSION = 300000000) so every gate takes the "newest SDK" branch (see the QFuture-port section). It lives in the external SDK tree (not the Legatus repo) because it is a drop-in replacement for the SDK-shipped header.