From 17a67ca219e765af08fe56dc610a760f54864714 Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Wed, 28 Oct 2026 11:40:58 +0100 Subject: [PATCH 1/3] tests: change: re-record the SatelliteMobility tyf fingerprint for the new fake GUI probabilities Before this commit, the tyf fingerprint test of the SatelliteMobility configuration of examples/mobility fails on an unchanged INET tree: the stored value is 772e-b194, and the test now calculates 332a-ea89. The stored value was recorded when opp_repl ran the tyf ingredients with fake graphical user interface (GUI) probabilities of 0.1. opp_repl commit 5049cc8fbe changed these probabilities (before event, after event, on hold and on simulation time) to 0.001. After this commit, the row stores 332a-ea89, and the test passes again. No INET source, configuration or image changes. The y (display strings) and f (canvas figures) ingredients are added on each refreshDisplay() call that the fake GUI makes, and the probabilities decide when those calls happen, so the new arguments alone move the value. The simulation itself does not change: the other ingredient sets of this configuration keep their stored values. Provenance: opp_run_fingerprint_tests of opp_repl main 39e15bc, -m release -w examples/mobility -c '^SatelliteMobility$' --ingredients tyf, release build of a40c4ffcdc. Change: tests | behavior.change | fingerprint --- tests/fingerprint/store.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fingerprint/store.json b/tests/fingerprint/store.json index d2233d166a2..324b570ba8a 100644 --- a/tests/fingerprint/store.json +++ b/tests/fingerprint/store.json @@ -20515,8 +20515,8 @@ "sim_time_limit": "3600s", "test_result": "PASS", "ingredients": "tyf", - "fingerprint": "772e-b194", - "timestamp": 1792589940.0, + "fingerprint": "332a-ea89", + "timestamp": 1793183936.0, "itervars": "$repetition==0" }, { From 4fc2cab372b87dd4fed26b59a75f8abbd51228e7 Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Wed, 28 Oct 2026 11:42:59 +0100 Subject: [PATCH 2/3] visualizer: fix: name the observer in a title above each sky view plot GeoSkyViewCanvasVisualizer drew one polar sky plot per observer and gave it no title. A comment in buildPlotDecorations() explained the choice: the observer's icon sits next to the plot and names it, which holds only with finite plotOffsetX and plotOffsetY. With their default (infinite) the plots are tiled in a grid below the map, away from the icons. In examples/mobility SatelliteMobility the two plots below the world map belong to the ground stations Lima and Los Angeles, and nothing on the canvas says which is which. The Network Description (NED) documentation of labelColor already calls it the "Color of the title and elevation labels". After this commit, each plot has a title above its N label, in labelColor: the observer node's display name, or its full name if none is set. In the grid layout each row is one title line (14 pixels) taller, so that a title clears the S label of the plot above. The new displayTitles parameter turns the titles off and restores the old canvas; it defaults to true because grid mode is the default layout. To see the change, run `inet -u Qtenv -c SatelliteMobility` in examples/mobility. The module test tests/module/GeoSkyViewCanvasVisualizer_titles.test checks the titles, their position above the plot, and the row pitch with and without titles. The User's Guide now describes the grid layout. Simulation results do not change: the title is a canvas figure, drawn only when the visualizer draws. The fingerprints with the default ingredients of inet.opp (tplx, ~tNl, ~tND) do not change. The canvas-figure fingerprint (tyf) of SatelliteMobility moves from 332a-ea89 to 9471-891b, because the titles are new figures and the grid rows move down; this commit re-records it in tests/fingerprint/store.json. With displayTitles = false this commit gives 332a-ea89, so the titles and the row spacing account for the whole move. Change: src.visualizer.canvas.GeoSkyViewCanvasVisualizer | behavior.add.fix | test whatsnew fingerprint --- doc/src/users-guide/ch-visualization.rst | 7 +- .../visualizer-sky-view-observer-titles.txt | 3 + .../scene/GeoSkyViewCanvasVisualizer.cc | 21 ++- .../canvas/scene/GeoSkyViewCanvasVisualizer.h | 1 + .../scene/GeoSkyViewCanvasVisualizer.ned | 5 +- tests/fingerprint/store.json | 4 +- .../GeoSkyViewCanvasVisualizer_titles.test | 133 ++++++++++++++++++ 7 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 doc/whatsnew/fixes/visualizer-sky-view-observer-titles.txt create mode 100644 tests/module/GeoSkyViewCanvasVisualizer_titles.test diff --git a/doc/src/users-guide/ch-visualization.rst b/doc/src/users-guide/ch-visualization.rst index 4c1773cb4e7..1b332f19e90 100644 --- a/doc/src/users-guide/ch-visualization.rst +++ b/doc/src/users-guide/ch-visualization.rst @@ -489,8 +489,11 @@ Visualizing the Sky View ~~~~~~~~~~~~~~~~~~~~~~~~~~ The :ned:`GeoSkyViewCanvasVisualizer` draws an azimuth/elevation polar -plot, the "sky view", next to each observer node, showing where the target -nodes currently are in the observer's sky. It is primarily intended for +plot, the "sky view", for each observer node, showing where the target +nodes currently are in the observer's sky. By default the plots are tiled in a +grid below the map, each with the observer's name as its title; with finite +:par:`plotOffsetX` and :par:`plotOffsetY` each plot is drawn next to its +observer node instead. The visualizer is primarily intended for ground stations observing satellites. The nodes that get a plot are selected with the :par:`observerFilter` diff --git a/doc/whatsnew/fixes/visualizer-sky-view-observer-titles.txt b/doc/whatsnew/fixes/visualizer-sky-view-observer-titles.txt new file mode 100644 index 00000000000..ec26937f0f7 --- /dev/null +++ b/doc/whatsnew/fixes/visualizer-sky-view-observer-titles.txt @@ -0,0 +1,3 @@ +Sky view plots name their observer + +GeoSkyViewCanvasVisualizer drew each observer's sky plot without a title, and in its default grid mode it tiles the plots below the map in observer order, so a plot could not be matched to its ground station. Each plot now has a title above it with the observer node's display name, or its full name if no display name is set. The grid rows are spaced one title line further apart, so a title does not touch the plot above it. The new displayTitles parameter turns the titles off and restores the old layout. Simulation results do not change. diff --git a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc index 24f822c8be2..c5c3b4616aa 100644 --- a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc +++ b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc @@ -22,6 +22,8 @@ namespace visualizer { Define_Module(GeoSkyViewCanvasVisualizer); +static const double TITLE_HEIGHT = 14; // pixels between the plot rim and the bottom of the title above it + GeoSkyViewCanvasVisualizer::ObserverPlot::~ObserverPlot() { delete groupFigure; // deletes all child figures (plot decorations and target dots/labels/trails) @@ -54,6 +56,7 @@ void GeoSkyViewCanvasVisualizer::initialize(int stage) maskOpacity = par("maskOpacity"); cardinalColor = cFigure::parseColor(par("cardinalColor")); labelColor = cFigure::parseColor(par("labelColor")); + displayTitles = par("displayTitles"); targetColorSet.parseColors(par("targetColor")); targetRadius = par("targetRadius"); lowElevationOpacity = par("lowElevationOpacity"); @@ -309,9 +312,18 @@ void GeoSkyViewCanvasVisualizer::buildPlotDecorations(ObserverPlot *plot) addLabel("E", cFigure::Point(R + 1, 0), cFigure::ANCHOR_W, cardinalColor, 0); addLabel("W", cFigure::Point(-R - 1, 0), cFigure::ANCHOR_E, cardinalColor, 0); + // title above the N label: in grid mode the plots are away from the observer icons, + // so the title is what tells the plots apart + if (displayTitles) { + auto observerModule = check_and_cast(plot->observer); + auto observerNode = observerModule->getParentModule() != nullptr ? observerModule->getParentModule() : observerModule; + const char *displayName = observerNode->getDisplayName(); + std::string title = displayName != nullptr && *displayName != '\0' ? displayName : observerNode->getFullName(); + addLabel(title.c_str(), cFigure::Point(0, -R - TITLE_HEIGHT), cFigure::ANCHOR_S, labelColor, 0); + } + // elevation labels sitting just above their ring along the North spoke, in a small - // (half-size) font; the 0 deg ring is the disc rim and is not labeled. No title is - // drawn: the observer module icon is right next to the plot and already shows its name. + // (half-size) font; the 0 deg ring is the disc rim and is not labeled for (double el : { 30.0, 60.0 }) { double r = R * (90 - el) / 90; char buf[8]; @@ -373,12 +385,13 @@ void GeoSkyViewCanvasVisualizer::refreshDisplay() const // grid below the target module's viewport; otherwise each plot is anchored to its observer icon bool gridMode = std::isinf(plotOffsetX) || std::isinf(plotOffsetY); double cell = 2 * plotRadius + plotRadius / 2; // plot diameter plus a gap + double rowTitleHeight = displayTitles ? TITLE_HEIGHT : 0; // the title line above each plot double gridX0 = 0, gridY0 = 0; int columns = 1; if (gridMode) { cFigure::Rectangle viewport = getViewportBounds(); gridX0 = viewport.x; - gridY0 = viewport.y + viewport.height + plotRadius / 2; // just below the viewport + gridY0 = viewport.y + viewport.height + plotRadius / 2 + rowTitleHeight; // just below the viewport columns = std::max(1, (int)std::floor(viewport.width / cell)); } @@ -390,7 +403,7 @@ void GeoSkyViewCanvasVisualizer::refreshDisplay() const // disc center, so seat the cell's top-left at the grid cell by offsetting by plotRadius int col = index % columns; int row = index / columns; - plot->groupFigure->setTransform(cFigure::Transform().translate(gridX0 + col * cell + plotRadius, gridY0 + row * cell + plotRadius)); + plot->groupFigure->setTransform(cFigure::Transform().translate(gridX0 + col * cell + plotRadius, gridY0 + row * (cell + rowTitleHeight) + plotRadius)); } else { auto observerModule = check_and_cast(plot->observer); diff --git a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h index 8f078428b9d..b6127cb0a75 100644 --- a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h +++ b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h @@ -88,6 +88,7 @@ class INET_API GeoSkyViewCanvasVisualizer : public VisualizerBase, public cListe double maskOpacity = NaN; cFigure::Color cardinalColor; cFigure::Color labelColor; + bool displayTitles = false; ColorSet targetColorSet; double targetRadius = NaN; diff --git a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned index 4a6a82c6205..d6f0e4c5f38 100644 --- a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned +++ b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned @@ -19,7 +19,9 @@ import inet.visualizer.contract.IGeoSkyViewVisualizer; // rings mark elevations, the cardinal points (N/E/S/W) are drawn, and the band // below `elevationMask` is masked translucently. Each visible target is a // colored dot (optionally named, with a fading sky trail); targets below the -// horizon are hidden. +// horizon are hidden. By default, a title above each plot names its observer node: +// its display name, or its full name if no display name is set. In the grid layout +// the plots are 2.5 * `plotRadius` apart, so a wider title overlaps the next one. // // Look angles are computed from the geographic positions of the observer and the // targets via the `IGeographicCoordinateSystem` given by `coordinateSystemModule`. @@ -50,6 +52,7 @@ simple GeoSkyViewCanvasVisualizer extends VisualizerBase like IGeoSkyViewVisuali double maskOpacity = default(0.25); // Opacity of the below-minimum-elevation mask string cardinalColor = default("#888888"); // Color of the N-S/W-E lines and the N/E/S/W labels string labelColor = default("black"); // Color of the title and elevation labels + bool displayTitles = default(true); // Display a title with the observer node's name above each plot string targetColor = default("dark"); // Target dot color, a list of colors or a color group name (e.g. dark, light) double targetRadius = default(3); // Radius of a target dot in pixels diff --git a/tests/fingerprint/store.json b/tests/fingerprint/store.json index 324b570ba8a..42d425ce752 100644 --- a/tests/fingerprint/store.json +++ b/tests/fingerprint/store.json @@ -20515,8 +20515,8 @@ "sim_time_limit": "3600s", "test_result": "PASS", "ingredients": "tyf", - "fingerprint": "332a-ea89", - "timestamp": 1793183936.0, + "fingerprint": "9471-891b", + "timestamp": 1793184170.0, "itervars": "$repetition==0" }, { diff --git a/tests/module/GeoSkyViewCanvasVisualizer_titles.test b/tests/module/GeoSkyViewCanvasVisualizer_titles.test new file mode 100644 index 00000000000..075dc65a4ff --- /dev/null +++ b/tests/module/GeoSkyViewCanvasVisualizer_titles.test @@ -0,0 +1,133 @@ +%description: + +Checks the titles of the GeoSkyViewCanvasVisualizer sky plots. Cmdenv runs with +its fake graphical user interface, so the visualizers draw. Two visualizers +watch the same two ground stations: gs[0] has the display name "Lima", gs[1] +has none. At the end of the run a checker prints the label texts of each sky +plot group on the network canvas, with the tags of its visualizer, and the +plot's center with the vertical position of each label relative to it: + - default: the title is the display name, or the full name if none is set + - hidden: displayTitles = false draws no title + +The layout uses the default plotRadius of 70 pixels. The network's bgb of +300x200 pixels holds one 175-pixel grid column, so each visualizer's two plots +are in two rows below it. With titles, the title is 84 pixels above the plot +center (14 above the rim, 13 above the N label at -71), the first center is at +200 + 35 + 14 + 70 = 319, and the row pitch is 175 + 14 = 189, so a title stays +clear of the S label (+71) of the plot above it. Without titles, the centers are +at 305 and 480: the old row pitch of 175. + +%file: test.cc + +#include "inet/common/INETDefs.h" + +using namespace inet; + +namespace GeoSkyViewCanvasVisualizer_titles { + +class SkyViewLabelChecker : public cSimpleModule +{ + protected: + virtual void finish() override + { + auto canvas = getParentModule()->getCanvas(); + for (int i = 0; i < canvas->getRootFigure()->getNumFigures(); i++) { + auto group = dynamic_cast(canvas->getRootFigure()->getFigure(i)); + if (group == nullptr || strcmp(group->getName(), "skyView")) + continue; + std::string text = std::string("SKYVIEW ") + group->getTags() + ":"; + for (int j = 0; j < group->getNumFigures(); j++) + if (auto label = dynamic_cast(group->getFigure(j))) + text += std::string(" [") + label->getText() + "]"; + std::cout << text << std::endl; + // the plot center (the group's translation) and the label positions relative to it + const cFigure::Transform& transform = group->getTransform(); + std::cout << "LAYOUT " << group->getTags() << ": plot at " << transform.t1 << " " << transform.t2 << ";"; + for (int j = 0; j < group->getNumFigures(); j++) + if (auto label = dynamic_cast(group->getFigure(j))) + std::cout << " [" << label->getText() << " " << label->getPosition().y << "]"; + std::cout << std::endl; + } + } +}; + +Define_Module(SkyViewLabelChecker); + +} // namespace GeoSkyViewCanvasVisualizer_titles + +%file: test.ned + +import inet.common.geometry.common.Wgs84EcefGeographicCoordinateSystem; +import inet.mobility.contract.IMobility; +import inet.visualizer.canvas.scene.GeoSkyViewCanvasVisualizer; + +simple SkyViewLabelChecker +{ +} + +module GroundStation +{ + parameters: + @networkNode; + submodules: + mobility: like IMobility; +} + +network Test +{ + parameters: + @display("bgb=300,200"); + submodules: + coordinateSystem: Wgs84EcefGeographicCoordinateSystem; + skyViewDefault: GeoSkyViewCanvasVisualizer; + skyViewHidden: GeoSkyViewCanvasVisualizer; + checker: SkyViewLabelChecker; + gs[2]: GroundStation; +} + +%inifile: omnetpp.ini + +[General] +network = Test +sim-time-limit = 1s +ned-path = .;../../../../src +cmdenv-fake-gui = true + +*.gs[0].display-name = "Lima" +*.gs[*].mobility.initFromDisplayString = false +*.gs[*].mobility.coordinateSystemModule = "^.^.coordinateSystem" +*.gs[0].mobility.initialLatitude = -12.046deg +*.gs[0].mobility.initialLongitude = -77.043deg +*.gs[1].mobility.initialLatitude = 34.052deg +*.gs[1].mobility.initialLongitude = -118.244deg + +*.skyView*.coordinateSystemModule = "^.coordinateSystem" +*.skyView*.observerFilter = "**.gs[*].mobility" +*.skyView*.targetFilter = "**.sat[*].mobility" +*.skyViewDefault.tags = "default" +*.skyViewHidden.tags = "hidden" +*.skyViewHidden.displayTitles = false + +%#-------------------------------------------------------------------------------------------------------------- +%# remove colorizing +%subst: /\x1B\[[0-9;]*m// +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +SKYVIEW sky_view default: [N] [S] [E] [W] [Lima] [30 +%contains: stdout +SKYVIEW sky_view default: [N] [S] [E] [W] [gs[1]] [30 +%contains: stdout +SKYVIEW sky_view hidden: [N] [S] [E] [W] [30 +%contains: stdout +LAYOUT sky_view default: plot at 70 319; [N -71] [S 71] [E 0] [W 0] [Lima -84] [30 +%contains: stdout +LAYOUT sky_view default: plot at 70 508; [N -71] [S 71] [E 0] [W 0] [gs[1] -84] [30 +%contains: stdout +LAYOUT sky_view hidden: plot at 70 305; [N -71] [S 71] [E 0] [W 0] [30 +%contains: stdout +LAYOUT sky_view hidden: plot at 70 480; [N -71] [S 71] [E 0] [W 0] [30 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#-------------------------------------------------------------------------------------------------------------- From 95714b7825773dbaf0ca9d5645ab60796de8eecd Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Wed, 28 Oct 2026 11:19:25 +0100 Subject: [PATCH 3/3] visualizer: add: a titleFormat parameter for the sky view plot titles The title above each GeoSkyViewCanvasVisualizer sky plot showed the observer node's display name, or its full name if no display name is set. The display name hides the module name that the configuration uses: examples/mobility SatelliteMobility sets each ground station by index (*.gs[0].display-name = "Lima", *.gs[0].mobility.initialLatitude, ...), and its plots read "Lima" and "Los Angeles", not gs[0] and gs[1]. An observerFilter such as "**.gs[*].mobility" can also match same-named nodes in different compound modules; the full path tells their plots apart. Other visualizers let the user choose the text of their labels with a format string (labelFormat, resolved by InfoVisualizerBase); the sky view had no such choice. No configuration in the repository sets a format other than the default yet. After this commit, the new titleFormat parameter selects the title text. Its directives are %n (the observer node's full name), %N (its display name, or the full name if none is set) and %p (its full path), the same letters as in InfoVisualizerBase. The default "%N" gives that same title, so the canvas does not change. The new protected virtual getObserverTitle() resolves the format; a subclass can override it to name the observer from data that the directives do not reach, as getTargetLabel() reads the satelliteName parameter of a target. The module test tests/module/GeoSkyViewCanvasVisualizer_titles.test now also checks titleFormat = "%p (%n)". Simulation results and canvas figures do not change: the default format gives the same text, and the tyf fingerprint of examples/mobility SatelliteMobility stays at 9471-891b. Change: src.visualizer.canvas.GeoSkyViewCanvasVisualizer | behavior.add | test whatsnew --- .../visualizer-sky-view-title-format.txt | 3 ++ .../scene/GeoSkyViewCanvasVisualizer.cc | 31 ++++++++++++++----- .../canvas/scene/GeoSkyViewCanvasVisualizer.h | 5 +++ .../scene/GeoSkyViewCanvasVisualizer.ned | 8 ++++- .../GeoSkyViewCanvasVisualizer_titles.test | 12 +++++-- 5 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 doc/whatsnew/compatible/visualizer-sky-view-title-format.txt diff --git a/doc/whatsnew/compatible/visualizer-sky-view-title-format.txt b/doc/whatsnew/compatible/visualizer-sky-view-title-format.txt new file mode 100644 index 00000000000..9169f5cefd5 --- /dev/null +++ b/doc/whatsnew/compatible/visualizer-sky-view-title-format.txt @@ -0,0 +1,3 @@ +Sky view plot titles have a format + +The new titleFormat parameter of GeoSkyViewCanvasVisualizer selects the text of the title above each sky plot, the same way as labelFormat selects the label text of other visualizers. Its directives are %n (the observer node's full name), %N (its display name, or the full name if none is set) and %p (its full path). The default "%N" gives the title that this release introduces with the fix "Sky view plots name their observer": the display name of the observer node, or its full name if none is set. Simulation results do not change. diff --git a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc index c5c3b4616aa..3d3128302b7 100644 --- a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc +++ b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc @@ -57,6 +57,7 @@ void GeoSkyViewCanvasVisualizer::initialize(int stage) cardinalColor = cFigure::parseColor(par("cardinalColor")); labelColor = cFigure::parseColor(par("labelColor")); displayTitles = par("displayTitles"); + titleFormat.parseFormat(par("titleFormat")); targetColorSet.parseColors(par("targetColor")); targetRadius = par("targetRadius"); lowElevationOpacity = par("lowElevationOpacity"); @@ -314,13 +315,8 @@ void GeoSkyViewCanvasVisualizer::buildPlotDecorations(ObserverPlot *plot) // title above the N label: in grid mode the plots are away from the observer icons, // so the title is what tells the plots apart - if (displayTitles) { - auto observerModule = check_and_cast(plot->observer); - auto observerNode = observerModule->getParentModule() != nullptr ? observerModule->getParentModule() : observerModule; - const char *displayName = observerNode->getDisplayName(); - std::string title = displayName != nullptr && *displayName != '\0' ? displayName : observerNode->getFullName(); - addLabel(title.c_str(), cFigure::Point(0, -R - TITLE_HEIGHT), cFigure::ANCHOR_S, labelColor, 0); - } + if (displayTitles) + addLabel(getObserverTitle(plot->observer).c_str(), cFigure::Point(0, -R - TITLE_HEIGHT), cFigure::ANCHOR_S, labelColor, 0); // elevation labels sitting just above their ring along the North spoke, in a small // (half-size) font; the 0 deg ring is the disc rim and is not labeled @@ -379,6 +375,27 @@ std::string GeoSkyViewCanvasVisualizer::getTargetLabel(const IMobility *target) return node != nullptr ? node->getFullName() : module->getFullName(); } +std::string GeoSkyViewCanvasVisualizer::getObserverTitle(const IMobility *observer) const +{ + auto module = check_and_cast(observer); + auto node = module->getParentModule() != nullptr ? module->getParentModule() : module; + std::function resolver = [&] (char directive) -> std::string { + switch (directive) { + case 'n': + return node->getFullName(); + case 'N': { + const char *displayName = node->getDisplayName(); + return displayName != nullptr && *displayName != '\0' ? displayName : node->getFullName(); + } + case 'p': + return node->getFullPath(); + default: + throw cRuntimeError("Unknown directive: %c", directive); + } + }; + return titleFormat.formatString(resolver); +} + void GeoSkyViewCanvasVisualizer::refreshDisplay() const { // when either offset is infinite, the plots are detached from the observer icons and tiled in a diff --git a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h index b6127cb0a75..92de4fc81d9 100644 --- a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h +++ b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h @@ -10,6 +10,7 @@ #include +#include "inet/common/StringFormat.h" #include "inet/common/figures/TrailFigure.h" #include "inet/common/geometry/common/GeographicCoordinateSystem.h" #include "inet/mobility/contract/IMobility.h" @@ -89,6 +90,7 @@ class INET_API GeoSkyViewCanvasVisualizer : public VisualizerBase, public cListe cFigure::Color cardinalColor; cFigure::Color labelColor; bool displayTitles = false; + StringFormat titleFormat; ColorSet targetColorSet; double targetRadius = NaN; @@ -123,6 +125,9 @@ class INET_API GeoSkyViewCanvasVisualizer : public VisualizerBase, public cListe virtual cFigure::Point elevationAzimuthToCanvas(double elevationDeg, double azimuthDeg) const; virtual std::string getTargetLabel(const IMobility *target) const; + // the title of an observer's plot from titleFormat; override to name the observer from + // other data, as getTargetLabel() reads satelliteName + virtual std::string getObserverTitle(const IMobility *observer) const; virtual cFigure::Rectangle getViewportBounds() const; // the target module's drawing area, used to tile detached plots below it public: diff --git a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned index d6f0e4c5f38..b73421877e8 100644 --- a/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned +++ b/src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned @@ -23,6 +23,11 @@ import inet.visualizer.contract.IGeoSkyViewVisualizer; // its display name, or its full name if no display name is set. In the grid layout // the plots are 2.5 * `plotRadius` apart, so a wider title overlaps the next one. // +// The title format string can contain the following directives: +// - %n observer node full name +// - %N observer node display name (the full name if no display name is set) +// - %p observer node full path +// // Look angles are computed from the geographic positions of the observer and the // targets via the `IGeographicCoordinateSystem` given by `coordinateSystemModule`. // @@ -52,7 +57,8 @@ simple GeoSkyViewCanvasVisualizer extends VisualizerBase like IGeoSkyViewVisuali double maskOpacity = default(0.25); // Opacity of the below-minimum-elevation mask string cardinalColor = default("#888888"); // Color of the N-S/W-E lines and the N/E/S/W labels string labelColor = default("black"); // Color of the title and elevation labels - bool displayTitles = default(true); // Display a title with the observer node's name above each plot + bool displayTitles = default(true); // Display a title above each plot, with the text given by titleFormat + string titleFormat = default("%N"); // Determines what is displayed in the title, see the directives above string targetColor = default("dark"); // Target dot color, a list of colors or a color group name (e.g. dark, light) double targetRadius = default(3); // Radius of a target dot in pixels diff --git a/tests/module/GeoSkyViewCanvasVisualizer_titles.test b/tests/module/GeoSkyViewCanvasVisualizer_titles.test index 075dc65a4ff..36614e3fc08 100644 --- a/tests/module/GeoSkyViewCanvasVisualizer_titles.test +++ b/tests/module/GeoSkyViewCanvasVisualizer_titles.test @@ -1,12 +1,13 @@ %description: Checks the titles of the GeoSkyViewCanvasVisualizer sky plots. Cmdenv runs with -its fake graphical user interface, so the visualizers draw. Two visualizers +its fake graphical user interface, so the visualizers draw. Three visualizers watch the same two ground stations: gs[0] has the display name "Lima", gs[1] has none. At the end of the run a checker prints the label texts of each sky plot group on the network canvas, with the tags of its visualizer, and the plot's center with the vertical position of each label relative to it: - - default: the title is the display name, or the full name if none is set + - default: the title is the display name, or the full name if none is set (%N) + - format: titleFormat = "%p (%n)" gives the full path and the full name - hidden: displayTitles = false draws no title The layout uses the default plotRadius of 70 pixels. The network's bgb of @@ -80,6 +81,7 @@ network Test submodules: coordinateSystem: Wgs84EcefGeographicCoordinateSystem; skyViewDefault: GeoSkyViewCanvasVisualizer; + skyViewFormat: GeoSkyViewCanvasVisualizer; skyViewHidden: GeoSkyViewCanvasVisualizer; checker: SkyViewLabelChecker; gs[2]: GroundStation; @@ -105,6 +107,8 @@ cmdenv-fake-gui = true *.skyView*.observerFilter = "**.gs[*].mobility" *.skyView*.targetFilter = "**.sat[*].mobility" *.skyViewDefault.tags = "default" +*.skyViewFormat.tags = "format" +*.skyViewFormat.titleFormat = "%p (%n)" *.skyViewHidden.tags = "hidden" *.skyViewHidden.displayTitles = false @@ -117,6 +121,10 @@ SKYVIEW sky_view default: [N] [S] [E] [W] [Lima] [30 %contains: stdout SKYVIEW sky_view default: [N] [S] [E] [W] [gs[1]] [30 %contains: stdout +SKYVIEW sky_view format: [N] [S] [E] [W] [Test.gs[0] (gs[0])] [30 +%contains: stdout +SKYVIEW sky_view format: [N] [S] [E] [W] [Test.gs[1] (gs[1])] [30 +%contains: stdout SKYVIEW sky_view hidden: [N] [S] [E] [W] [30 %contains: stdout LAYOUT sky_view default: plot at 70 319; [N -71] [S 71] [E 0] [W 0] [Lima -84] [30