SWMMVis  6.0.0-alpha.4
Qt6/C++ GIS-based graphical user interface for the SWMMVis engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
swmmresultslayer.h
Go to the documentation of this file.
1
10#ifndef SWMMRESULTSLAYER_H
11#define SWMMRESULTSLAYER_H
12
15#include "layers/gisrasterlayer.h" // RasterColorRamp
17
18#include <openswmm/engine/openswmm_output.h>
19
20#include <QBrush>
21#include <QColor>
22#include <QDateTime>
23#include <QHash>
24#include <QPen>
25#include <QSet>
26#include <QString>
27#include <QVariantMap>
28#include <QVector>
29
30#include <memory>
31
34class QGraphicsItem; // Slice §Y.2 — m_itemByFeature stores QGraphicsItem*
35class QGraphicsSimpleTextItem; // L-1 — m_labelByFeature stores label items
36
37// Slice U-0 — granular per-Category sublayer mix. The legacy four
38// sublayers (NodeMarker / ConduitLine / ConduitArrow / SubcatchmentFill)
39// are replaced by one FeatureSublayer instance per SWMMModelLayer::Category
40// (11 in total). Each instance owns its own archetype-appropriate
41// style bag so every visual object type can be styled independently.
42#include "render/iattributeprovider.h" // Slice DM.1
45
46namespace OpenSWMM::Render {
47class IFeatureRenderer;
48class RuleList; // Slice B.5 — see ruleList() override below.
49enum class ClassEditKind; // Gap B1 — legend facade (full def in ifeaturerenderer.h)
50}
51
57{
58 // Node results
59 NodeDepth = 0,
60 NodeHead = 1,
61 NodeVolume = 2,
62 NodeInflow = 3,
63 NodeOverflow = 4,
65 // Link results
66 LinkFlow = 10,
67 LinkDepth = 11,
68 LinkVelocity = 12,
69 LinkCapacity = 13,
70 // Subcatchment results
71 SubcatchRunoff = 20,
75};
76
95 public OpenSWMM::Render::IAttributeProvider // Slice DM.1
96{
97 Q_OBJECT
98 Q_INTERFACES(OpenSWMM::Render::IAttributeProvider) // Slice DM.1
99
101 Q_PROPERTY(QString scenarioName READ scenarioName WRITE setScenarioName
102 NOTIFY scenarioNameChanged)
111 // EGL renders as a line only (no physically-meaningful fill above HGL).
123 NOTIFY variableChanged)
124 Q_PROPERTY(bool showLegend READ showLegend WRITE setShowLegend
125 NOTIFY showLegendChanged)
126
127public:
128
129 explicit SWMMResultsLayer(const QString &resultsFilePath,
131 OpenSWMMVisWorkspace *parent = nullptr);
132
133 ~SWMMResultsLayer() override;
134
138 [[nodiscard]] class SWMMModelLayer *modelLayer() const { return m_modelLayer; }
139
140 // ----- Results file ---------------------------------------------------
141
142 [[nodiscard]] QString resultsFilePath() const;
143
151 [[nodiscard]] QString reportFilePath() const;
152 void setReportFilePath(const QString &path);
153
154 // ----- Self-description for the Layer Properties dialog ----------------
155 [[nodiscard]] QString sourceDescription() const override;
156 [[nodiscard]] QVector<QPair<QString, QString>> extendedMetadata() const override;
157
162 [[nodiscard]] QString scenarioName() const;
163 void setScenarioName(const QString &name);
164
172 [[nodiscard]] QColor profileLineColor() const;
173 void setProfileLineColor(const QColor &color);
174
175 // -------------------------------------------------------------------
176 // Per-source styling for Slice BC's profile plot.
177 //
178 // Each result layer (think "model run" or "scenario") carries its own
179 // pen+brush for the four overlay layers — Current HGL, Current EGL,
180 // Max HGL, Max EGL — so two layers plotted side by side can be styled
181 // independently (e.g. baseline solid blue vs. proposed dashed red).
182 //
183 // Defaults are derived from profileLineColor() at construction.
184 // setProfileLineColor() does NOT re-derive them: once the user
185 // customises a pen, it stays put across colour changes. Each setter
186 // emits the matching changed() signal; the dialog uses that to
187 // refresh its preview swatch.
188 // -------------------------------------------------------------------
189 [[nodiscard]] QPen profileHglLinePen() const;
190 [[nodiscard]] QBrush profileHglFillBrush() const;
191 [[nodiscard]] QPen profileEglLinePen() const;
192 [[nodiscard]] QPen profileMaxHglLinePen() const;
193 [[nodiscard]] QBrush profileMaxHglFillBrush() const;
194 [[nodiscard]] QPen profileMaxEglLinePen() const;
195
196 void setProfileHglLinePen (const QPen &pen);
197 void setProfileHglFillBrush (const QBrush &brush);
198 void setProfileEglLinePen (const QPen &pen);
199 void setProfileMaxHglLinePen (const QPen &pen);
200 void setProfileMaxHglFillBrush(const QBrush &brush);
201 void setProfileMaxEglLinePen (const QPen &pen);
202
217 void writeProfileStyle(class QSettings &settings) const;
218
225 void readProfileStyle(class QSettings &settings);
226
231 bool openResults(QList<QString> &warnings, QList<QString> &errors);
232
239 void openResultsAsync();
240
251 bool openResultsLive(QList<QString> &errors);
252
261 int refreshLive();
262
264 [[nodiscard]] bool isLive() const noexcept { return m_live; }
265
266 void closeResults();
267
268 // ----- Per-output node summary statistics (Slice QA.3 + QA-01) -------
269 //
270 // These four accessors return the same statistics the editing-engine
271 // exposes via `swmm_node_get_stat_*`, but sourced from THIS output
272 // file rather than the editing engine's ambient state. The node
273 // attribute panel (Slice QA.2) dispatches to one of these when the
274 // user picks a non-null "Stats source" from the combo, so multiple
275 // loaded SWMMResultsLayers can be compared in the same panel.
276 //
277 // Backed by engine API QA-01 (`swmm_output_get_node_stat_*` in
278 // openswmm_output.h), which aggregates the four flooding stats
279 // on-demand from the .out file's per-period node results. The
280 // aggregation runs at REPORT-step resolution — see the QA-01
281 // header doc for the precision caveat when the routing step is
282 // much finer than the report step.
283 //
284 // \p nodeName is the SWMM model-side identifier (e.g. "J1"); the
285 // implementation resolves it to the output-side index via the
286 // already-built nodeOutputIndex() map. Returns 0.0 for unknown
287 // names or when the underlying file is closed.
288 //
289 // Each aggregator is one O(n_periods) walk of the .out file, so all
290 // four are memoised per node index (see m_nodeStats / nodeStatsFor,
291 // cleared by closeResults()) — same reason and same shape as the
292 // link / subcatchment caches below.
293 [[nodiscard]] double nodeStatMaxDepth(const QString &nodeName) const;
294 [[nodiscard]] double nodeStatMaxOverflow(const QString &nodeName) const;
295 [[nodiscard]] double nodeStatVolFlooded(const QString &nodeName) const;
296 [[nodiscard]] double nodeStatTimeFlooded(const QString &nodeName) const;
297
298 // ----- Per-output link / subcatchment summary statistics -------------
299 //
300 // Peers of the four node accessors above, for the dynamics columns the
301 // Attribute Table shows for links and subcatchments. Unlike the node
302 // stats there is no `swmm_output_get_*_stat_*` engine API for these, so
303 // they are aggregated HERE by walking the .out file's per-period series
304 // once per object. The result is cached per output index (see
305 // m_linkStats / m_subcatchStats, cleared by closeResults()), as are the
306 // node stats above.
307 //
308 // PRECISION CAVEAT — identical in kind to the QA-01 node aggregators:
309 // the walk sees REPORT steps, not routing steps. Maxima are therefore
310 // the largest REPORTED value (a peak between two report times is
311 // missed), and the volume / duration integrals use the report step as
312 // dt. Expect small disagreement with the .rpt summary tables when
313 // REPORT_STEP is much coarser than ROUTING_STEP; they converge as the
314 // two approach each other.
315 //
316 // Units match the editing-engine getters they stand in for: flow and
317 // velocity in project flow / velocity units, filling dimensionless,
318 // volumes in project volume units, durations in hours. Volume is the
319 // one that needs work — the .out file stores a RATE in the file's own
320 // flow units, so the integral goes through Qcf/Ucf (see the .cpp).
321 //
322 // The three pump statistics reconstruct too, from a combination that is
323 // not obvious: the engine's on/off test is `setting > 0 && flow > 0`, and
324 // the .out file carries BOTH — flow directly, and `setting` as the
325 // CAPACITY variable, which for every non-conduit link is the pump speed /
326 // regulator opening rather than a fill ratio (legacy link.c). See the
327 // extra accuracy note on linkStatPumpCycles.
328 [[nodiscard]] double linkStatMaxFlow(const QString &linkName) const;
329 [[nodiscard]] double linkStatMaxVelocity(const QString &linkName) const;
330 [[nodiscard]] double linkStatMaxFilling(const QString &linkName) const;
331 [[nodiscard]] double linkStatVolFlow(const QString &linkName) const;
332 [[nodiscard]] double linkStatSurchargeTime(const QString &linkName) const;
333
344 [[nodiscard]] double linkStatPumpCycles(const QString &linkName) const;
348 [[nodiscard]] double linkStatPumpOnTime(const QString &linkName) const;
349 [[nodiscard]] double linkStatPumpVolume(const QString &linkName) const;
350
351 [[nodiscard]] double subcatchStatPrecip(const QString &subName) const;
352 [[nodiscard]] double subcatchStatRunoffVol(const QString &subName) const;
353 [[nodiscard]] double subcatchStatMaxRunoff(const QString &subName) const;
354
355 // ----- Animation ------------------------------------------------------
356
357 [[nodiscard]] int currentTimeStep() const;
358 [[nodiscard]] QDateTime currentDateTime() const;
359 [[nodiscard]] int totalTimeSteps() const;
360 [[nodiscard]] QDateTime startDateTime() const;
361 [[nodiscard]] QDateTime endDateTime() const;
362 // 2026-07-19 — reported data origin (slider range fix). startDateTime()
363 // is the SIMULATION start (`swmm_output_get_start_date`), but the first
364 // data frame lives at period 0's report time = reportStart + reportStep.
365 // Anchoring the animation range / period grid on the simulation start
366 // produced a dead zone at the left of the global slider whenever
367 // REPORT_START != START_DATE (and an off-by-one in
368 // periodIndexForDateTime). Falls back to startDateTime() when period 0's
369 // time is unavailable.
370 [[nodiscard]] QDateTime reportedStartDateTime() const;
371 [[nodiscard]] int reportStepSeconds() const;
372
376 void setCurrentTimeStep(int step);
377
383 [[nodiscard]] int periodIndexForDateTime(const QDateTime &dt) const;
384
391 [[nodiscard]] int periodIndexForDateTimeAsOf(const QDateTime &dt) const;
392
396 void stepForward(bool loop = false);
397
401 void stepBackward(bool loop = false);
402
403 // ----- Variable & colour mapping --------------------------------------
404
405 [[nodiscard]] SWMMResultVariable variable() const;
407
408 [[nodiscard]] RasterColorRamp colorRamp() const;
409 void setColorRamp(const RasterColorRamp &ramp);
410
416
417 // ----- Renderer (Slice BI Phase 8.13.6.3) -----------------------------
418 // The renderer is the §J.2 seam every future paint path will go through.
419 // In sub-phase 8.13.6.3 this is API plumbing only — the existing paint
420 // loop still reads m_colorRamp / m_variable directly. Sub-phase 8.13.6.4
421 // will swap the paint loop to consult m_renderer instead, after which
422 // m_colorRamp / m_variable become private implementation details of the
423 // default GraduatedRenderer.
424
433 [[nodiscard]] OpenSWMM::Render::IFeatureRenderer *renderer() const;
434
442 void setRenderer(std::unique_ptr<OpenSWMM::Render::IFeatureRenderer> r);
443
444 // ----- Per-kind renderers (Slice OUT.1) -------------------------------
445 // Mirrors SWMMModelLayer's per-kind renderer slots so the user can
446 // style Junctions vs Outfalls (etc.) independently for the same
447 // NodeDepth/LinkFlow variable. The layer-level renderer() above stays
448 // as the fallback when no kind-renderer is installed.
449
461
469 std::unique_ptr<OpenSWMM::Render::IFeatureRenderer> r);
470
477
485 [[nodiscard]] bool kindRendererIsDefault(SWMMModelLayer::Category c) const;
486
496
497 // ----- Gap B1 — legend-as-editor facade (mirrors SWMMModelLayer) -----
498 //
499 // The legend views read rows through legendSymbolItems() and route
500 // per-class edits through the facade below. Class keys are
501 // kind-qualified ("<kindKey>\x1F<innerKey>") so edits land on the
502 // right kind renderer even when two kinds share inner keys.
503
504 [[nodiscard]] QList<OpenSWMM::Render::LegendSymbolItem> legendSymbolItems();
506 [[nodiscard]] QColor colorForClass(const QString &classKey) const;
507 void setColorForClass(const QString &classKey, const QColor &color);
508 [[nodiscard]] qreal sizeForClass(const QString &classKey) const;
509
510 // ----- Rule Model (Slice B.5, Phase B) --------------------------------
511 //
512 // Mirrors SWMMModelLayer::ruleList() — 11 kindRenderer slots → 11
513 // Rules, lazy-built on first access. Each Rule owns a clone of the
514 // matching kindRenderer; Rule-side swaps propagate back via
515 // setKindRenderer. Same one-way sync caveat as B.4 — external
516 // setKindRenderer calls don't refresh the in-memory RuleList.
517 [[nodiscard]] OpenSWMM::Render::RuleList *ruleList() override;
518 [[nodiscard]] const OpenSWMM::Render::RuleList *ruleList() const override;
519
520 // ----- IAttributeProvider (Slice DM.1) --------------------------------
521 //
522 // Returns the engine output codes available for the given category.
523 // All entries are isDynamic=true (per-frame values). Drives the
524 // attribute combo in the Graduated / Categorized renderer panels —
525 // see RENDERING_DIALOG_DEMO_PLAN.md §2.
526 [[nodiscard]] QVector<OpenSWMM::Render::AttributeField>
528
535 [[nodiscard]] QStringList speciesNames() const;
536
537 // ----- Legend ---------------------------------------------------------
538
539 [[nodiscard]] bool showLegend() const;
540 void setShowLegend(bool show);
541
542 // ----- Bulk output access (Slice BC) ----------------------------------
543
551 [[nodiscard]] SWMM_Output outputHandle() const;
552
559 [[nodiscard]] int nodeOutputIndex(const QString &name) const;
560
564 [[nodiscard]] int linkOutputIndex(const QString &name) const;
565
571 [[nodiscard]] int subcatchOutputIndex(const QString &name) const;
572
578 [[nodiscard]] int flowUnits() const;
579
580 // ----- OpenSWMMVisLayer interface -----------------------------------------
581
582 void populateScene(QGraphicsScene *scene,
583 const MapExtent &canvasExtent,
584 const SpatialReferenceSystem *canvasSRS) override;
585
586 void depopulateScene(QGraphicsScene *scene) override;
587
592 void setLabelConfig(const OpenSWMM::Render::LabelConfig &cfg) override;
593
594 void onCanvasCRSChanged(const SpatialReferenceSystem *newCanvasSRS) override;
595
596 // ----- ISublayerHost interface (Slice U-0) ------------------------------
597 //
598 // Returns the granular per-Category sublayer mix (paint order = list
599 // order, bottom-up). Order matches SWMMModelLayer::Category but groups
600 // by archetype so polygon fills paint first, then lines, then point
601 // markers, then rain gages on top:
602 //
603 // [0] Subcatchments (FillKind, polygon archetype, dynamic)
604 // [1] Conduits (LineKind, line archetype, dynamic)
605 // [2] Pumps (LineKind, line archetype, dynamic)
606 // [3] Orifices (LineKind, line archetype, dynamic)
607 // [4] Weirs (LineKind, line archetype, dynamic)
608 // [5] Outlets (LineKind, line archetype, dynamic)
609 // [6] Junctions (MarkerKind, point archetype, dynamic)
610 // [7] Outfalls (MarkerKind, point archetype, dynamic)
611 // [8] Storage (MarkerKind, point archetype, dynamic)
612 // [9] Dividers (MarkerKind, point archetype, dynamic)
613 // [10] RainGages (MarkerKind, point archetype, static)
614 //
615 // The sublayer instances are owned by this layer via QObject parent-
616 // child.
617 [[nodiscard]] QList<OpenSWMM::Render::ISublayer *> sublayers() const override;
618
619 // 2026-07-19 — animation-tick fast path (slider scrub perf). The base
620 // ISublayerHost::dispatchAnimationTick() invalidates every dynamic
621 // sublayer; in this layer each ISublayer::invalidated fires the
622 // wireRefresh lambda, which re-runs fetchResultsForStep() PER SUBLAYER
623 // and escalates SceneDirty::Structural — turning the intended cheap
624 // Values-only restyle into ~8 redundant .out fetches plus a full
625 // populateScene rebuild on every tick. Animation ticks change only
626 // per-feature values, never the item set, so this override routes
627 // through setCurrentTimeStep()'s Values path and never touches the
628 // sublayer-invalidation (Structural) machinery. wireRefresh keeps its
629 // Structural escalation for its real purpose: visibility / style /
630 // attribute edits.
631 void dispatchAnimationTick(int period) override;
632
635 bool moveSublayer(int from, int to) override;
636
641
660 [[nodiscard]] QList<OpenSWMM::Render::LegendSymbolItem>
662
666 [[nodiscard]] std::vector<std::unique_ptr<openswmmvis::ui::ILayerStyleSubject>>
667 styleSubjects() override;
668
669signals:
670 void resultsFilePathChanged(const QString &path);
671 void scenarioNameChanged(const QString &name);
672 void profileLineColorChanged(const QColor &color);
675 void currentDateTimeChanged(const QDateTime &dt);
680 void periodsAppended(int firstNew, int count);
684 void showLegendChanged(bool show);
686 void resultsError(const QString &message);
691
692public:
701 void refreshScene(QGraphicsScene *scene,
702 const MapExtent &canvasExtent,
703 const SpatialReferenceSystem *canvasSRS) override;
704
705private:
708 OpenSWMMVis::Species::SpeciesMissWarner m_speciesMissWarner;
709
715 bool finishOpen(qint64 msOpen, QList<QString> &warnings, QList<QString> &errors);
716
717 void fetchResultsForStep(int step);
718 void buildOutputIdMaps();
719
723 void restyleScene(QGraphicsScene *scene);
724
730 void refreshLabels(QGraphicsScene *scene);
731 void clearLabels();
732
737 bool requiresStructuralRebuildForRestyle() const;
738
739 // Phase 7 (2026-05-25) — lazy attribute-range samplers. Walk every
740 // period of (kind, outCode) once; cache the (min, max) result. Cleared
741 // on closeResults. Used by populateScene to build a local
742 // RasterColorRamp whose range is data-derived rather than the
743 // hardcoded [0, 1] of the layer's m_colorRamp. Returns {0,1} on
744 // sampling failure so paint still has a defined ramp.
745 QPair<double, double> ensureNodeAttributeRange(int outCode);
746 QPair<double, double> ensureLinkAttributeRange(int outCode);
747 QPair<double, double> ensureSubcatchAttributeRange(int outCode);
748
749 // Engine output handle ------------------------------------------------
750 SWMM_Output m_handle = nullptr;
751
752 // File & scenario metadata --------------------------------------------
753 QString m_resultsFilePath;
754 QString m_reportFilePath;
755 QString m_scenarioName;
756 QColor m_profileLineColor;
757 int m_reportStepSec = 0;
758
759 // Per-source profile styling -----------------------------------------
760 // Initialised from m_profileLineColor at construction; independently
761 // editable thereafter (color changes do NOT clobber them).
762 QPen m_profileHglLinePen;
763 QBrush m_profileHglFillBrush;
764 QPen m_profileEglLinePen;
765 QPen m_profileMaxHglLinePen;
766 QBrush m_profileMaxHglFillBrush;
767 QPen m_profileMaxEglLinePen;
768
769 // Model layer (non-owning) --------------------------------------------
770 class SWMMModelLayer *m_modelLayer = nullptr;
771
772 // Animation state -----------------------------------------------------
773 int m_currentStep = 0;
774 int m_totalSteps = 0;
775 bool m_live = false;
776 QDateTime m_startDateTime;
777 QDateTime m_endDateTime;
778 // 2026-07-19 — period 0's report time; see reportedStartDateTime().
779 QDateTime m_reportedStartDateTime;
780
781 // Variable & colour mapping -------------------------------------------
783 RasterColorRamp m_colorRamp;
784 bool m_showLegend = true;
785
786 // Slice BI Phase 8.13.6.3 — renderer plumbing. Initialised eagerly in
787 // the ctor (default GraduatedRenderer) so renderer() never returns null.
788 // The existing paint loop still reads m_colorRamp / m_variable; 8.13.6.4
789 // refactors that to consult m_renderer instead.
790 std::unique_ptr<OpenSWMM::Render::IFeatureRenderer> m_renderer;
791
792 // Slice OUT.1 — per-kind renderer slots. Indexed by
793 // SWMMModelLayer::Category ordinal; slots default to nullptr (no
794 // override) so paint can fall back to m_renderer. Slice OUT.2 hooks
795 // the per-feature override cache into these and refactors the paint
796 // loop in populateScene() to consult them.
797 std::vector<std::unique_ptr<OpenSWMM::Render::IFeatureRenderer>> m_kindRenderers;
798
799 // Slice B.5 — RuleList mirroring m_kindRenderers (lazy-built).
800 mutable std::unique_ptr<OpenSWMM::Render::RuleList> m_ruleList;
804 bool m_ruleKindSyncGuard = false;
805 void buildRuleListLazy() const;
806
807 // Slice Z.7a — per-frame rebinning for Rules with rebinPerFrame=true.
808 // Connected to currentTimeStepChanged in buildRuleListLazy.
809 void rebinDynamicRulesIfNeeded();
810
811 // Slice OUT.2 — per-feature override cache. Sized to category row
812 // count at rebuild time. Read by populateScene's paint loop in
813 // preference to m_colorRamp.colorForValue(). Rebuilt whenever
814 // result values change (setVariable / setCurrentTimeStep /
815 // animation tick) OR when setKindRenderer() swaps a slot.
816 QVector<QColor> m_kindFeatureColors[SWMMModelLayer::NumCategories];
817 QVector<double> m_kindFeatureSizes [SWMMModelLayer::NumCategories];
818 QVector<double> m_kindFeatureWidths[SWMMModelLayer::NumCategories];
819 QVector<int> m_kindFeatureShapes[SWMMModelLayer::NumCategories];
820 QVector<int> m_kindFeatureDashes[SWMMModelLayer::NumCategories];
821 bool m_kindUsesOverrides[SWMMModelLayer::NumCategories] = {};
822
823 // Slice X.21 — categorized-renderer string-attribute cache. Keyed
824 // by "<categoryOrdinal>/<classifyAttribute>" → per-row string
825 // value pulled from m_modelLayer->identifyByName(...). Identity-
826 // by-name is a hot lookup that touches every cached node / link /
827 // subcatch entry, so re-fetching it every animation tick is
828 // wasteful — the source data is topology-invariant. Flat string
829 // key avoids the QHash<QPair<int,QString>, …> requirement of an
830 // explicit qHash overload.
831 mutable QHash<QString, QVector<QString>> m_categoricalAttrCache;
832
841 void rebuildKindFeatureOverrides(SWMMModelLayer::Category c);
842
845 void rebuildAllActiveKindFeatureOverrides();
846
853 void reclassifyKindsForResolvedRange(int scope, int outCode);
854
855 // Per-step result caches (fetched from .out each time step changes) ----
856 QVector<float> m_nodeResults;
857 QVector<float> m_linkResults;
858 QVector<float> m_subcatchResults;
860 // Phase 2 (2026-05-25) — concurrent multi-attribute paint. Each visible
861 // sublayer carries its own `style.attribute` Q_PROPERTY; per period,
862 // fetchResultsForStep populates a vector per (kind, varCode) pair that
863 // any visible sublayer needs. populateScene reads from these maps using
864 // the sublayer's attribute. The legacy m_nodeResults/m_linkResults/
865 // m_subcatchResults remain populated (for the active m_variable) so
866 // existing callers continue to work unchanged.
867 QHash<int, QVector<float>> m_nodeResultsByVar;
868 QHash<int, QVector<float>> m_linkResultsByVar;
869 QHash<int, QVector<float>> m_subcatchResultsByVar;
870
871 // Phase 7 (2026-05-25) — per-attribute observed range, computed by
872 // sampling EVERY period of a variable when first needed. Replaces the
873 // hardcoded [0, 1] m_colorRamp range so values aren't clamped at one
874 // end of the ramp. Cleared on closeResults / openResults; lazily
875 // populated by ensure*AttributeRange() helpers in the cpp.
876 QHash<int, QPair<double, double>> m_nodeAttributeRange;
877 QHash<int, QPair<double, double>> m_linkAttributeRange;
878 QHash<int, QPair<double, double>> m_subcatchAttributeRange;
879
880 // Per-attribute scan in-flight markers. A request from the paint path
881 // (ensure*AttributeRange) returns a placeholder range immediately,
882 // queues a background QtConcurrent scan of every period, and on
883 // completion the result is inserted into the matching cache and a
884 // repaintRequested() is emitted from the layer thread.
885 QSet<int> m_nodeRangePending;
886 QSet<int> m_linkRangePending;
887 QSet<int> m_subcatchRangePending;
888
889 // Name → output-file index maps (built once on openResults) -----------
890 QHash<QString, int> m_nodeOutputIdx;
891 QHash<QString, int> m_linkOutputIdx;
892 QHash<QString, int> m_subcatchOutputIdx;
893
894 // Aggregated per-object summary statistics, keyed by OUTPUT index and
895 // filled lazily by linkStatsFor() / subcatchStatsFor(). Each miss costs
896 // one series read per contributing variable over the whole run, so the
897 // cache is what keeps an Attribute Table scroll from re-walking the
898 // .out file for every painted cell. Mutable so the const accessors can
899 // populate it; cleared by closeResults().
900 struct LinkStats {
901 double maxFlow = 0.0, maxVelocity = 0.0, maxFilling = 0.0;
902 double volFlow = 0.0, surchargeTimeHr = 0.0;
903 // Pumps only; left at zero for every other link type.
904 double pumpCycles = 0.0, pumpOnTimeHr = 0.0, pumpVolume = 0.0;
905 };
906 struct SubcatchStats {
907 double precipVol = 0.0, runoffVol = 0.0, maxRunoff = 0.0;
908 };
909 // Nodes get the same treatment. Their four values come from engine
910 // aggregators rather than being computed here, but each of those is
911 // still one O(n_periods) walk of the .out file — a seek plus a 4-byte
912 // read per period — so uncached they cost four whole-file walks per
913 // painted row, per repaint.
914 struct NodeStats {
915 double maxDepth = 0.0, maxOverflow = 0.0;
916 double volFlooded = 0.0;
919 double timeFloodedHr = 0.0;
920 };
921 mutable QHash<int, LinkStats> m_linkStats;
922 mutable QHash<int, SubcatchStats> m_subcatchStats;
923 mutable QHash<int, NodeStats> m_nodeStats;
924
928 [[nodiscard]] LinkStats linkStatsFor(int outIdx) const;
929 [[nodiscard]] SubcatchStats subcatchStatsFor(int outIdx) const;
930 [[nodiscard]] NodeStats nodeStatsFor(int outIdx) const;
931
932 // GDAL coordinate transform -------------------------------------------
933 class OGRCoordinateTransformation *m_transform = nullptr;
934
935 // Granular per-Category sublayer storage (Slice U-0) -------------------
936 // One FeatureSublayer per SWMMModelLayer::Category. Indexed by the
937 // category ordinal; size = SWMMModelLayer::NumCategories. Constructed
938 // in the ctor with `this` as QObject parent (no manual delete). The
939 // sublayers() return-list orders them by archetype (polygon → line →
940 // point) so paint order looks correct out of the box.
942
943 // User-customisable sublayer paint order (Slice GUI-2026-05-30 §2).
944 // Populated lazily on first sublayers() / moveSublayer() call from the
945 // archetype-default sequence (polygon → line → marker). Reordered via
946 // moveSublayer(); persisted by ISublayerHost::saveSublayersToJson and
947 // restored by loadSublayersFromJson on .oswp load.
948 mutable QList<OpenSWMM::Render::ISublayer *> m_sublayerOrder;
949
950 // Slice §Y.2 — incremental scene refresh state.
951 //
952 // m_sceneDirty drives refreshScene's branch. Structural forces the
953 // legacy depopulate + populate path (every QGraphicsItem destroyed and
954 // re-allocated). Values reuses the cached items and only mutates
955 // brush / pen. Clean returns immediately. The default is Structural
956 // so the first paint after construction (or after a results file
957 // open) still produces a full scene.
958 enum class SceneDirty : int { Clean = 0, Values = 1, Structural = 2 };
959 SceneDirty m_sceneDirty { SceneDirty::Structural };
960
961 // Per-Category item cache. populateScene records the primary
962 // QGraphicsItem for every feature row into the matching slot; null
963 // entries are valid (NaN values / missing geometry / filtered rows).
964 // restyleScene walks the slots and applies setBrush / setPen in place.
965 // depopulateScene clears the slots. Sizing tracks the model's
966 // categoryCount at population time; restyleScene falls back to
967 // structural when the size mismatches the current count (mid-session
968 // topology edit).
969 QVector<QGraphicsItem *> m_itemByFeature[SWMMModelLayer::NumCategories];
970
971 // L-1 — parallel per-feature label items ("name: value"). Same sizing /
972 // lifecycle contract as m_itemByFeature: built/refreshed by refreshLabels,
973 // cleared by depopulateScene (base destroys the tagged scene items).
974 QVector<QGraphicsSimpleTextItem *> m_labelByFeature[SWMMModelLayer::NumCategories];
975
981 void escalateSceneDirty(SceneDirty next);
982};
983
984Q_DECLARE_METATYPE(SWMMResultsLayer *)
985Q_DECLARE_METATYPE(SWMMResultVariable)
986
987#endif // SWMMRESULTSLAYER_H
An axis-aligned bounding box in the coordinate space of a given CRS.
Definition mapextent.h:26
One paint pass for a single OpenSWMMVis::SwmmCategory.
Definition featuresublayer.h:42
Interface every themeable layer implements so renderer panels can populate attribute combos without h...
Definition iattributeprovider.h:73
Abstract interface — every vector layer's paint path goes here.
Definition ifeaturerenderer.h:85
Mixin interface for layers that expose a list of ISublayers.
Definition isublayerhost.h:64
Ordered owning container for Rules on one layer.
Definition rulelist.h:48
D-G1 warn-on-miss (Y2b-3): a saved theme or series may name a species the currently-open run does not...
Definition speciesattributes.h:112
Abstract base class for all layers displayed in the MapCanvas.
Definition openswmmvislayer.h:116
QString name() const
Returns the user-visible name of this layer.
Definition openswmmvislayer.cpp:62
Top-level project container managing sessions, the SWMM model layer, and persistence paths for one SW...
Definition openswmmvisworkspace.h:42
Renders the SWMM network elements (nodes, links, subcatchments, rain gages) for one OpenSWMMEngine mo...
Definition swmmmodellayer.h:133
static constexpr Category NumCategories
Definition swmmmodellayer.h:167
Overlays time-stepped simulation results on the SWMM network geometry.
Definition swmmresultslayer.h:96
void setProfileHglLinePen(const QPen &pen)
Definition swmmresultslayer.cpp:591
void readProfileStyle(class QSettings &settings)
Companion of writeProfileStyle. Reads any of the six keys present at the current QSettings position a...
Definition swmmresultslayer.cpp:654
bool moveSublayer(int from, int to) override
Definition swmmresultslayer.cpp:423
void setColorForClass(const QString &classKey, const QColor &color)
Definition swmmresultslayer.cpp:2752
void setProfileLineColor(const QColor &color)
Definition swmmresultslayer.cpp:567
double nodeStatVolFlooded(const QString &nodeName) const
Definition swmmresultslayer.cpp:1103
QColor profileLineColor
Definition swmmresultslayer.h:104
void setProfileEglLinePen(const QPen &pen)
Definition swmmresultslayer.cpp:603
int linkOutputIndex(const QString &name) const
Output-file index for the named link, or -1.
Definition swmmresultslayer.cpp:730
void closeResults()
Definition swmmresultslayer.cpp:1006
OpenSWMM::Render::FeatureSublayer * featureSublayer(SWMMModelLayer::Category c) const
Definition swmmresultslayer.cpp:435
OpenSWMM::Render::IFeatureRenderer * renderer() const
The IFeatureRenderer that will drive this layer's paint pass.
Definition swmmresultslayer.cpp:2156
void setVariable(SWMMResultVariable var)
Definition swmmresultslayer.cpp:2074
QString scenarioName
Definition swmmresultslayer.h:102
double subcatchStatRunoffVol(const QString &subName) const
Definition swmmresultslayer.cpp:1353
double nodeStatMaxOverflow(const QString &nodeName) const
Definition swmmresultslayer.cpp:1098
bool openResults(QList< QString > &warnings, QList< QString > &errors)
Opens the binary results file.
Definition swmmresultslayer.cpp:745
void setReportFilePath(const QString &path)
Definition swmmresultslayer.cpp:545
class SWMMModelLayer * modelLayer() const
Definition swmmresultslayer.h:138
QString sourceDescription() const override
Human-readable source of this layer: a file path, a service URL, or a description for in-memory / der...
Definition swmmresultslayer.cpp:517
void profileLineColorChanged(const QColor &color)
void openResultsAsync()
Asynchronous counterpart to openResults. Runs the blocking swmm_output_open (file + header/index pars...
Definition swmmresultslayer.cpp:956
QBrush profileMaxHglFillBrush
Definition swmmresultslayer.h:115
double subcatchStatPrecip(const QString &subName) const
Definition swmmresultslayer.cpp:1350
double linkStatPumpCycles(const QString &linkName) const
Definition swmmresultslayer.cpp:1341
void scenarioNameChanged(const QString &name)
QPen profileHglLinePen
Definition swmmresultslayer.h:106
void setShowLegend(bool show)
Definition swmmresultslayer.cpp:3221
bool supportsClassEdit(OpenSWMM::Render::ClassEditKind kind) const
Definition swmmresultslayer.cpp:2734
void setProfileMaxEglLinePen(const QPen &pen)
Definition swmmresultslayer.cpp:621
double linkStatPumpOnTime(const QString &linkName) const
Definition swmmresultslayer.cpp:1344
void showLegendChanged(bool show)
void setProfileHglFillBrush(const QBrush &brush)
Definition swmmresultslayer.cpp:597
QString resultsFilePath
Definition swmmresultslayer.h:100
void resultsFinalized()
void dispatchAnimationTick(int period) override
Hook invoked by AnimationController on every time-step change.
Definition swmmresultslayer.cpp:2020
double nodeStatMaxDepth(const QString &nodeName) const
Definition swmmresultslayer.cpp:1093
double nodeStatTimeFlooded(const QString &nodeName) const
Definition swmmresultslayer.cpp:1108
void stepForward(bool loop=false)
Advances to the next time step (wraps at end if loop is true).
Definition swmmresultslayer.cpp:2039
QList< OpenSWMM::Render::LegendSymbolItem > legendSymbolItems()
Definition swmmresultslayer.cpp:2727
QDateTime endDateTime() const
Definition swmmresultslayer.cpp:1977
void resultsFilePathChanged(const QString &path)
void currentTimeStepChanged(int step)
SWMMResultVariable variable
Definition swmmresultslayer.h:123
void periodsAppended(int firstNew, int count)
OpenSWMM::Render::IFeatureRenderer * kindRenderer(SWMMModelLayer::Category c) const
Returns the per-kind renderer for category c, or nullptr when none has been installed yet (callers sh...
Definition swmmresultslayer.cpp:2230
QDateTime reportedStartDateTime() const
Definition swmmresultslayer.cpp:1983
void resultsOpenFinished(bool ok)
Emitted on the GUI thread when openResultsAsync() completes.
void rendererChanged()
Emitted when setRenderer() swaps the renderer pointer.
std::vector< std::unique_ptr< openswmmvis::ui::ILayerStyleSubject > > styleSubjects() override
Definition swmmresultslayer.cpp:444
int periodIndexForDateTimeAsOf(const QDateTime &dt) const
Causal peer of periodIndexForDateTime: the latest period at or before dt (floor instead of nearest),...
Definition swmmresultslayer.cpp:700
double linkStatVolFlow(const QString &linkName) const
Definition swmmresultslayer.cpp:1335
double subcatchStatMaxRunoff(const QString &subName) const
Definition swmmresultslayer.cpp:1356
double linkStatMaxVelocity(const QString &linkName) const
Definition swmmresultslayer.cpp:1329
QList< OpenSWMM::Render::ISublayer * > sublayers() const override
The ordered list of sublayers owned by this layer.
Definition swmmresultslayer.cpp:395
QPen profileMaxEglLinePen
Definition swmmresultslayer.h:117
void onCanvasCRSChanged(const SpatialReferenceSystem *newCanvasSRS) override
Called when the user changes the canvas CRS so layers can pre-compute any CRS-dependent transform cac...
Definition swmmresultslayer.cpp:3234
bool showLegend
Definition swmmresultslayer.h:125
int totalTimeSteps
Definition swmmresultslayer.h:121
int refreshLive()
Re-count periods on a live file. Grows totalTimeSteps(), extends endDateTime(), drops whole-file aggr...
Definition swmmresultslayer.cpp:801
void setRenderer(std::unique_ptr< OpenSWMM::Render::IFeatureRenderer > r)
Replaces the current renderer.
Definition swmmresultslayer.cpp:2161
void refreshScene(QGraphicsScene *scene, const MapExtent &canvasExtent, const SpatialReferenceSystem *canvasSRS) override
Slice §Y.2 — incremental scene refresh.
Definition swmmresultslayer.cpp:3799
SWMM_Output outputHandle() const
Raw SWMM_Output handle to the open .out file, or nullptr when results aren't open yet....
Definition swmmresultslayer.cpp:720
int nodeOutputIndex(const QString &name) const
Output-file index for the named node, or -1 if not found. The output file uses its own indexing schem...
Definition swmmresultslayer.cpp:725
int subcatchOutputIndex(const QString &name) const
Output-file index for the named subcatchment, or -1. Mirrors nodeOutputIndex / linkOutputIndex; added...
Definition swmmresultslayer.cpp:735
int periodIndexForDateTime(const QDateTime &dt) const
Returns the period index closest to dt on this layer's time grid.
Definition swmmresultslayer.cpp:676
void resultsError(const QString &message)
RasterColorRamp colorRamp() const
Definition swmmresultslayer.cpp:2122
void setScenarioName(const QString &name)
Definition swmmresultslayer.cpp:555
void autoStretchColorRamp()
Automatically sets the colour ramp range from the data min/max across all time steps for the current ...
Definition swmmresultslayer.cpp:3185
void setColorRamp(const RasterColorRamp &ramp)
Definition swmmresultslayer.cpp:2124
QVector< OpenSWMM::Render::AttributeField > availableAttributes(OpenSWMMVis::SwmmCategory cat) const override
Definition swmmresultslayer.cpp:2425
bool isLive() const noexcept
True between openResultsLive() and the footer's arrival.
Definition swmmresultslayer.h:264
void setCurrentTimeStep(int step)
Seeks to the given 0-based time step.
Definition swmmresultslayer.cpp:1989
void setKindRenderer(SWMMModelLayer::Category c, std::unique_ptr< OpenSWMM::Render::IFeatureRenderer > r)
Installs (or replaces) the per-kind renderer for category c. The layer takes ownership....
Definition swmmresultslayer.cpp:2239
bool openResultsLive(QList< QString > &errors)
Open a results file the engine is still writing (live tail).
Definition swmmresultslayer.cpp:773
void setProfileMaxHglFillBrush(const QBrush &brush)
Definition swmmresultslayer.cpp:615
QBrush profileHglFillBrush
Definition swmmresultslayer.h:108
void writeProfileStyle(class QSettings &settings) const
Writes this layer's per-source profile pens & brushes to the current QSettings position....
Definition swmmresultslayer.cpp:636
QStringList speciesNames() const
Y2a — the loaded run's species names, in .out order (pollutants plus the reserved age/temperature col...
Definition swmmresultslayer.cpp:2525
QDateTime startDateTime() const
Definition swmmresultslayer.cpp:1976
void depopulateScene(QGraphicsScene *scene) override
Removes all QGraphicsItems belonging to this layer from the scene.
Definition swmmresultslayer.cpp:3755
int currentTimeStep
Definition swmmresultslayer.h:119
void totalTimeStepsChanged(int count)
void resetKindRendererToDefaults(SWMMModelLayer::Category c)
Restores the per-kind slot for c to its compile-time default (GraduatedRenderer keyed to the scope's ...
Definition swmmresultslayer.cpp:2607
void refreshRuleMirror(SWMMModelLayer::Category c)
Re-clones the live kind renderer for c into the Rule mirror (no-op when the rule list hasn't been bui...
Definition swmmresultslayer.cpp:2612
void setLabelConfig(const OpenSWMM::Render::LabelConfig &cfg) override
Definition swmmresultslayer.cpp:4105
QColor colorForClass(const QString &classKey) const
Definition swmmresultslayer.cpp:2744
QPen profileMaxHglLinePen
Definition swmmresultslayer.h:113
int flowUnits() const
Engine-reported flow units (0=CFS, 1=GPM, 2=MGD, 3=CMS, 4=LPS, 5=MLD). -1 when results aren't open....
Definition swmmresultslayer.cpp:740
void currentDateTimeChanged(const QDateTime &dt)
double linkStatMaxFilling(const QString &linkName) const
Definition swmmresultslayer.cpp:1332
QVector< QPair< QString, QString > > extendedMetadata() const override
Ordered key→value pairs of TYPE-SPECIFIC metadata, rendered by the Metadata tab below the common bloc...
Definition swmmresultslayer.cpp:523
QDateTime currentDateTime
Definition swmmresultslayer.h:120
void stepBackward(bool loop=false)
Goes back one time step (wraps at start if loop is true).
Definition swmmresultslayer.cpp:2051
QString reportFilePath() const
Path of the .rpt summary report written by the run that produced this layer's .out file (empty when u...
Definition swmmresultslayer.cpp:510
void variableChanged(SWMMResultVariable var)
QList< OpenSWMM::Render::LegendSymbolItem > sublayerLegendItems()
Phase 8 (2026-05-25) — ramp-aware legend rows.
Definition swmmresultslayer.cpp:1717
double linkStatMaxFlow(const QString &linkName) const
Definition swmmresultslayer.cpp:1326
qreal sizeForClass(const QString &classKey) const
Definition swmmresultslayer.cpp:2767
void profileStyleChanged()
void populateScene(QGraphicsScene *scene, const MapExtent &canvasExtent, const SpatialReferenceSystem *canvasSRS) override
Creates / updates QGraphicsItems representing this layer in the scene.
Definition swmmresultslayer.cpp:3259
QPen profileEglLinePen
Definition swmmresultslayer.h:110
double linkStatPumpVolume(const QString &linkName) const
Definition swmmresultslayer.cpp:1347
int reportStepSeconds() const
Definition swmmresultslayer.cpp:1978
double linkStatSurchargeTime(const QString &linkName) const
Definition swmmresultslayer.cpp:1338
bool kindRendererIsDefault(SWMMModelLayer::Category c) const
True when the installed kind renderer for c is semantically identical to the compile-time default — i...
Definition swmmresultslayer.cpp:2676
OpenSWMM::Render::RuleList * ruleList() override
Definition swmmresultslayer.cpp:2274
void setProfileMaxHglLinePen(const QPen &pen)
Definition swmmresultslayer.cpp:609
Represents a coordinate reference system backed by a GDAL OGRSpatialReference.
Definition spatialreferencesystem.h:28
Granular per-Category sublayer that replaces the legacy NodeMarker / ConduitLine / ConduitArrow / Sub...
Map layer backed by a GDAL raster dataset with configurable colour-ramp rendering and on-demand viewp...
Layer-side seam for "what attributes can I theme by?" — Slice DM.1.
QString nodeName
Definition importplanning.cpp:95
QGraphicsScene * scene
Definition inletdrawingview.cpp:78
bool ok
Definition inpmeshwriter.cpp:575
Mixin interface for layers that own a list of ISublayers.
Mesh2DVariable var
Definition mesh2dresultsexport.cpp:294
double step
Definition meshcellparams.cpp:28
Definition gisrasterlayer.h:40
ClassEditKind
Categories of per-class mutation that a renderer may support.
Definition ifeaturerenderer.h:64
SwmmCategory
Stable ordinal categorising every SWMM-element kind that has per-kind styling, per-kind tree rows,...
Definition swmm_category.h:28
int count
Definition numberformat.cpp:101
Abstract base class for all map layers displayed in the MapCanvas.
int path
Definition pslgminsize.cpp:234
QVector< int > parent
union-find
Definition pslgminsize.cpp:176
TokenKind kind
Definition queryparser.cpp:34
QString message
Definition queryparser.cpp:150
Dynamic water-quality species as themeable result attributes (GUI plan D-G1 / subplan Y2a).
Per-layer label rendering parameters.
Definition labelconfig.h:47
Gradient mapping from a normalised value in [0,1] to a QColor.
Definition colorramp.h:71
Map layer that renders an OpenSWMMEngine network (nodes, links, subcatchments, rain gages) and provid...
SWMMResultVariable
Selectable result output variables for colour-mapped display.
Definition swmmresultslayer.h:57