diff --git a/examples/slow_charge_tl.cpp b/examples/slow_charge_tl.cpp index ab06b37c..e508eef8 100644 --- a/examples/slow_charge_tl.cpp +++ b/examples/slow_charge_tl.cpp @@ -150,7 +150,7 @@ int main(int argc, char** argv) { } graph.adjustNodeCapacities(); std::cout << "Setting traffic light parameters..." << '\n'; - graph.initTrafficLights(); + graph.autoInitTrafficLights(); std::cout << "Done." << std::endl; std::cout << "Creating dynamics...\n"; diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index 3ec5a4f5..e63666be 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -165,13 +165,18 @@ PYBIND11_MODULE(dsf_cpp, m) { "adjustNodeCapacities", &dsf::mobility::RoadNetwork::adjustNodeCapacities, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::adjustNodeCapacities").c_str()) - .def("initTrafficLights", - &dsf::mobility::RoadNetwork::initTrafficLights, - pybind11::arg("minGreenTime") = 30, - dsf::g_docstrings.at("dsf::mobility::RoadNetwork::initTrafficLights").c_str()) + .def("autoInitTrafficLights", + &dsf::mobility::RoadNetwork::autoInitTrafficLights, + pybind11::arg("mainRoadPercentage") = 0.6, + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoInitTrafficLights") + .c_str()) .def("autoMapStreetLanes", &dsf::mobility::RoadNetwork::autoMapStreetLanes, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoMapStreetLanes").c_str()) + .def("autoAssignRoadPriorities", + &dsf::mobility::RoadNetwork::autoAssignRoadPriorities, + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoAssignRoadPriorities") + .c_str()) .def("setStreetStationaryWeights", &dsf::mobility::RoadNetwork::setStreetStationaryWeights, pybind11::arg("weights"), diff --git a/src/dsf/mobility/Road.cpp b/src/dsf/mobility/Road.cpp index ce219cd3..5a850157 100644 --- a/src/dsf/mobility/Road.cpp +++ b/src/dsf/mobility/Road.cpp @@ -22,8 +22,7 @@ namespace dsf::mobility { m_length{length}, m_maxSpeed{maxSpeed}, m_nLanes{nLanes}, - m_name{std::move(name)}, - m_priority{nLanes * 100} { + m_name{std::move(name)} { if (!(length > 0.)) { throw std::invalid_argument( std::format("The length of a road ({}) must be greater than 0.", length)); @@ -84,10 +83,6 @@ namespace dsf::mobility { } m_transportCapacity = transportCapacity; } - void Road::setPriority(int priority) { - assert(priority >= 0); - m_priority = priority; - } Direction Road::turnDirection(double const& previousStreetAngle) const { auto const deltaAngle{this->deltaAngle(previousStreetAngle)}; if (std::abs(deltaAngle) >= std::numbers::pi) { diff --git a/src/dsf/mobility/Road.hpp b/src/dsf/mobility/Road.hpp index f2053f45..4c63e98f 100644 --- a/src/dsf/mobility/Road.hpp +++ b/src/dsf/mobility/Road.hpp @@ -26,7 +26,7 @@ namespace dsf::mobility { int m_capacity; double m_transportCapacity; std::string m_name; - int m_priority; + bool m_hasPriority = false; std::set m_forbiddenTurns; // Stores the forbidden turns (road ids) std::optional m_roadType{std::nullopt}; @@ -70,9 +70,8 @@ namespace dsf::mobility { /// @param transportCapacity The transport capacity /// @throws std::invalid_argument If the transport capacity is less or equal to 0 void setTransportCapacity(double transportCapacity); - /// @brief Set the road's priority - /// @param priority The road's priority - void setPriority(int priority); + /// @brief Set the road's priority to true + inline void setPriority() { m_hasPriority = true; } /// @brief Add a road id to the forbidden turns /// @param roadId The road id to add void addForbiddenTurn(Id roadId); @@ -102,8 +101,8 @@ namespace dsf::mobility { /// @return std::string The name inline auto const& name() const noexcept { return m_name; } /// @brief Get the priority - /// @return int The priority - inline auto priority() const noexcept { return m_priority; } + /// @return bool Whether the road has priority + inline auto hasPriority() const noexcept { return m_hasPriority; } /// @brief Get the road's forbidden turns /// @return std::set The road's forbidden turns /// @details The forbidden turns are the road ids that are not allowed to be used by the agents diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index 330248ee..ef80209b 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -1,12 +1,16 @@ +#include "RoadNetwork.hpp" #include "../geometry/Point.hpp" #include "../geometry/PolyLine.hpp" -#include "RoadNetwork.hpp" #include #include #include #include +#include + +// Default traffic light cycle duration in seconds +constexpr dsf::Delay TRAFFICLIGHT_DEFAULT_CYCLE = 90; namespace dsf::mobility { void RoadNetwork::m_updateMaxAgentCapacity() { @@ -160,7 +164,7 @@ namespace dsf::mobility { return std::tolower(c); }); if (strType.find("traffic_signals") != std::string::npos) { - makeTrafficLight(nodeId, 120); + makeTrafficLight(nodeId, TRAFFICLIGHT_DEFAULT_CYCLE); } else if (strType.find("roundabout") != std::string::npos) { makeRoundabout(nodeId); } @@ -354,212 +358,195 @@ namespace dsf::mobility { }); } - void RoadNetwork::initTrafficLights(Delay const minGreenTime) { - for (auto& [_, pNode] : m_nodes) { - if (!pNode->isTrafficLight()) { - continue; - } - auto& tl = static_cast(*pNode); - if (!tl.streetPriorities().empty() || !tl.cycles().empty()) { - continue; - } - auto const& inNeighbours = pNode->ingoingEdges(); - std::map> capacities; - std::unordered_map streetAngles; - std::unordered_map maxSpeeds; - std::unordered_map nLanes; - std::unordered_map streetNames; - double higherSpeed{0.}, lowerSpeed{std::numeric_limits::max()}; - int higherNLanes{0}, lowerNLanes{std::numeric_limits::max()}; - if (inNeighbours.size() < 3) { - spdlog::warn("Not enough in neighbours {} for Traffic Light {}", - inNeighbours.size(), - pNode->id()); - // Replace with a normal intersection - auto const& geometry{pNode->geometry()}; - if (geometry.has_value()) { - pNode = std::make_unique(pNode->id(), *geometry); - } else { - pNode = std::make_unique(pNode->id()); - } - continue; - } - for (auto const& edgeId : inNeighbours) { - auto const& pStreet{edge(edgeId)}; - - double const speed{pStreet->maxSpeed()}; - int const nLan{pStreet->nLanes()}; - auto const cap{pStreet->capacity()}; - capacities.emplace(pStreet->id(), cap); - auto angle{pStreet->angle()}; - if (angle < 0.) { - angle += 2 * std::numbers::pi; - } - streetAngles.emplace(pStreet->id(), angle); - - maxSpeeds.emplace(pStreet->id(), speed); - nLanes.emplace(pStreet->id(), nLan); - streetNames.emplace(pStreet->id(), pStreet->name()); - - higherSpeed = std::max(higherSpeed, speed); - lowerSpeed = std::min(lowerSpeed, speed); - - higherNLanes = std::max(higherNLanes, nLan); - lowerNLanes = std::min(lowerNLanes, nLan); - } - { - std::vector> sortedAngles; - std::copy( - streetAngles.begin(), streetAngles.end(), std::back_inserter(sortedAngles)); - std::sort(sortedAngles.begin(), - sortedAngles.end(), - [](auto const& a, auto const& b) { return a.second < b.second; }); - streetAngles.clear(); - for (auto const& [streetId, angle] : sortedAngles) { - streetAngles.emplace(streetId, angle); - } - } - if (tl.streetPriorities().empty()) { - /************************************************************* - * 1. Check for street names with multiple occurrences - * ***********************************************************/ - std::unordered_map counts; - for (auto const& [streetId, name] : streetNames) { - if (name.empty()) { - // Ignore empty names - continue; + void RoadNetwork::autoInitTrafficLights(double const mainRoadPercentage) { + tbb::parallel_for_each( + m_nodes.begin(), m_nodes.end(), [this, mainRoadPercentage](auto& pair) { + auto& pNode = pair.second; + if (!pNode->isTrafficLight()) { + return; } - if (!counts.contains(name)) { - counts[name] = 1; - } else { - ++counts.at(name); + auto& tl = static_cast(*pNode); + if (!tl.streetPriorities().empty() || !tl.cycles().empty()) { + return; } - } - // Check if spdlog is in debug mode - if (spdlog::get_level() <= spdlog::level::debug) { - for (auto const& [name, count] : counts) { - spdlog::debug("Street name {} has {} occurrences", name, count); + auto const& inNeighbours = pNode->ingoingEdges(); + std::map> capacities; + std::unordered_map streetAngles; + std::unordered_map maxSpeeds; + std::unordered_map nLanes; + std::unordered_map streetNames; + double higherSpeed{0.}, lowerSpeed{std::numeric_limits::max()}; + int higherNLanes{0}, lowerNLanes{std::numeric_limits::max()}; + if (inNeighbours.size() < 3) { + spdlog::warn("Not enough in neighbours {} for Traffic Light {}", + inNeighbours.size(), + pNode->id()); + // Replace with a normal intersection + auto const& geometry{pNode->geometry()}; + if (geometry.has_value()) { + pNode = std::make_unique(pNode->id(), *geometry); + } else { + pNode = std::make_unique(pNode->id()); + } + return; } - } - for (auto const& [streetId, name] : streetNames) { - if (!name.empty() && counts.at(name) > 1) { - tl.addStreetPriority(streetId); + for (auto const& edgeId : inNeighbours) { + auto const& pStreet{edge(edgeId)}; + + double const speed{pStreet->maxSpeed()}; + int const nLan{pStreet->nLanes()}; + auto const cap{pStreet->capacity()}; + capacities.emplace(pStreet->id(), cap); + auto angle{pStreet->angle()}; + if (angle < 0.) { + angle += 2 * std::numbers::pi; + } + streetAngles.emplace(pStreet->id(), angle); + + maxSpeeds.emplace(pStreet->id(), speed); + nLanes.emplace(pStreet->id(), nLan); + streetNames.emplace(pStreet->id(), pStreet->name()); + + higherSpeed = std::max(higherSpeed, speed); + lowerSpeed = std::min(lowerSpeed, speed); + + higherNLanes = std::max(higherNLanes, nLan); + lowerNLanes = std::min(lowerNLanes, nLan); + + if (pStreet->hasPriority()) { + tl.addStreetPriority(pStreet->id()); + } } - } - } - if (tl.streetPriorities().empty() && higherSpeed != lowerSpeed) { - /************************************************************* + + if (tl.streetPriorities().empty()) { + /************************************************************* + * 1. Check for street names with multiple occurrences + * ***********************************************************/ + std::unordered_map counts; + for (auto const& [streetId, name] : streetNames) { + if (name.empty()) { + // Ignore empty names + return; + } + if (!counts.contains(name)) { + counts[name] = 1; + } else { + ++counts.at(name); + } + } + // Check if spdlog is in debug mode + if (spdlog::get_level() <= spdlog::level::debug) { + for (auto const& [name, count] : counts) { + spdlog::debug("Street name {} has {} occurrences", name, count); + } + } + for (auto const& [streetId, name] : streetNames) { + if (!name.empty() && counts.at(name) > 1) { + tl.addStreetPriority(streetId); + } + } + } + if (tl.streetPriorities().empty() && higherSpeed != lowerSpeed) { + /************************************************************* * 2. Check for street names with same max speed * ***********************************************************/ - for (auto const& [sid, speed] : maxSpeeds) { - if (speed == higherSpeed) { - tl.addStreetPriority(sid); + for (auto const& [sid, speed] : maxSpeeds) { + if (speed == higherSpeed) { + tl.addStreetPriority(sid); + } + } } - } - } - if (tl.streetPriorities().empty() && higherNLanes != lowerNLanes) { - /************************************************************* + if (tl.streetPriorities().empty() && higherNLanes != lowerNLanes) { + /************************************************************* * 2. Check for street names with same number of lanes * ***********************************************************/ - for (auto const& [sid, nLan] : nLanes) { - if (nLan == higherNLanes) { - tl.addStreetPriority(sid); + for (auto const& [sid, nLan] : nLanes) { + if (nLan == higherNLanes) { + tl.addStreetPriority(sid); + } + } } - } - } - if (tl.streetPriorities().empty()) { - /************************************************************* + if (tl.streetPriorities().empty()) { + /************************************************************* * 3. Check for streets with opposite angles * ***********************************************************/ - auto const& streetId = streetAngles.begin()->first; - auto const& angle = streetAngles.begin()->second; - for (auto const& [streetId2, angle2] : streetAngles) { - if (std::abs(angle - angle2) > 0.75 * std::numbers::pi) { - tl.addStreetPriority(streetId); - tl.addStreetPriority(streetId2); - break; + std::vector> sortedAngles; + std::copy(streetAngles.begin(), + streetAngles.end(), + std::back_inserter(sortedAngles)); + std::sort(sortedAngles.begin(), + sortedAngles.end(), + [](auto const& a, auto const& b) { return a.second < b.second; }); + streetAngles.clear(); + for (auto const& [streetId, angle] : sortedAngles) { + streetAngles.emplace(streetId, angle); + } + + auto const& streetId = streetAngles.begin()->first; + auto const& angle = streetAngles.begin()->second; + for (auto const& [streetId2, angle2] : streetAngles) { + if (std::abs(angle - angle2) > 0.75 * std::numbers::pi) { + tl.addStreetPriority(streetId); + tl.addStreetPriority(streetId2); + break; + } + } } - } - } - if (tl.streetPriorities().empty()) { - spdlog::warn("Failed to auto-init Traffic Light {} - going random", pNode->id()); - // Assign first and third keys of capacity map - auto it = capacities.begin(); - auto const& firstKey = it->first; - ++it; - ++it; - auto const& thirdKey = it->first; - tl.addStreetPriority(firstKey); - tl.addStreetPriority(thirdKey); - } - - // Assign cycles - std::pair greenTimes; - { - auto capPriority{0.}, capNoPriority{0.}; - std::unordered_map normCapacities; - auto sum{0.}; - for (auto const& [streetId, cap] : capacities) { - sum += cap; - } - for (auto const& [streetId, cap] : capacities) { - normCapacities.emplace(streetId, cap / sum); - } - for (auto const& [streetId, normCap] : normCapacities) { - if (tl.streetPriorities().contains(streetId)) { - capPriority += normCap; - } else { - capNoPriority += normCap; + if (tl.streetPriorities().empty() || tl.streetPriorities().size() != 2) { + spdlog::warn("Failed to auto-init Traffic Light {} - going random", + pNode->id()); + // Assign first and third keys of capacity map + auto it = capacities.begin(); + auto const& firstKey = it->first; + ++it; + ++it; + auto const& thirdKey = it->first; + tl.addStreetPriority(firstKey); + tl.addStreetPriority(thirdKey); } - } - spdlog::debug("Capacities for Traffic Light {}: priority {} no priority {}", - pNode->id(), - capPriority, - capNoPriority); - greenTimes = std::make_pair(static_cast(capPriority * tl.cycleTime()), - static_cast(capNoPriority * tl.cycleTime())); - } - // if one of green times is less than 20, set it to 20 and refactor the other to have the sum to 120 - if (greenTimes.first < minGreenTime) { - greenTimes.first = minGreenTime; - greenTimes.second = tl.cycleTime() - minGreenTime; - } - if (greenTimes.second < minGreenTime) { - greenTimes.second = minGreenTime; - greenTimes.first = tl.cycleTime() - minGreenTime; - } - std::for_each(inNeighbours.begin(), inNeighbours.end(), [&](auto const& edgeId) { - auto const streetId{this->edge(edgeId)->id()}; - auto const nLane{nLanes.at(streetId)}; - Delay greenTime{greenTimes.first}; - Delay phase{0}; - if (!tl.streetPriorities().contains(streetId)) { - phase = greenTime; - greenTime = greenTimes.second; - } - spdlog::debug("Setting cycle for street {} with green time {} and phase {}", - streetId, - greenTime, - phase); - switch (nLane) { - case 3: - tl.setCycle(streetId, - dsf::Direction::RIGHTANDSTRAIGHT, - TrafficLightCycle{static_cast(greenTime * 2. / 3), phase}); - tl.setCycle( - streetId, - dsf::Direction::LEFT, - TrafficLightCycle{ - static_cast(greenTime / 3.), - static_cast(phase + static_cast(greenTime * 2. / 3))}); - break; - default: - tl.setCycle( - streetId, dsf::Direction::ANY, TrafficLightCycle{greenTime, phase}); - break; - } - }); - } + + // Assign cycles + std::pair greenTimes; + { + auto const mainGreenTime{ + static_cast(mainRoadPercentage * tl.cycleTime())}; + auto const secondaryGreenTime{ + static_cast(tl.cycleTime() - mainGreenTime)}; + greenTimes = std::make_pair(mainGreenTime, secondaryGreenTime); + } + std::for_each(inNeighbours.begin(), inNeighbours.end(), [&](auto const& edgeId) { + auto const streetId{this->edge(edgeId)->id()}; + auto const nLane{nLanes.at(streetId)}; + Delay greenTime{greenTimes.first}; + Delay phase{0}; + if (!tl.streetPriorities().contains(streetId)) { + phase = greenTime; + greenTime = greenTimes.second; + } + spdlog::debug("Setting cycle for street {} with green time {} and phase {}", + streetId, + greenTime, + phase); + switch (nLane) { + case 3: + tl.setCycle( + streetId, + dsf::Direction::RIGHTANDSTRAIGHT, + TrafficLightCycle{static_cast(greenTime * 2. / 3), phase}); + tl.setCycle(streetId, + dsf::Direction::LEFT, + TrafficLightCycle{ + static_cast(greenTime / 3.), + static_cast( + phase + static_cast(greenTime * 2. / 3))}); + break; + default: + tl.setCycle( + streetId, dsf::Direction::ANY, TrafficLightCycle{greenTime, phase}); + break; + } + }); + }); } void RoadNetwork::autoMapStreetLanes() { auto const& nodes = this->nodes(); @@ -567,23 +554,25 @@ namespace dsf::mobility { auto const& pNode{pair.second}; auto const& inNeighbours{pNode->ingoingEdges()}; auto const& outNeighbours{pNode->outgoingEdges()}; - int maxPriority{0}; + double maxEstimatedFlow{0.0}; std::for_each(inNeighbours.cbegin(), inNeighbours.cend(), - [this, &maxPriority](auto const& edgeId) { + [this, &maxEstimatedFlow](auto const& edgeId) { auto const& pStreet{this->edge(edgeId)}; - maxPriority = std::max(maxPriority, pStreet->priority()); + auto const estFlow{pStreet->maxSpeed() * pStreet->nLanes()}; + maxEstimatedFlow = std::max(maxEstimatedFlow, estFlow); }); std::for_each(outNeighbours.cbegin(), outNeighbours.cend(), - [this, &maxPriority](auto const& edgeId) { + [this, &maxEstimatedFlow](auto const& edgeId) { auto const& pStreet{this->edge(edgeId)}; - maxPriority = std::max(maxPriority, pStreet->priority()); + auto const estFlow{pStreet->maxSpeed() * pStreet->nLanes()}; + maxEstimatedFlow = std::max(maxEstimatedFlow, estFlow); }); std::for_each( inNeighbours.cbegin(), inNeighbours.cend(), - [this, &pNode, &outNeighbours, &maxPriority](auto const& edgeId) { + [this, &pNode, &outNeighbours, &maxEstimatedFlow](auto const& edgeId) { auto const& pInStreet{this->edge(edgeId)}; auto const nLanes{pInStreet->nLanes()}; if (nLanes == 1) { @@ -593,7 +582,7 @@ namespace dsf::mobility { std::for_each( outNeighbours.cbegin(), outNeighbours.cend(), - [this, &pInStreet, &allowedTurns, &maxPriority](auto const& edgeId) { + [this, &pInStreet, &allowedTurns, &maxEstimatedFlow](auto const& edgeId) { auto const& pOutStreet{this->edge(edgeId)}; if (pOutStreet->target() == pInStreet->source() || pInStreet->forbiddenTurns().contains(pOutStreet->id())) { @@ -606,8 +595,11 @@ namespace dsf::mobility { return; } // Actually going straight means remain on the same road, thus... - if (((pInStreet->priority() == maxPriority) == - (outOppositeStreet->get()->priority() == maxPriority)) && + auto const inEstFlow{pInStreet->maxSpeed() * pInStreet->nLanes()}; + auto const outEstFlow{outOppositeStreet->get()->maxSpeed() * + outOppositeStreet->get()->nLanes()}; + if (((inEstFlow == maxEstimatedFlow) == + (outEstFlow == maxEstimatedFlow)) && !allowedTurns.contains(Direction::STRAIGHT)) { spdlog::debug("Street {} prioritized STRAIGHT", pInStreet->id()); if (allowedTurns.contains(Direction::STRAIGHT) && @@ -775,6 +767,59 @@ namespace dsf::mobility { }); } + void RoadNetwork::autoAssignRoadPriorities() { + spdlog::debug("Auto-assigning road priorities..."); + tbb::parallel_for_each(m_nodes.cbegin(), m_nodes.cend(), [this](auto const& pair) { + auto const& pNode{pair.second}; + auto const& inNeighbours{pNode->ingoingEdges()}; + // NOTE: std::multimap iterates keys in ascending order of RoadType. + // RoadType is defined so that more important roads (e.g., HIGHWAY = 0, + // PRIMARY = 1, SECONDARY = 2, ...) have smaller enum values. The logic + // below relies on this ordering to consider higher-priority road types + // first when selecting streets to mark as priority roads. + std::multimap types; + for (auto const& edgeId : inNeighbours) { + auto const& pStreet{this->edge(edgeId)}; + auto const roadType = pStreet->roadType(); + if (roadType.has_value()) { + types.emplace(roadType.value(), pStreet->id()); + } + } + if (types.size() < 2) { + return; + } + std::vector priorityRoads; + // Find the first road type that has at least 2 streets + for (auto it = types.begin(); it != types.end();) { + auto const& currentType = it->first; + auto const count = types.count(currentType); + + if (count == 2) { + auto range = types.equal_range(currentType); + for (auto rangeIt = range.first; rangeIt != range.second; ++rangeIt) { + priorityRoads.push_back(rangeIt->second); + } + break; + } + + // Move to the next different type + it = types.upper_bound(currentType); + } + + if (priorityRoads.size() < 2) { + spdlog::warn("Node {}: unable to auto-assign road priorities", pNode->id()); + return; + } + + for (auto const& streetId : priorityRoads) { + auto const& pStreet{this->edge(streetId)}; + pStreet->setPriority(); + spdlog::debug("Setting priority to street {}", pStreet->id()); + } + }); + spdlog::debug("Done auto-assigning road priorities."); + } + void RoadNetwork::adjustNodeCapacities() { double value; for (auto const& [_, pNode] : nodes()) { diff --git a/src/dsf/mobility/RoadNetwork.hpp b/src/dsf/mobility/RoadNetwork.hpp index d185e4b3..e1eebaaa 100644 --- a/src/dsf/mobility/RoadNetwork.hpp +++ b/src/dsf/mobility/RoadNetwork.hpp @@ -90,14 +90,16 @@ namespace dsf::mobility { /// @details The nodes' capacity is adjusted using the graph's streets transport capacity, which may vary basing on the number of lanes. The node capacity will be set to the sum of the incoming streets' transport capacity. void adjustNodeCapacities(); /// @brief Initialize the traffic lights with random parameters - /// @param minGreenTime The minimum green time for the traffic lights cycles (default is 30) + /// @param mainRoadPercentage The percentage of main roads for the traffic lights cycles (default is 0.6) /// @details Traffic Lights with no parameters set are initialized with random parameters. /// Street priorities are assigned considering the number of lanes and the speed limit. /// Traffic Lights with an input degree lower than 3 are converted to standard intersections. - void initTrafficLights(Delay const minGreenTime = 30); + void autoInitTrafficLights(double const mainRoadPercentage = 0.6); /// @brief Automatically re-maps street lanes basing on network's topology /// @details For example, if one street has the right turn forbidden, then the right lane becomes a straight one void autoMapStreetLanes(); + /// @brief Automatically assigns road priorities at intersections, basing on road types + void autoAssignRoadPriorities(); /// @brief Import the graph's streets from a file /// @param fileName The name of the file to import the streets from. diff --git a/src/dsf/mobility/Street.cpp b/src/dsf/mobility/Street.cpp index 4bcd7df6..6ba5eb2e 100644 --- a/src/dsf/mobility/Street.cpp +++ b/src/dsf/mobility/Street.cpp @@ -57,7 +57,7 @@ namespace dsf::mobility { isEqual &= (this->m_maxSpeed == other.m_maxSpeed); isEqual &= (this->m_nLanes == other.m_nLanes); isEqual &= (this->m_name == other.m_name); - isEqual &= (this->m_priority == other.m_priority); + isEqual &= (this->m_hasPriority == other.m_hasPriority); return isEqual; } diff --git a/test/mobility/Test_graph.cpp b/test/mobility/Test_graph.cpp index 091a07ce..f4441216 100644 --- a/test/mobility/Test_graph.cpp +++ b/test/mobility/Test_graph.cpp @@ -242,7 +242,9 @@ TEST_CASE("RoadNetwork") { SUBCASE("make trafficlight") { GIVEN("A graph with a traffic light with no parameters") { Street s1{1, std::make_pair(0, 1), 30., 15., 3}; + s1.setPriority(); Street s2{11, std::make_pair(2, 1), 30., 15., 3}; + s2.setPriority(); Street s3{16, std::make_pair(3, 1), 30., 15., 1}; Street s4{21, std::make_pair(4, 1), 30., 15., 2}; RoadNetwork graph2; @@ -253,24 +255,24 @@ TEST_CASE("RoadNetwork") { pStreet->setCapacity(2 * pStreet->nLanes()); } WHEN("We auto-init Traffic Lights") { - graph2.initTrafficLights(); + graph2.autoInitTrafficLights(); THEN("Parameters are correctly set") { auto& tl{graph2.node(1)}; CHECK_EQ(tl.cycleTime(), 120); auto const& cycles{tl.cycles()}; CHECK_EQ(cycles.size(), 4); - CHECK_EQ(cycles.at(1).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 53); - CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).greenTime(), 26); + CHECK_EQ(cycles.at(1).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 48); + CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).greenTime(), 24); CHECK_EQ(cycles.at(1).at(dsf::Direction::RIGHTANDSTRAIGHT).phase(), 0); - CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).phase(), 53); - CHECK_EQ(cycles.at(11).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 53); - CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).greenTime(), 26); + CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).phase(), 48); + CHECK_EQ(cycles.at(11).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 48); + CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).greenTime(), 24); CHECK_EQ(cycles.at(11).at(dsf::Direction::RIGHTANDSTRAIGHT).phase(), 0); - CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).phase(), 53); - CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).greenTime(), 40); - CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).phase(), 80); - CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).greenTime(), 40); - CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).phase(), 80); + CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).phase(), 48); + CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).greenTime(), 48); + CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).phase(), 72); + CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).greenTime(), 48); + CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).phase(), 72); } } } @@ -380,6 +382,35 @@ TEST_CASE("RoadNetwork") { } } } + + SUBCASE("autoAssignRoadPriorities") { + GIVEN("A graph with a node having multiple incoming edges of different types") { + RoadNetwork graph{}; + // Node 1 is the intersection + // Edge 1: 0 -> 1 (HIGHWAY) + Street s1(1, std::make_pair(0, 1), 100., 30., 1); + s1.setRoadType(RoadType::HIGHWAY); + + // Edge 2: 2 -> 1 (HIGHWAY) + Street s2(2, std::make_pair(2, 1), 100., 30., 1); + s2.setRoadType(RoadType::HIGHWAY); + + // Edge 3: 3 -> 1 (SECONDARY) + Street s3(3, std::make_pair(3, 1), 100., 30., 1); + s3.setRoadType(RoadType::SECONDARY); + + graph.addStreets(s1, s2, s3); + + WHEN("We auto assign road priorities") { + graph.autoAssignRoadPriorities(); + THEN("The priorities are assigned to the most important roads") { + CHECK(graph.edge(1)->hasPriority()); + CHECK(graph.edge(2)->hasPriority()); + CHECK_FALSE(graph.edge(3)->hasPriority()); + } + } + } + } } TEST_CASE("Dijkstra") { diff --git a/test/mobility/Test_street.cpp b/test/mobility/Test_street.cpp index 19903753..a59b1e49 100644 --- a/test/mobility/Test_street.cpp +++ b/test/mobility/Test_street.cpp @@ -245,7 +245,9 @@ TEST_CASE("Road") { CHECK_EQ(road.capacity(), static_cast(std::ceil((100.0 * 2) / Road::meanVehicleLength()))); CHECK_EQ(road.transportCapacity(), 1.0); - CHECK_EQ(road.priority(), 200); // 2 * 100 + CHECK_FALSE(road.hasPriority()); + road.setPriority(); + CHECK(road.hasPriority()); } } @@ -385,8 +387,8 @@ TEST_CASE("Road") { SUBCASE("setPriority") { WHEN("Priority is set") { - road.setPriority(150); - THEN("Priority is updated") { CHECK_EQ(road.priority(), 150); } + road.setPriority(); + THEN("Priority is updated") { CHECK(road.hasPriority()); } } } } @@ -432,7 +434,7 @@ TEST_CASE("Road") { static_cast(std::ceil((100.0 * 2) / Road::meanVehicleLength()))); CHECK_EQ(road.transportCapacity(), 1.0); CHECK_EQ(road.name(), "Test Road"); - CHECK_EQ(road.priority(), 200); // 2 * 100 + CHECK_FALSE(road.hasPriority()); CHECK(road.forbiddenTurns().empty()); }