Skip to content

Commit 53d80bc

Browse files
authored
Merge pull request #397 from physycom/changeStreetNLanes
Add `changeStreetNLanes` functions
2 parents d787e02 + c2f1f7c commit 53d80bc

File tree

9 files changed

+342
-24
lines changed

9 files changed

+342
-24
lines changed

src/dsf/bindings.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,20 @@ PYBIND11_MODULE(dsf_cpp, m) {
261261
pybind11::arg("status"),
262262
dsf::g_docstrings.at("dsf::mobility::RoadNetwork::setStreetStatusByName")
263263
.c_str())
264+
.def("changeStreetNLanesById",
265+
&dsf::mobility::RoadNetwork::changeStreetNLanesById,
266+
pybind11::arg("streetId"),
267+
pybind11::arg("nLanes"),
268+
pybind11::arg("speedFactor") = std::nullopt,
269+
dsf::g_docstrings.at("dsf::mobility::RoadNetwork::changeStreetNLanesById")
270+
.c_str())
271+
.def("changeStreetNLanesByName",
272+
&dsf::mobility::RoadNetwork::changeStreetNLanesByName,
273+
pybind11::arg("name"),
274+
pybind11::arg("nLanes"),
275+
pybind11::arg("speedFactor") = std::nullopt,
276+
dsf::g_docstrings.at("dsf::mobility::RoadNetwork::changeStreetNLanesByName")
277+
.c_str())
264278
.def("changeStreetCapacityById",
265279
&dsf::mobility::RoadNetwork::changeStreetCapacityById,
266280
pybind11::arg("streetId"),

src/dsf/dsf.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
static constexpr uint8_t DSF_VERSION_MAJOR = 4;
88
static constexpr uint8_t DSF_VERSION_MINOR = 7;
9-
static constexpr uint8_t DSF_VERSION_PATCH = 6;
9+
static constexpr uint8_t DSF_VERSION_PATCH = 7;
1010

1111
static auto const DSF_VERSION =
1212
std::format("{}.{}.{}", DSF_VERSION_MAJOR, DSF_VERSION_MINOR, DSF_VERSION_PATCH);

src/dsf/mobility/RoadNetwork.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,9 @@ namespace dsf::mobility {
936936

937937
void RoadNetwork::setStreetStatusById(Id const streetId, RoadStatus const status) {
938938
try {
939-
edge(streetId)->setStatus(status);
939+
auto const& pStreet{edge(streetId)};
940+
pStreet->setStatus(status);
941+
spdlog::info("Changed status of {} to {}", *pStreet, status);
940942
} catch (const std::out_of_range&) {
941943
throw std::out_of_range(std::format("Street with id {} not found", streetId));
942944
}
@@ -958,6 +960,36 @@ namespace dsf::mobility {
958960
nAffectedRoads.load(),
959961
streetName);
960962
}
963+
void RoadNetwork::changeStreetNLanesById(Id const streetId,
964+
int const nLanes,
965+
std::optional<double> const speedFactor) {
966+
try {
967+
edge(streetId)->changeNLanes(nLanes, speedFactor);
968+
} catch (const std::out_of_range&) {
969+
throw std::out_of_range(std::format("Street with id {} not found", streetId));
970+
}
971+
}
972+
void RoadNetwork::changeStreetNLanesByName(std::string const& streetName,
973+
int const nLanes,
974+
std::optional<double> const speedFactor) {
975+
std::atomic<std::size_t> nAffectedRoads{0};
976+
std::for_each(
977+
DSF_EXECUTION m_edges.cbegin(),
978+
m_edges.cend(),
979+
[this, &streetName, &nLanes, &speedFactor, &nAffectedRoads](auto const& pair) {
980+
auto const& pStreet = pair.second;
981+
if (pStreet->name().find(streetName) != std::string::npos) {
982+
pStreet->changeNLanes(nLanes, speedFactor);
983+
++nAffectedRoads;
984+
}
985+
});
986+
spdlog::info(
987+
"Changed number of lanes to {} for {} streets with name containing "
988+
"\"{}\"",
989+
nLanes,
990+
nAffectedRoads.load(),
991+
streetName);
992+
}
961993
void RoadNetwork::changeStreetCapacityById(Id const streetId, double const factor) {
962994
try {
963995
auto const& pStreet{edge(streetId)};

src/dsf/mobility/RoadNetwork.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,20 @@ namespace dsf::mobility {
207207
/// @param name The name to match
208208
/// @param status The status to set
209209
void setStreetStatusByName(std::string const& name, RoadStatus const status);
210+
/// @brief Change the street's number of lanes by its id
211+
/// @param streetId The id of the street
212+
/// @param nLanes The new number of lanes
213+
/// @param speedFactor Optional, The factor to multiply the max speed of the street
214+
void changeStreetNLanesById(Id const streetId,
215+
int const nLanes,
216+
std::optional<double> const speedFactor = std::nullopt);
217+
/// @brief Change the street's number of lanes of all streets with the given name
218+
/// @param name The name to match
219+
/// @param nLanes The new number of lanes
220+
/// @param speedFactor Optional, The factor to multiply the max speed of the street
221+
void changeStreetNLanesByName(std::string const& name,
222+
int const nLanes,
223+
std::optional<double> const speedFactor = std::nullopt);
210224
/// @brief Change the street's capacity by its id
211225
/// @param streetId The id of the street
212226
/// @param factor The factor to multiply the capacity by

src/dsf/mobility/Street.cpp

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@
55
#include <spdlog/spdlog.h>
66

77
namespace dsf::mobility {
8+
void Street::m_updateLaneMapping(int const nLanes) {
9+
m_laneMapping.clear();
10+
switch (nLanes) {
11+
case 1:
12+
m_laneMapping.emplace_back(Direction::ANY);
13+
break;
14+
case 2:
15+
m_laneMapping.emplace_back(Direction::RIGHTANDSTRAIGHT);
16+
m_laneMapping.emplace_back(Direction::LEFT);
17+
break;
18+
case 3:
19+
m_laneMapping.emplace_back(Direction::RIGHTANDSTRAIGHT);
20+
m_laneMapping.emplace_back(Direction::STRAIGHT);
21+
m_laneMapping.emplace_back(Direction::LEFT);
22+
break;
23+
default:
24+
m_laneMapping.emplace_back(Direction::RIGHT);
25+
for (auto i{1}; i < nLanes - 1; ++i) {
26+
m_laneMapping.emplace_back(Direction::STRAIGHT);
27+
}
28+
m_laneMapping.emplace_back(Direction::LEFT);
29+
break;
30+
}
31+
}
832
Street::Street(Id id,
933
std::pair<Id, Id> nodePair,
1034
double length,
@@ -27,27 +51,7 @@ namespace dsf::mobility {
2751
m_movingAgents{dsf::priority_queue<std::unique_ptr<Agent>,
2852
std::vector<std::unique_ptr<Agent>>,
2953
AgentComparator>()} {
30-
switch (nLanes) {
31-
case 1:
32-
m_laneMapping.emplace_back(Direction::ANY);
33-
break;
34-
case 2:
35-
m_laneMapping.emplace_back(Direction::RIGHTANDSTRAIGHT);
36-
m_laneMapping.emplace_back(Direction::LEFT);
37-
break;
38-
case 3:
39-
m_laneMapping.emplace_back(Direction::RIGHTANDSTRAIGHT);
40-
m_laneMapping.emplace_back(Direction::STRAIGHT);
41-
m_laneMapping.emplace_back(Direction::LEFT);
42-
break;
43-
default:
44-
m_laneMapping.emplace_back(Direction::RIGHT);
45-
for (auto i{1}; i < nLanes - 1; ++i) {
46-
m_laneMapping.emplace_back(Direction::STRAIGHT);
47-
}
48-
m_laneMapping.emplace_back(Direction::LEFT);
49-
break;
50-
}
54+
m_updateLaneMapping(nLanes);
5155
}
5256
auto Street::operator==(Street const& other) const -> bool {
5357
bool isEqual{true};
@@ -79,6 +83,29 @@ namespace dsf::mobility {
7983
assert(index < m_exitQueues.size());
8084
m_exitQueues[index] = std::move(queue);
8185
}
86+
void Street::changeNLanes(int const nLanes, std::optional<double> const speedFactor) {
87+
if (this->nExitingAgents() > 0) {
88+
spdlog::warn("Changing number of lanes for {} which has {} exiting agents",
89+
*this,
90+
this->nExitingAgents());
91+
}
92+
if (nLanes <= 0) {
93+
throw std::invalid_argument("Number of lanes must be positive");
94+
}
95+
if (nLanes == m_nLanes) {
96+
return;
97+
}
98+
spdlog::info(
99+
"Changing number of lanes for {} from {} to {}", *this, m_nLanes, nLanes);
100+
m_capacity = static_cast<int>(m_capacity * static_cast<double>(nLanes) / m_nLanes);
101+
m_transportCapacity = m_transportCapacity * nLanes / m_nLanes;
102+
m_nLanes = nLanes;
103+
if (speedFactor.has_value()) {
104+
m_maxSpeed = m_maxSpeed * speedFactor.value();
105+
}
106+
m_exitQueues.resize(m_nLanes);
107+
m_updateLaneMapping(m_nLanes);
108+
}
82109
void Street::enableCounter(std::string name, CounterPosition position) {
83110
if (m_counter.has_value()) {
84111
throw std::runtime_error(

src/dsf/mobility/Street.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ namespace dsf::mobility {
5555
CounterPosition m_counterPosition{CounterPosition::EXIT};
5656
double m_stationaryWeight{1.0};
5757

58+
/// @brief Update the street's lane mapping
59+
/// @param nLanes The street's number of lanes
60+
void m_updateLaneMapping(int const nLanes);
61+
5862
public:
5963
/// @brief Construct a new Street object
6064
/// @param id The street's id
@@ -96,6 +100,12 @@ namespace dsf::mobility {
96100
weight > 0. ? m_stationaryWeight = weight
97101
: throw std::invalid_argument("Stationary weight must be positive");
98102
}
103+
/// @brief Change the number of lanes of the street. Usually if there is a construction site, you may want to
104+
/// reduce the number of lanes and possibly the max speed.
105+
/// @param nLanes The new number of lanes
106+
/// @param speedFactor Optional, The factor to multiply the max speed of the street
107+
void changeNLanes(int const nLanes,
108+
std::optional<double> const speedFactor = std::nullopt);
99109
/// @brief Enable a coil (dsf::Counter sensor) on the street
100110
/// @param name The name of the counter (default is "Coil_<street_id>")
101111
/// @param position The position of the counter on the street (default is EXIT)

src/dsf/utility/queue.hpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ namespace dsf {
77
template <typename T, typename Container = std::deque<T>>
88
class queue : public std::queue<T, Container> {
99
public:
10+
using std::queue<T, Container>::queue;
1011
typedef typename Container::iterator iterator;
1112
typedef typename Container::const_iterator const_iterator;
1213

14+
queue(queue&&) noexcept = default;
15+
queue& operator=(queue&&) noexcept = default;
16+
queue(const queue&) = delete;
17+
queue& operator=(const queue&) = delete;
18+
1319
// c is a protected member of std::queue, which is the underlying container
1420
iterator begin() { return this->c.begin(); }
1521
iterator end() { return this->c.end(); }
@@ -25,10 +31,16 @@ namespace dsf {
2531
typename Compare = std::less<typename Container::value_type>>
2632
class priority_queue : public std::priority_queue<T, Container, Compare> {
2733
public:
34+
using std::priority_queue<T, Container, Compare>::priority_queue;
2835
typedef typename Container::iterator iterator;
2936
typedef typename Container::const_iterator const_iterator;
3037

31-
// c is a protected member of std::queue, which is the underlying container
38+
priority_queue(priority_queue&&) noexcept = default;
39+
priority_queue& operator=(priority_queue&&) noexcept = default;
40+
priority_queue(const priority_queue&) = delete;
41+
priority_queue& operator=(const priority_queue&) = delete;
42+
43+
// c is a protected member of std::priority_queue, which is the underlying container
3244
iterator begin() { return this->c.begin(); }
3345
iterator end() { return this->c.end(); }
3446
const_iterator begin() const { return this->c.begin(); }

test/mobility/Test_graph.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,3 +1548,139 @@ TEST_CASE("allPathsTo with closed roads") {
15481548
CHECK_FALSE(pathMap.contains(1));
15491549
}
15501550
}
1551+
1552+
TEST_CASE("Change Street Lanes") {
1553+
Road::setMeanVehicleLength(5.);
1554+
1555+
SUBCASE("changeStreetNLanesById") {
1556+
GIVEN("A network with multiple streets") {
1557+
RoadNetwork graph{};
1558+
graph.addNode(0, dsf::geometry::Point(0.0, 0.0));
1559+
graph.addNode(1, dsf::geometry::Point(1.0, 0.0));
1560+
graph.addNode(2, dsf::geometry::Point(2.0, 0.0));
1561+
1562+
Street s01(10, std::make_pair(0, 1), 100.0, 20.0, 2);
1563+
Street s12(11, std::make_pair(1, 2), 150.0, 25.0, 3);
1564+
graph.addStreets(s01, s12);
1565+
1566+
auto const* pStreet01 = graph.street(0, 1);
1567+
REQUIRE(pStreet01 != nullptr);
1568+
auto const initialCapacity = (*pStreet01)->capacity();
1569+
auto const initialMaxSpeed = (*pStreet01)->maxSpeed();
1570+
1571+
WHEN("Lanes are increased for street by id") {
1572+
graph.changeStreetNLanesById(10, 4);
1573+
1574+
THEN("The street's properties are updated correctly") {
1575+
CHECK_EQ((*pStreet01)->nLanes(), 4);
1576+
CHECK_EQ((*pStreet01)->capacity(), initialCapacity * 2);
1577+
CHECK_EQ((*pStreet01)->maxSpeed(), initialMaxSpeed);
1578+
CHECK_EQ((*pStreet01)->exitQueues().size(), 4);
1579+
}
1580+
}
1581+
1582+
WHEN("Lanes are changed with speed factor") {
1583+
graph.changeStreetNLanesById(10, 1, 0.6);
1584+
1585+
THEN("Both lanes and speed are updated") {
1586+
CHECK_EQ((*pStreet01)->nLanes(), 1);
1587+
CHECK_EQ((*pStreet01)->maxSpeed(), doctest::Approx(initialMaxSpeed * 0.6));
1588+
}
1589+
}
1590+
1591+
WHEN("Invalid street id is provided") {
1592+
THEN("Exception is thrown") {
1593+
CHECK_THROWS_AS(graph.changeStreetNLanesById(999, 2), std::out_of_range);
1594+
}
1595+
}
1596+
1597+
WHEN("Invalid number of lanes is provided") {
1598+
THEN("Exception is thrown") {
1599+
CHECK_THROWS_AS(graph.changeStreetNLanesById(10, 0), std::invalid_argument);
1600+
CHECK_THROWS_AS(graph.changeStreetNLanesById(10, -1), std::invalid_argument);
1601+
}
1602+
}
1603+
}
1604+
}
1605+
1606+
SUBCASE("changeStreetNLanesByName") {
1607+
GIVEN("A network with named streets") {
1608+
RoadNetwork graph{};
1609+
graph.addNode(0, dsf::geometry::Point(0.0, 0.0));
1610+
graph.addNode(1, dsf::geometry::Point(1.0, 0.0));
1611+
graph.addNode(2, dsf::geometry::Point(2.0, 0.0));
1612+
graph.addNode(3, dsf::geometry::Point(1.0, 1.0));
1613+
1614+
Street s01(10, std::make_pair(0, 1), 100.0, 20.0, 2, "Main Street");
1615+
Street s12(11, std::make_pair(1, 2), 150.0, 25.0, 3, "Main Street");
1616+
Street s03(12, std::make_pair(0, 3), 120.0, 15.0, 1, "Side Road");
1617+
Street s32(13, std::make_pair(3, 2), 130.0, 18.0, 2, "Side Road");
1618+
graph.addStreets(s01, s12, s03, s32);
1619+
1620+
auto const* pMainStreet1 = graph.street(0, 1);
1621+
auto const* pMainStreet2 = graph.street(1, 2);
1622+
auto const* pSideRoad1 = graph.street(0, 3);
1623+
auto const* pSideRoad2 = graph.street(3, 2);
1624+
1625+
REQUIRE(pMainStreet1 != nullptr);
1626+
REQUIRE(pMainStreet2 != nullptr);
1627+
REQUIRE(pSideRoad1 != nullptr);
1628+
REQUIRE(pSideRoad2 != nullptr);
1629+
1630+
auto const initialMainSpeed1 = (*pMainStreet1)->maxSpeed();
1631+
auto const initialMainSpeed2 = (*pMainStreet2)->maxSpeed();
1632+
auto const initialSideSpeed1 = (*pSideRoad1)->maxSpeed();
1633+
auto const initialSideSpeed2 = (*pSideRoad2)->maxSpeed();
1634+
1635+
WHEN("All streets with 'Main' in name are changed") {
1636+
graph.changeStreetNLanesByName("Main", 1);
1637+
1638+
THEN("Only Main Streets are affected") {
1639+
CHECK_EQ((*pMainStreet1)->nLanes(), 1);
1640+
CHECK_EQ((*pMainStreet2)->nLanes(), 1);
1641+
CHECK_EQ((*pSideRoad1)->nLanes(), 1); // unchanged
1642+
CHECK_EQ((*pSideRoad2)->nLanes(), 2); // unchanged
1643+
CHECK_EQ((*pMainStreet1)->maxSpeed(), initialMainSpeed1);
1644+
CHECK_EQ((*pMainStreet2)->maxSpeed(), initialMainSpeed2);
1645+
}
1646+
}
1647+
1648+
WHEN("Streets are changed by name with speed factor") {
1649+
graph.changeStreetNLanesByName("Side", 3, 0.8);
1650+
1651+
THEN("Only Side Roads are affected with both changes") {
1652+
CHECK_EQ((*pSideRoad1)->nLanes(), 3);
1653+
CHECK_EQ((*pSideRoad2)->nLanes(), 3);
1654+
CHECK_EQ((*pMainStreet1)->nLanes(), 2); // unchanged
1655+
CHECK_EQ((*pMainStreet2)->nLanes(), 3); // unchanged
1656+
CHECK_EQ((*pSideRoad1)->maxSpeed(), doctest::Approx(initialSideSpeed1 * 0.8));
1657+
CHECK_EQ((*pSideRoad2)->maxSpeed(), doctest::Approx(initialSideSpeed2 * 0.8));
1658+
CHECK_EQ((*pMainStreet1)->maxSpeed(), initialMainSpeed1); // unchanged
1659+
CHECK_EQ((*pMainStreet2)->maxSpeed(), initialMainSpeed2); // unchanged
1660+
}
1661+
}
1662+
1663+
WHEN("No streets match the name pattern") {
1664+
graph.changeStreetNLanesByName("NonExistent", 5);
1665+
1666+
THEN("No streets are changed") {
1667+
CHECK_EQ((*pMainStreet1)->nLanes(), 2);
1668+
CHECK_EQ((*pMainStreet2)->nLanes(), 3);
1669+
CHECK_EQ((*pSideRoad1)->nLanes(), 1);
1670+
CHECK_EQ((*pSideRoad2)->nLanes(), 2);
1671+
}
1672+
}
1673+
1674+
WHEN("Partial name match is used") {
1675+
graph.changeStreetNLanesByName("Street", 4);
1676+
1677+
THEN("All streets with 'Street' in name are changed") {
1678+
CHECK_EQ((*pMainStreet1)->nLanes(), 4);
1679+
CHECK_EQ((*pMainStreet2)->nLanes(), 4);
1680+
CHECK_EQ((*pSideRoad1)->nLanes(), 1); // unchanged
1681+
CHECK_EQ((*pSideRoad2)->nLanes(), 2); // unchanged
1682+
}
1683+
}
1684+
}
1685+
}
1686+
}

0 commit comments

Comments
 (0)