毕业设计思路篇(五)之交通流量模拟
前面几篇都是在搭台,这篇才是车真正在路上跑起来。
思路篇(四) 把车放进各条路的 m_queVehicle 之后,仿真进入 runSimulation(Graph &G)。外层按道路遍历,内层按该路上的车辆队列逐个处理;只有 it.time < SYSTEM_TIME 的车才参与本步计算,相当于用离散时间片驱动。车速会扣道路拥堵度,车开到路尽头要进路口,问 思路篇(二) 里初始化好的 TrafficLight 能不能过——这套逻辑是整份毕业设计里最绕、也最容易出 bug 的一段。
runSimulation(Graph &G)
1. 遍历每条道路
每一轮仿真扫一遍 G.m_Road_v。对单条路,先把 m_queVehicle 拷到临时队列 src,处理完再写回 obj,避免边遍历边 push_back 搞乱迭代器。路内没有车时循环自然跳过。
2. 遍历该道路的车辆
从 src 队首弹出一辆车,根据时间戳决定是否更新状态;处理完的车要么回到本路 obj,要么换路到别条路的队列,要么在路口等灯后塞回 obj。
a. 计算特定时间间隔后的位置
本步先算速度 fSpec:(100 - road.get_Congestion() - 20) / 3.6,再算十秒(代码里时间步长与 * 10 配套)后的位移 dist = it.dDistance + it.fSpec * 10,同时 it.time++。拥堵越高速度越低,是我当时简化的宏观模型,没做跟驰微观仿真。
b. 若应行驶至其他道路
dist >= road.m_dLength 表示本时间片内车已经开到路尽头,要尝试进入下一段路。先拷贝 it.queRoute 并 pop() 掉已走完的当前路 id;若队列空了说明到终点,演示代码里直接 exit(0);否则 next = route.front() 是下一条道路 id。
此时车逻辑上在路口,对应路口下标存在 it.m_nSiteRoadID(与道路终点路口相关)。先对该路口灯 clock(SYSTEM_TIME) 推进相位,再 getStatus(it.m_nSiteRoadID, next)——注意这里 from 是当前路 id、to 是下一条路 id,和 思路篇(二) 的 roadID 约定一致。
若能通行,则填至目标道路
若不能,则继续停留在路口缓冲区
绿灯:queRoute 更新为 pop 后的队列,距离清零,m_nSiteRoadID 改成 next,车 push_back 到 G.m_Road_v[next].m_queVehicle。红灯:把 dDistance 设为当前路长度(表示堵在路末端等),车放回本路 obj,下一时间片再试。
c. 若仍停留在原道路
dist 没到路长,说明还在本段路上跑,更新 it.dDistance = dist 后放回 obj。时间戳尚未到的车原样 push_back 到 obj,等全局时间追上来再动。
for (auto &road:G.m_Road_v) {
auto src = road.m_queVehicle;
decltype(road.m_queVehicle) obj;
//路内车的遍历
while (!src.empty()) {
//弹出一辆车
auto it = src.front();
src.pop_front();
// 当车的时间戳小于实际时间时,才模拟运行
if (it.time < SYSTEM_TIME) {
it.fSpec = (100 - road.get_Congestion() - 20) / 3.6;
dist = it.dDistance + it.fSpec * 10;
it.time++;
it.showself();
//如果车十秒后不在此路
if (dist >= road.m_dLength) {
//路径擦除
auto route = it.queRoute;
int site = it.m_nSiteRoadID;
route.pop();
//如果抵达终点
if (route.empty()) {
cout << "it is be shutdown" << endl;
exit(0);
// 否侧没有抵达终点
} else {
//获取所在路和下一条路的ID
int next = route.front();
//判断红绿灯情况
cout << site << endl;
G.m_CrossRoad_v[site].m_CTrafficLight_Light.clock(SYSTEM_TIME);
//如果可以通行
if (G.m_CrossRoad_v[site].m_CTrafficLight_Light.getStatus(it.m_nSiteRoadID, next)) {
cout << GREEN << "绿灯通行:" << endl;
it.queRoute = route;
it.dDistance = 0;
it.m_nSiteRoadID = next;
auto *site_road = &G.m_Road_v[next].m_queVehicle;
site_road->push_back(it);
//如果不能通行
} else {
//将距离置为道路长度,表示正在等候红灯
it.dDistance = G.m_Road_v[it.m_nSiteRoadID].m_dLength;
cout << YELLOW << "等待红灯" << endl;
//车辆塞回去
obj.push_back(it);
}
}
//否则,当车十秒后还在此路时
} else {
it.dDistance = dist;
obj.push_back(it);
}
//否则直接填入
} else {
obj.push_back(it);
}
}
road.m_queVehicle = obj;
}
调试时我主要靠 showself 和彩色 cout 看每步是绿灯还是等红灯。常见坑:换路后车已经进 m_Road_v[next].m_queVehicle,但本轮外层还在遍历原 road,所以必须用 src/obj 双队列,不能把正在换路的车和本路剩余车搅在一起。另一个坑是 m_nSiteRoadID 和路口下标混用——getStatus 第一个参数实际是道路 id,命名当时写得有点误导,对照 思路篇(二) 的 roadID 表查才对上。
系列主流程到这里闭环。路线从哪来、图怎么建,分别回看 番外篇 和 汇总篇。
版权声明: 本文首发于 指尖魔法屋-毕业设计思路篇(五)之交通流量模拟(https://blog.thinkmoon.cn/post/145-graduation-design-traffic-notes-cplusplus/) 转载或引用必须申明原指尖魔法屋来源及源地址!