Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions doc/src/users-guide/ch-visualization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
3 changes: 3 additions & 0 deletions doc/whatsnew/compatible/visualizer-sky-view-title-format.txt
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions doc/whatsnew/fixes/visualizer-sky-view-observer-titles.txt
Original file line number Diff line number Diff line change
@@ -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.
38 changes: 34 additions & 4 deletions src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -54,6 +56,8 @@ void GeoSkyViewCanvasVisualizer::initialize(int stage)
maskOpacity = par("maskOpacity");
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");
Expand Down Expand Up @@ -309,9 +313,13 @@ 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)
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. 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];
Expand Down Expand Up @@ -367,18 +375,40 @@ 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<const cModule *>(observer);
auto node = module->getParentModule() != nullptr ? module->getParentModule() : module;
std::function<std::string(char)> 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
// 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));
}

Expand All @@ -390,7 +420,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<const cModule *>(plot->observer);
Expand Down
6 changes: 6 additions & 0 deletions src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <map>

#include "inet/common/StringFormat.h"
#include "inet/common/figures/TrailFigure.h"
#include "inet/common/geometry/common/GeographicCoordinateSystem.h"
#include "inet/mobility/contract/IMobility.h"
Expand Down Expand Up @@ -88,6 +89,8 @@ class INET_API GeoSkyViewCanvasVisualizer : public VisualizerBase, public cListe
double maskOpacity = NaN;
cFigure::Color cardinalColor;
cFigure::Color labelColor;
bool displayTitles = false;
StringFormat titleFormat;

ColorSet targetColorSet;
double targetRadius = NaN;
Expand Down Expand Up @@ -122,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:
Expand Down
11 changes: 10 additions & 1 deletion src/inet/visualizer/canvas/scene/GeoSkyViewCanvasVisualizer.ned
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ 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.
//
// 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`.
Expand Down Expand Up @@ -50,6 +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 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
Expand Down
4 changes: 2 additions & 2 deletions tests/fingerprint/store.json
Original file line number Diff line number Diff line change
Expand Up @@ -20515,8 +20515,8 @@
"sim_time_limit": "3600s",
"test_result": "PASS",
"ingredients": "tyf",
"fingerprint": "772e-b194",
"timestamp": 1792589940.0,
"fingerprint": "9471-891b",
"timestamp": 1793184170.0,
"itervars": "$repetition==0"
},
{
Expand Down
141 changes: 141 additions & 0 deletions tests/module/GeoSkyViewCanvasVisualizer_titles.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
%description:

Checks the titles of the GeoSkyViewCanvasVisualizer sky plots. Cmdenv runs with
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 (%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
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<cGroupFigure *>(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<cLabelFigure *>(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<cLabelFigure *>(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: <default("inet.mobility.static.StationaryMobility")> like IMobility;
}

network Test
{
parameters:
@display("bgb=300,200");
submodules:
coordinateSystem: Wgs84EcefGeographicCoordinateSystem;
skyViewDefault: GeoSkyViewCanvasVisualizer;
skyViewFormat: 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"
*.skyViewFormat.tags = "format"
*.skyViewFormat.titleFormat = "%p (%n)"
*.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 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
%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: (
%#--------------------------------------------------------------------------------------------------------------
Loading