Files
qt-migration-legatus/qt6-after/qt6-migrated-legatus/MIGRATION_SUMMARY_ARCGIS300_SYMBOLOGY.md
T
berrux-dev 2b957d1edb 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.
2026-10-07 06:24:35 +02:00

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 removed qAsConst).
  • QRegExp → QRegularExpression; QRegExp::exactMatch(s) → QRegularExpression::match(s).hasMatch().
  • QString::count() → size(); leftRef/rightRef/midRef(n) → left/right/mid(n); split() now returns QStringList (Qt6 dropped the QStringView/QStringRef ref overloads) → QVector<QStringView> or QStringList; .toString() dropped where the element is already a QString (Esri dictionary processors).
  • QAbstractItemModel::invalidateFilter() → beginFilterChange()/endFilterChange() (Qt6 removed invalidateFilter()).
  • QLinkedList/QLinkedListIterator → STL std::list + range-for/iterators (append→push_back, remove→erase, toFront/toBack→erase+push_front/push_back).
  • LOGFUNCTION → Q_FUNC_INFO (no-op: INGLLogger.h defines LOGFUNCTION as Q_FUNC_INFO).
  • const QImage &x = …image() → const QImage x = …image() (Qt6 QImage::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 -w filters it out).

The four "verify carefully" items — resolutions

  1. Role_def → Role (symbol_data_to_render_role.h) — RESOLVED, 1:1. Legacy ppm_service.xml and migrated ppm_enums.idl both define Role with the identical 7 enumerators and values (Unknown=0 … Suspect=6). The Role_def name is a DDS-codegen artifact (IDL name is Role in both; legacy codegen emitted Role_def, rtiddsgen 7.7 emits Role). role.underlying() switch behaves identically; return values unchanged.

  2. #ifdef BUILD_WITH_EXTERNAL_DDS role-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_DDS is referenced only as #ifdef in 28 source files and is never defined (no #define, no CMake set/option/-D, empty CMAKE_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.

  3. Logger pattern — RESOLVED, migration-only. m_logger is a member of type LOGGER_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.)

  4. Filter API (base_symbol_model.cpp) — RESOLVED, 1:1. 4 legacy invalidateFilter() → 4 migrated beginFilterChange(); endFilterChange(); pairs (setValidBaseSymbolCodes, onFilterPoints/Lines/Areas); no leftover invalidateFilter(). BaseSymbolProxyModel is a QSortFilterProxyModel (via LtsColumnFilterProxyModel) that overrides filterAcceptsRow (unchanged); the changed members are plain QSet ops (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 defining ACRGIS_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> result signal/getter name collision. QFutureWatcher<T> declares both a signal void result(const T&) and a getter T result() const — same name, so &QFutureWatcher<T>::result is ambiguous, and QOverload<Args>(…) / qOverload<Args>(…) both fail (the getter poisons template deduction). Working form: connect to the unambiguous void finished() signal and read the value via the result() 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.x TaskWatcher completion signals gave.
  • Non-const fetchSymbolAsync. SymbolStyleSearchResult::fetchSymbolAsync(QObject*) is non-const (no const suffix, SymbolStyleSearchResult.h), so it can't be called on the const search result stored in the map. Fix: copy the (cheap, shared_ptr-backed) SymbolStyleSearchResult into a non-const local and call fetchSymbolAsync on 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-generated QUuid (csTaskId); onEsriSymbolSwatchCompleted is otherwise unchanged. Symbol::symbol() (100.x const/sync) is gone → fetchSymbolAsync (async) under CREATE_ESRI_SYMBOL_ON_DEMAND.
  • QtConcurrent::run(renderExtensions, const QImage&, …) → QFuture<void>. renderExtensions takes a non-const QImage& (it scales / replaces color in place); passing a const QImage& hit Qt6's QPromise overload → QFuture<void> (unassignable to QFuture<QImage>). Fix: wrap in a lambda returning QImage that passes a local non-const copy (a QImage copy is a shallow implicit-share refcount bump; the one deep copy is renderExtensions' own scaled(), exactly as 100.x mutated QtConcurrent::run's stored copy).
  • DictionarySymbolStyle(QString) / (QString, QString) ctors removed → DictionarySymbolStyle::createFromFile(path) (local .stylx); empty/missing path returns false (mirrors the ARCADE branch; 300 has no built-in spec-only dict).
  • Error::code() removed → error.arcGISServerErrorCode().value_or(-1).
  • LoadStatus / Error incomplete → #include <MapTypes.h> / #include <Error.h> (300 LoadStatus is an enum class in MapTypes.h; Loadable.h only forward-declares it).
  • arcgis/arcgisversion.h (custom RME header) dropped by 300 → a shim arcgisversion.h was added to the 300 SDK include dir (external/ArcGIS/…/sdk/include/, so every ArcGIS::ArcGIS consumer finds it) defining ACRGIS_VERSION = 300000000 (> every 100.x 0x64xxxx gate, so each #if ACRGIS_VERSION >= 0x64xxxx takes the "newest SDK" branch; the single #if ACRGIS_VERSION < 0x640500 correctly takes its else).
  • Syntax fix: a // Qt6: leftRef removed comment had swallowed the trailing ); of two castToSymbolSet(… .left(2).toUInt()) statements (base_symbol_model.cpp); restructured so the parens close before the comment.

Build results

  • Pure-Qt symbology (green): situationMapCommon and advancedSymbologyXML build and link (in build4 and reconfirmed under the ArcGIS-ON configure). These carry no ArcGIS dependency.
  • ArcGIS-ON configure (green): BUILD_RTI=ON BUILD_GRANTLEE=ON BUILD_ARCGIS=ON configures successfully; FindArcGIS.cmake resolves 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 (Symbology SIDC codec + SymbologyImpl Esri processors, links ArcGIS::ArcGIS) compiles and links — libadvancedSymbology.so built 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). Full migration_tests suite: 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 7 LtsWidgetsQt6.* pass under QT_QPA_PLATFORM=offscreen, confirmed 10/10). None touch the symbology family.

Remaining issues / risks

  • RESOLVED — SymbologyImpl 100.x ArcGIS task API (was the blocker). The three SymbologyImpl files previously used the 100.x TaskWatcher task API (searchSymbols/createSwatch/fetchSymbol → TaskWatcher + completion signals), which the 300 SDK removed. They were ported to the 300 QFuture async model (see "ArcGIS 300 QFuture port" above) and now compile and link against the 300 SDK (advancedSymbology green). 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.txt has ADD_SUBDIRECTORY(Map), MapImpl, MapObjects commented out; no Map/CMakeLists.txt. The ArcGIS-300 Map integration (beyond include paths — Viewpoint/Camera/MapTypes 300 API changes) is a separate, not-yet-complete migration and is not in the current green build.

  • BUILD_WITH_EXTERNAL_DDS never defined (see item 2): the DDS roleToRole/ ForeignInterfaceImpl real 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 defined ACRGIS_VERSION for the #if ACRGIS_VERSION >= … version gates). A shim arcgisversion.h was 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.