Skip to content

Commit

Permalink
feat: display outbound leg information for holds
Browse files Browse the repository at this point in the history
  • Loading branch information
AndyTWF committed Jan 15, 2023
1 parent a8b98bd commit ac3b359
Show file tree
Hide file tree
Showing 20 changed files with 349 additions and 8 deletions.
2 changes: 1 addition & 1 deletion src/plugin/CMakeLists.txt
Expand Up @@ -269,7 +269,7 @@ set(src__flightrule
source_group("src\\flightrule" FILES ${src__flightrule})

set(src__geometry
geometry/Line.cpp geometry/Line.h geometry/DistanceRadiusToScreenRadius.cpp geometry/DistanceRadiusToScreenRadius.h)
geometry/Line.cpp geometry/Line.h geometry/DistanceRadiusToScreenRadius.cpp geometry/DistanceRadiusToScreenRadius.h geometry/MeasurementUnitType.h geometry/MeasurementUnitFactory.cpp geometry/MeasurementUnitFactory.h geometry/Measurement.cpp geometry/Measurement.h geometry/MeasurementUnit.cpp geometry/MeasurementUnit.h)
source_group("src\\geometry" FILES ${src__geometry})

set(src__graphics
Expand Down
12 changes: 12 additions & 0 deletions src/plugin/geometry/Measurement.cpp
@@ -0,0 +1,12 @@
#include "Measurement.h"
#include "MeasurementUnit.h"

namespace UKControllerPlugin::Geometry {

Measurement::Measurement(std::unique_ptr<MeasurementUnit> unit, double value) : unit(std::move(unit)), value(value)
{
assert(this->unit && "Invalid unit in measurement");
}

Measurement::~Measurement() = default;
} // namespace UKControllerPlugin::Geometry
23 changes: 23 additions & 0 deletions src/plugin/geometry/Measurement.h
@@ -0,0 +1,23 @@
#pragma once
#include "MeasurementUnit.h"

namespace UKControllerPlugin::Geometry {

struct MeasurementUnit;

/**
* A measurement, comprising a unit and a value
*/
struct Measurement
{
public:
Measurement(std::unique_ptr<MeasurementUnit> unit, double value);
~Measurement();

// The unit for the measurement
std::unique_ptr<MeasurementUnit> unit;

// The value of the measurement
double value;
};
} // namespace UKControllerPlugin::Geometry
8 changes: 8 additions & 0 deletions src/plugin/geometry/MeasurementUnit.cpp
@@ -0,0 +1,8 @@
#include "MeasurementUnit.h"

namespace UKControllerPlugin::Geometry {
auto MeasurementUnit::operator==(const MeasurementUnitType& type) const -> bool
{
return this->type == type;
}
} // namespace UKControllerPlugin::Geometry
18 changes: 18 additions & 0 deletions src/plugin/geometry/MeasurementUnit.h
@@ -0,0 +1,18 @@
#pragma once
#include "MeasurementUnitType.h"

namespace UKControllerPlugin::Geometry {
struct MeasurementUnit
{
MeasurementUnit(MeasurementUnitType type, std::string description)
: type(type), description(std::move(description)){};

// An enum representing the unit type
MeasurementUnitType type;

// A description of the unit
std::string description;

[[nodiscard]] auto operator==(const MeasurementUnitType& type) const -> bool;
};
} // namespace UKControllerPlugin::Geometry
50 changes: 50 additions & 0 deletions src/plugin/geometry/MeasurementUnitFactory.cpp
@@ -0,0 +1,50 @@
#include "MeasurementUnit.h"
#include "MeasurementUnitFactory.h"

namespace UKControllerPlugin::Geometry {

auto UnitTypeFromString(const std::string& unitString) -> MeasurementUnitType
{
if (unitString == "s") {
return MeasurementUnitType::Seconds;
}

if (unitString == "min") {
return MeasurementUnitType::Minutes;
}

if (unitString == "nm") {
return MeasurementUnitType::NauticalMiles;
}

return MeasurementUnitType::None;
}

auto UnitToString(MeasurementUnitType unit) -> std::string
{
if (unit == MeasurementUnitType::Seconds) {
return "Seconds";
}

if (unit == MeasurementUnitType::Minutes) {
return "Minutes";
}

if (unit == MeasurementUnitType::NauticalMiles) {
return "NM";
}

return "??";
}

auto UnitFromString(const std::string& unitString) -> std::unique_ptr<MeasurementUnit>
{
const auto unitType = UnitTypeFromString(unitString);
return std::make_unique<MeasurementUnit>(unitType, UnitToString(unitType));
}

auto UnitStringValid(const std::string& unitString) -> bool
{
return UnitTypeFromString(unitString) != MeasurementUnitType::None;
}
} // namespace UKControllerPlugin::Geometry
9 changes: 9 additions & 0 deletions src/plugin/geometry/MeasurementUnitFactory.h
@@ -0,0 +1,9 @@
#pragma once
#include "MeasurementUnitType.h"

namespace UKControllerPlugin::Geometry {
struct MeasurementUnit;

[[nodiscard]] auto UnitFromString(const std::string& unitString) -> std::unique_ptr<MeasurementUnit>;
[[nodiscard]] auto UnitStringValid(const std::string& unitString) -> bool;
} // namespace UKControllerPlugin::Geometry
11 changes: 11 additions & 0 deletions src/plugin/geometry/MeasurementUnitType.h
@@ -0,0 +1,11 @@
#pragma once

namespace UKControllerPlugin::Geometry {
enum class MeasurementUnitType : unsigned int
{
None,
Seconds,
Minutes,
NauticalMiles,
};
} // namespace UKControllerPlugin::Geometry
17 changes: 16 additions & 1 deletion src/plugin/hold/HoldDisplay.cpp
Expand Up @@ -14,6 +14,8 @@
#include "euroscope/EuroscopePluginLoopbackInterface.h"
#include "euroscope/EuroscopeRadarLoopbackInterface.h"
#include "euroscope/UserSetting.h"
#include "geometry/Measurement.h"
#include "geometry/MeasurementUnit.h"
#include "graphics/GdiGraphicsInterface.h"
#include "list/PopupListInterface.h"
#include "navaids/Navaid.h"
Expand Down Expand Up @@ -686,7 +688,8 @@ namespace UKControllerPlugin {
const HoldingData* hold = *this->publishedHolds.cbegin();

// Render the data
Gdiplus::Rect dataRect = {this->windowPos.x, this->dataStartHeight, this->windowWidth, this->lineHeight};
Gdiplus::Rect dataRect = {this->windowPos.x, this->dataStartHeight - 10, this->windowWidth,
this->lineHeight};

graphics.DrawString(
std::wstring(L"Fix: ") + ConvertToTchar(this->navaid.identifier), dataRect, this->dataBrush);
Expand All @@ -703,6 +706,18 @@ namespace UKControllerPlugin {

dataRect.Y = dataRect.Y + this->lineHeight + 5;
graphics.DrawString(std::wstring(L"Minimum: ") + ConvertToTchar(hold->minimum), dataRect, this->dataBrush);

dataRect.Y = dataRect.Y + this->lineHeight + 5;

if (*hold->outboundLeg->unit == Geometry::MeasurementUnitType::None) {
graphics.DrawString(std::wstring(L"Outbound Leg: --"), dataRect, this->dataBrush);
} else {
graphics.DrawString(
std::wstring(L"Outbound Leg: ") + FormatOutboundLegValue(hold->outboundLeg->value) + L" " +
ConvertToTchar(hold->outboundLeg->unit->description),
dataRect,
this->dataBrush);
}
}

/*
Expand Down
10 changes: 10 additions & 0 deletions src/plugin/hold/HoldDisplayFunctions.cpp
Expand Up @@ -111,5 +111,15 @@ namespace UKControllerPlugin {

return verticalSpeed > 0 ? 1 : -1;
}

auto FormatOutboundLegValue(double value) -> std::wstring
{
std::wstringstream stream;
stream.setf(std::ios::fixed);
stream.precision(1);
stream << value;

return stream.str();
}
} // namespace Hold
} // namespace UKControllerPlugin
1 change: 1 addition & 0 deletions src/plugin/hold/HoldDisplayFunctions.h
Expand Up @@ -14,5 +14,6 @@ namespace UKControllerPlugin {
unsigned int GetDisplayRow(int holdMax, int occupiedLevel);
std::wstring GetTimeInHoldDisplayString(const std::chrono::system_clock::time_point& entryTime);
int GetVerticalSpeedDirection(int verticalSpeed);
[[nodiscard]] auto FormatOutboundLegValue(double value) -> std::wstring;
} // namespace Hold
} // namespace UKControllerPlugin
2 changes: 1 addition & 1 deletion src/plugin/hold/HoldingData.cpp
Expand Up @@ -11,7 +11,7 @@ namespace UKControllerPlugin::Hold {
: identifier(original.identifier), fix(original.fix), description(original.description),
minimum(original.minimum), maximum(original.maximum), inbound(original.inbound),
turnDirection(original.turnDirection), restrictions(std::move(original.restrictions)),
deemedSeparatedHolds(std::move(original.deemedSeparatedHolds))
deemedSeparatedHolds(std::move(original.deemedSeparatedHolds)), outboundLeg(std::move(original.outboundLeg))
{
}

Expand Down
15 changes: 13 additions & 2 deletions src/plugin/hold/HoldingData.h
@@ -1,4 +1,7 @@
#pragma once
#include "geometry/Measurement.h"
#include "geometry/MeasurementUnit.h"
#include "geometry/MeasurementUnitType.h"

namespace UKControllerPlugin {
namespace Hold {
Expand All @@ -19,10 +22,15 @@ namespace UKControllerPlugin {
unsigned int inbound = 361,
std::string turnDirection = "up",
std::set<std::unique_ptr<AbstractHoldLevelRestriction>> restrictions = {},
std::set<std::unique_ptr<DeemedSeparatedHold>> deemedSeparatedHolds = {})
std::set<std::unique_ptr<DeemedSeparatedHold>> deemedSeparatedHolds = {},
std::unique_ptr<Geometry::Measurement> outboundLeg = std::make_unique<Geometry::Measurement>(
std::make_unique<Geometry::MeasurementUnit>(Geometry::MeasurementUnitType::None, "Unknown Units"),
-1.0))
: identifier(identifier), fix(fix), description(description), minimum(minimum), maximum(maximum),
inbound(inbound), turnDirection(turnDirection), restrictions(std::move(restrictions)),
deemedSeparatedHolds(std::move(deemedSeparatedHolds)){};
deemedSeparatedHolds(std::move(deemedSeparatedHolds)), outboundLeg(std::move(outboundLeg))
{
}
~HoldingData();
HoldingData(HoldingData const&) = delete;
HoldingData& operator=(HoldingData const&) = delete;
Expand Down Expand Up @@ -57,6 +65,9 @@ namespace UKControllerPlugin {
// Holds against which this hold is deemed separated
std::set<std::unique_ptr<DeemedSeparatedHold>> deemedSeparatedHolds;

// The outbound leg
std::unique_ptr<Geometry::Measurement> outboundLeg;

/*
Compare two holds
*/
Expand Down
14 changes: 13 additions & 1 deletion src/plugin/hold/HoldingDataSerializer.cpp
Expand Up @@ -5,6 +5,8 @@
#include "HoldingDataSerializer.h"
#include "HoldRestrictionSerializer.h"
#include "bootstrap/PersistenceContainer.h"
#include "geometry/MeasurementUnitType.h"
#include "geometry/MeasurementUnitFactory.h"

using UKControllerPlugin::Bootstrap::PersistenceContainer;

Expand All @@ -29,6 +31,12 @@ namespace UKControllerPlugin::Hold {
json.at("maximum_altitude").get_to(holdingData.maximum);
json.at("inbound_heading").get_to(holdingData.inbound);
json.at("turn_direction").get_to(holdingData.turnDirection);

if (!json.at("outbound_leg_unit").is_null()) {
holdingData.outboundLeg = std::make_unique<Geometry::Measurement>(
Geometry::UnitFromString(json.at("outbound_leg_unit").get<std::string>()),
json.at("outbound_leg_value").get<double>());
}
}

void from_json_with_restrictions(
Expand Down Expand Up @@ -60,6 +68,10 @@ namespace UKControllerPlugin::Hold {
data.at("turn_direction").is_string() &&
(data.at("turn_direction") == "left" || data.at("turn_direction") == "right") &&
data.find("restrictions") != data.end() && data.at("restrictions").is_array() &&
data.contains("deemed_separated_holds") && data.at("deemed_separated_holds").is_array();
data.contains("deemed_separated_holds") && data.at("deemed_separated_holds").is_array() &&
data.contains("outbound_leg_value") && data.contains("outbound_leg_unit") &&
(data.at("outbound_leg_unit").is_null() ||
(data.at("outbound_leg_value").is_number() && data.at("outbound_leg_unit").is_string() &&
Geometry::UnitStringValid(data.at("outbound_leg_unit").get<std::string>())));
}
} // namespace UKControllerPlugin::Hold
2 changes: 1 addition & 1 deletion test/plugin/CMakeLists.txt
Expand Up @@ -146,7 +146,7 @@ set(test__flightrule
source_group("test\\flightrule" FILES ${test__flightrule})

set(test__geometry
geometry/LineTest.cpp geometry/DistanceRadiusToScreenRadiusTest.cpp)
geometry/LineTest.cpp geometry/DistanceRadiusToScreenRadiusTest.cpp geometry/MeasurementUnitFactoryTest.cpp)
source_group("test\\geometry" FILES ${test__geometry})

set(test__handoff
Expand Down
61 changes: 61 additions & 0 deletions test/plugin/geometry/MeasurementUnitFactoryTest.cpp
@@ -0,0 +1,61 @@
#include "geometry/MeasurementUnit.h"
#include "geometry/MeasurementUnitType.h"
#include "geometry/MeasurementUnitFactory.h"

using UKControllerPlugin::Geometry::MeasurementUnitType;
using UKControllerPlugin::Geometry::UnitFromString;
using UKControllerPlugin::Geometry::UnitStringValid;

namespace UKControllerPluginTest::Geometry {
class MeasurementUnitFactoryTest : public testing::Test
{
};

TEST_F(MeasurementUnitFactoryTest, ItReturnsSecondsFromString)
{
const auto unit = UnitFromString("s");
EXPECT_EQ(*unit, MeasurementUnitType::Seconds);
EXPECT_EQ(unit->description, "Seconds");
}

TEST_F(MeasurementUnitFactoryTest, ItReturnsMinutesFromString)
{
const auto unit = UnitFromString("min");
EXPECT_EQ(*unit, MeasurementUnitType::Minutes);
EXPECT_EQ(unit->description, "Minutes");
}

TEST_F(MeasurementUnitFactoryTest, ItReturnsNauticalMilesFromString)
{
const auto unit = UnitFromString("nm");
EXPECT_EQ(*unit, MeasurementUnitType::NauticalMiles);
EXPECT_EQ(unit->description, "NM");
}

TEST_F(MeasurementUnitFactoryTest, ItReturnsUnknownUnitFromString)
{
const auto unit = UnitFromString("abc");
EXPECT_EQ(*unit, MeasurementUnitType::None);
EXPECT_EQ(unit->description, "??");
}

TEST_F(MeasurementUnitFactoryTest, StringIsValidSeconds)
{
EXPECT_TRUE(UnitStringValid("s"));
}

TEST_F(MeasurementUnitFactoryTest, StringIsValidMinutes)
{
EXPECT_TRUE(UnitStringValid("min"));
}

TEST_F(MeasurementUnitFactoryTest, StringIsValidNauticalMiles)
{
EXPECT_TRUE(UnitStringValid("nm"));
}

TEST_F(MeasurementUnitFactoryTest, StringIsNotValidUnknownString)
{
EXPECT_FALSE(UnitStringValid("abc"));
}
} // namespace UKControllerPluginTest::Geometry
15 changes: 15 additions & 0 deletions test/plugin/hold/HoldDisplayFunctionsTest.cpp
Expand Up @@ -169,4 +169,19 @@ namespace UKControllerPluginTest::Hold {
{
EXPECT_EQ(-1, UKControllerPlugin::Hold::GetVerticalSpeedDirection(-500));
}

TEST(HoldDisplayFunctionsTest, ItReturnsOutboundLegValueIfWhole)
{
EXPECT_EQ(std::wstring(L"2.0"), UKControllerPlugin::Hold::FormatOutboundLegValue(2.0));
}

TEST(HoldDisplayFunctionsTest, ItReturnsOutboundLegValueIfMultipleDecimalPlaces)
{
EXPECT_EQ(std::wstring(L"3.1"), UKControllerPlugin::Hold::FormatOutboundLegValue(3.134));
}

TEST(HoldDisplayFunctionsTest, ItReturnsOutboundLegValueIfOneDecimalPlace)
{
EXPECT_EQ(std::wstring(L"4.5"), UKControllerPlugin::Hold::FormatOutboundLegValue(4.5));
}
} // namespace UKControllerPluginTest::Hold

0 comments on commit ac3b359

Please sign in to comment.