From 89b6ca07af7c5482fa835ba42248b70542abd4b1 Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 27 Oct 2026 19:23:24 +0100 Subject: [PATCH] feat(editor): the norms of a slip switch bent into a curve, as ST-T1-A6 sees it: switch_curve::check_slip tells that a slip is bent in a side track only, never in the running nor the main tracks (11.4 pkt 4b), that a track through it is to keep its curvature of one sign (9.7 pkt 12), that a slip curve bent the way the curve turns gets sharper and no sharper than the 180 m of a side track (table 9.2), and the cant as over a bent switch, with none recommended in a side track (9.3 pkt 5). The cant over a switch is checked by one function for both. Unit tests: a Rkpd of R 190 m bent into R 800 m has its slip curves of R 154 m, below the side track, and 249 m; into R 4000 m of R 181 m, within it; on a straight it's no bent one --- editor/editorSwitchCurve.cpp | 106 +++++++++++++++++++++++++------ editor/editorSwitchCurve.hpp | 22 +++++++ tests/unit/test_switch_curve.cpp | 68 ++++++++++++++++++++ 3 files changed, 175 insertions(+), 21 deletions(-) diff --git a/editor/editorSwitchCurve.cpp b/editor/editorSwitchCurve.cpp index 95fb8cc4..5142c885 100644 --- a/editor/editorSwitchCurve.cpp +++ b/editor/editorSwitchCurve.cpp @@ -28,6 +28,8 @@ constexpr double kStraight{2e-5}; constexpr double kAtEnd{0.3}; // m: how far off the switch a ramp keeps, 9.6 pkt 16 constexpr double kRampClear{6.0}; +// m: the least radius of a side track, table 9.2 +constexpr double kSideRadius{180.0}; // the curved switches bent from the basic ones, table 11.9: the radius of the basic switch, the limiting curvature of the // diverging track and the least radius of the main track; the values in its brackets, allowed only by way of exception, are past @@ -64,6 +66,34 @@ double cant_at(std::vector const &Track, double const Station) return previous->cant + (next->cant - previous->cant) * (Station - previous->station) / span; } +// the cant of the line over a switch From to To along it: the same all along it, a ramp off it by 6 m at least or ending at its ends +// (11.4 pkt 7, 9.6 pkt 16-17), as much as table 9.6 lets a switch have, Curved or straight +void check_cant(std::vector const &Line, double const From, double const To, bool const Curved, double &Start, double &End, std::vector &Notes, std::vector &Warnings) +{ + Start = std::abs(cant_at(Line, From)); + End = std::abs(cant_at(Line, To)); + if (std::abs(End - Start) > 1.0) + Warnings.emplace_back(format(STR_C("The cant changes within the switch, %.0f to %.0f mm: it's to stay the same over it (11.4 pkt 7)"), Start, End)); + auto const low{std::min(From, To)}; + auto const high{std::max(From, To)}; + for (auto const end : ramp_ends(Line)) + { + if (end > low + kAtEnd && end < high - kAtEnd) + Warnings.emplace_back(format(STR_C("A cant ramp starts or ends within the switch, %.1f m from its start: at the start or the end of the switch, or 6 m off it at least (9.6 pkt 16-17)"), std::abs(end - From))); + else if (std::abs(end - low) <= kAtEnd || std::abs(end - high) <= kAtEnd) + Notes.emplace_back(STR_C("A cant ramp starts or ends at an end of the switch")); + else if (auto const off{end < low ? low - end : end - high}; off < kRampClear) + Warnings.emplace_back(format(STR_C("A cant ramp ends %.1f m off the switch, 6 m at least (9.6 pkt 16)"), off)); + } + auto const cant{std::max(Start, End)}; + if (Curved && cant > 100.0 + 1e-6) + Warnings.emplace_back(format(STR_C("Cant %.0f mm in a bent switch, over 100 mm (table 9.6)"), cant)); + else if (Curved && cant > 60.0 + 1e-6) + Notes.emplace_back(format(STR_C("Cant %.0f mm: over the 60 mm recommended for a bent switch, up to 100 mm (table 9.6)"), cant)); + else if (false == Curved && cant > 1.0) + Notes.emplace_back(format(STR_C("Cant %.0f mm in a straight switch: none recommended, up to 100 mm (table 9.6)"), cant)); +} + } // namespace double curvature_at(std::vector const &Track, double const Station) @@ -140,8 +170,6 @@ switch_report check(std::vector const &Main, std::vector kStraight; report.transition = report.curved && std::abs(main.last - main.first) > std::max(2e-6, 0.05 * std::max(std::abs(main.first), std::abs(main.last))); @@ -166,27 +194,63 @@ switch_report check(std::vector const &Main, std::vector 1.0) - report.warnings.emplace_back(format(STR_C("The cant changes within the switch, %.0f to %.0f mm: it's to stay the same over it (11.4 pkt 7)"), report.cant_start, report.cant_end)); - auto const low{std::min(From, To)}; - auto const high{std::max(From, To)}; - for (auto const end : ramp_ends(Line)) + check_cant(Line, From, To, report.curved, report.cant_start, report.cant_end, report.notes, report.warnings); + return report; +} + +slip_report check_slip(std::vector> const &Tracks, std::vector> const &Curves, std::vector const &Line, double const From, double const To) +{ + slip_report report; + if (Tracks.size() < 2 || Line.size() < 2 || std::any_of(Tracks.begin(), Tracks.end(), [](std::vector const &Track) { return Track.size() < 2; })) + return report; + // the curvatures counted positive the way the line turns, as the first track goes along it + auto const sign{(Line.back().heading - Line.front().heading < 0.0 ? -1.0 : 1.0) * (To >= From ? 1.0 : -1.0)}; + struct span { - if (end > low + kAtEnd && end < high - kAtEnd) - report.warnings.emplace_back(format(STR_C("A cant ramp starts or ends within the switch, %.1f m from its start: at the start or the end of the switch, or 6 m off it at least (9.6 pkt 16-17)"), std::abs(end - From))); - else if (std::abs(end - low) <= kAtEnd || std::abs(end - high) <= kAtEnd) - report.notes.emplace_back(STR_C("A cant ramp starts or ends at an end of the switch")); - else if (auto const off{end < low ? low - end : end - high}; off < kRampClear) - report.warnings.emplace_back(format(STR_C("A cant ramp ends %.1f m off the switch, 6 m at least (9.6 pkt 16)"), off)); + double low{std::numeric_limits::max()}; + double high{std::numeric_limits::lowest()}; + double sharpest{0.0}; + }; + auto const along = [&](std::vector const &Track) { + span result; + auto const margin{std::min(1.0, (Track.back().station - Track.front().station) * 0.25)}; + for (auto s{Track.front().station + margin}; s <= Track.back().station - margin + 1e-9; s += 0.5) + { + auto const curvature{sign * curvature_at(Track, s)}; + result.low = std::min(result.low, curvature); + result.high = std::max(result.high, curvature); + result.sharpest = std::max(result.sharpest, std::abs(curvature)); + } + return result; + }; + auto const middle = [&](std::vector const &Track) { return sign * curvature_at(Track, 0.5 * (Track.front().station + Track.back().station)); }; + report.first = middle(Tracks[0]); + report.second = middle(Tracks[1]); + // the curvature of each track through it of one sign all along it, as in the main track of a switch (9.7 pkt 12); the slip curves + // begin with the leads along the stock rails, which bend with the line even where the curve turns against it + auto turning{false}; + for (auto const &track : Tracks) + { + auto const curvatures{along(track)}; + report.curved = report.curved || curvatures.sharpest > kStraight; + turning = turning || (curvatures.low < -kStraight && curvatures.high > kStraight); } - auto const cant{std::max(report.cant_start, report.cant_end)}; - if (report.curved && cant > 100.0 + 1e-6) - report.warnings.emplace_back(format(STR_C("Cant %.0f mm in a bent switch, over 100 mm (table 9.6)"), cant)); - else if (report.curved && cant > 60.0 + 1e-6) - report.notes.emplace_back(format(STR_C("Cant %.0f mm: over the 60 mm recommended for a bent switch, up to 100 mm (table 9.6)"), cant)); - else if (false == report.curved && cant > 1.0) - report.notes.emplace_back(format(STR_C("Cant %.0f mm in a straight switch: none recommended, up to 100 mm (table 9.6)"), cant)); + if (turning) + report.warnings.emplace_back(STR_C("The curvature of a track through the slip turns from one side to the other within it, which isn't allowed (9.7 pkt 12)")); + for (auto const &curve : Curves) + if (curve.size() >= 2) + report.curves.push_back(along(curve).sharpest); + if (report.curved) + { + report.warnings.emplace_back(STR_C("A slip isn't bent in the running nor in the main tracks, a bent one goes into a side track only (11.4 pkt 4b)")); + // bent the way the curve of the slip turns it gets sharper: no sharper than the side tracks take + for (auto const curvature : report.curves) + if (curvature > 1.0 / kSideRadius + 1e-9) + report.warnings.emplace_back(format(STR_C("A slip curve bent to R %.0f m, below the least %.0f m of a side track (table 9.2)"), 1.0 / curvature, kSideRadius)); + } + check_cant(Line, From, To, report.curved, report.cant_start, report.cant_end, report.notes, report.warnings); + if (report.curved && std::max(report.cant_start, report.cant_end) > 1.0) + report.notes.emplace_back(STR_C("In a side track the layouts needing no cant are recommended (9.3 pkt 5)")); return report; } diff --git a/editor/editorSwitchCurve.hpp b/editor/editorSwitchCurve.hpp index bde0f459..d61f942b 100644 --- a/editor/editorSwitchCurve.hpp +++ b/editor/editorSwitchCurve.hpp @@ -54,6 +54,28 @@ struct switch_report switch_report check(std::vector const &Main, std::vector const &Branch, std::vector const &Line, double const From, double const To, double const Radius, double const Ratio); +// a slip switch bent into a curve, as ST-T1-A6 sees it: in a side track only (11.4 pkt 4b), the curvature of each track through it of +// one sign all along it (9.7 pkt 12), its slip curves no sharper than a side track may be (table 9.2), the cant as over a switch bent, +// none recommended in a side track (9.3 pkt 5) +struct slip_report +{ + // of the two tracks through the slip in its middle, 1/m, positive turning the way the line does + double first{0.0}; + double second{0.0}; + // of the slip curves at their sharpest, 1/m + std::vector curves; + // of the line, mm + double cant_start{0.0}; + double cant_end{0.0}; + bool curved{false}; // the tracks through it bent + std::vector notes; + std::vector warnings; +}; + +// Tracks: the two tracks through the slip, Curves: its slip curves, each from its start, the stations from 0 there; Line as for a switch, +// From and To the stations of the ends of the first track through the slip along it +slip_report check_slip(std::vector> const &Tracks, std::vector> const &Curves, std::vector const &Line, double const From, double const To); + // the start of a switch Length long, going Direction along the line, drawn to where its start or its end meets an end of a cant ramp, // or to where it keeps the 6 m off it, when the station pressed is within Reach of that; What receives which of them, empty for none double snapped_start(std::vector const &Ramps, double const Pressed, double const Length, int const Direction, double const Reach, std::string *What = nullptr); diff --git a/tests/unit/test_switch_curve.cpp b/tests/unit/test_switch_curve.cpp index 53f3bd8c..206d32e1 100644 --- a/tests/unit/test_switch_curve.cpp +++ b/tests/unit/test_switch_curve.cpp @@ -99,3 +99,71 @@ TEST_CASE("switch on a curve: the ends of a cant ramp at the ends of the switch CHECK(switch_curve::snapped_start(ramps, 160.0, 30.0, 1, 3.0, &what) == doctest::Approx(160.0)); CHECK(what.empty()); } + +namespace { + +// a track of one curvature from its start, heading as given there, a point every half metre +std::vector arc(double const Length, double const Heading, double const Curvature) { + std::vector points; + for (double s{0.0}; s <= Length + 1e-9; s += 0.5) + points.push_back({s, Heading + Curvature * s, 0.0}); + return points; +} + +// a slip of 1:9 with the slip curves of R 190 m bent onto the line from the station on: the tracks through it with the curvature of the +// line, the slip curves with theirs plus the one of the line, one turning as the line does, the other against it +switch_curve::slip_report bent_slip(std::vector const &Line, double const From, double const Curvature) { + auto const angle{std::atan(1.0 / 9.0)}; + auto const half{16.6}; + auto const tracks{std::vector{arc(2.0 * half, 0.0, Curvature), arc(2.0 * half, angle, Curvature * std::cos(angle))}}; + auto const curves{std::vector{arc(190.0 * angle, 0.0, 1.0 / 190.0 + Curvature), arc(190.0 * angle, angle, -1.0 / 190.0 + Curvature)}}; + return switch_curve::check_slip(tracks, curves, Line, From, From + 2.0 * half); +} + +} // namespace + +TEST_CASE("slip bent into a curve: in a side track only, its slip curve bent the way of the curve no sharper than a side track takes") { + auto const sharp{bent_slip(line(0.0, 0.0, 400.0, 800.0, 0.0), 150.0, 1.0 / 800.0)}; + CHECK(sharp.curved); + CHECK(says(sharp.warnings, "11.4 pkt 4b")); + CHECK(sharp.first == doctest::Approx(1.0 / 800.0).epsilon(0.02)); + CHECK(sharp.second == doctest::Approx(std::cos(std::atan(1.0 / 9.0)) / 800.0).epsilon(0.02)); + REQUIRE(sharp.curves.size() == 2); + // 1/190 + 1/800: R 153.5 m, 1/190 - 1/800: R 249.2 m + CHECK(1.0 / sharp.curves[0] == doctest::Approx(153.5).epsilon(0.01)); + CHECK(1.0 / sharp.curves[1] == doctest::Approx(249.2).epsilon(0.01)); + CHECK(says(sharp.warnings, "R 154 m, below the least 180 m")); + CHECK(false == says(sharp.warnings, "R 249 m")); + CHECK(false == says(sharp.warnings, "9.7 pkt 12")); + + // R 4000 m: the slip curve of 1/190 + 1/4000, R 181.4 m, still within the side tracks + auto const gentle{bent_slip(line(0.0, 0.0, 400.0, 4000.0, 0.0), 150.0, 1.0 / 4000.0)}; + CHECK(gentle.curved); + CHECK(says(gentle.warnings, "11.4 pkt 4b")); + CHECK(false == says(gentle.warnings, "table 9.2")); + CHECK(gentle.notes.empty()); + + // one on a straight is no bent one + auto const straight{bent_slip(line(400.0, 0.0, 0.0, 1000.0, 0.0), 150.0, 0.0)}; + CHECK_FALSE(straight.curved); + CHECK(straight.warnings.empty()); +} + +TEST_CASE("slip bent into a curve: the cant of the side track, a track through it turning from one side to the other") { + // the cant the same over it, none recommended in a side track though + auto const canted{bent_slip(line(0.0, 0.0, 400.0, 1000.0, 30.0), 150.0, 1.0 / 1000.0)}; + CHECK(canted.cant_start == doctest::Approx(30.0)); + CHECK(canted.cant_end == doctest::Approx(30.0)); + CHECK(false == says(canted.warnings, "The cant changes")); + CHECK(says(canted.notes, "9.3 pkt 5")); + + // a reverse curve under it + auto const curve{line(0.0, 0.0, 400.0, 1000.0, 0.0)}; + auto first{arc(33.2, 0.0, 1.0 / 1000.0)}; + for (auto &point : first) + if (point.station > 16.6) + point.heading = 16.6 / 1000.0 - (point.station - 16.6) / 1000.0; + auto const angle{std::atan(1.0 / 9.0)}; + auto const report{switch_curve::check_slip({first, arc(33.2, angle, 1.0 / 1000.0)}, {arc(21.0, 0.0, 1.0 / 190.0)}, curve, 150.0, 183.2)}; + CHECK(says(report.warnings, "9.7 pkt 12")); +}