毕业设计思路篇(一)之交通图的构建

仿真里所有「车能往哪拐、灯管哪几条路」,最后都落在这张图上。图建歪了,后面写得再漂亮也是白搭。
系列第一篇,讲怎么把道路 XML 变成程序里的交通图。当时路网数据来自网络侧预处理,已经是 XML,每条路有长度、两端经纬度和节点 id。仿真不直接画地图,只关心拓扑:哪些路口相连、沿哪条有向边开、边多长。这篇做完,Graph 里就有 m_CrossRoad_v 和 m_Road_v,思路篇(二) 才能给每个路口挂交通灯。
1. 导入道路地图
道路地图来自网络数据,已预处理为 xml 格式。
<?xml version='1.0' encoding='UTF-8'?>
<Roads>
<road m_ID="71283896--1" len="620.98">
<from lon="113.9205606" lat="22.9317667" id="848388981"/>
<to lon="113.9260573" lat="22.9341232" id="2522072722"/>
</road>
<road m_ID="553852656--2" len="322.34">
<from lon="113.9529339" lat="22.9448978" id="5345735265"/>
<to lon="113.9516926" lat="22.9475618" id="5345735267"/>
</road>
</Roads>
解析:一个 road 节点代表一条道路,len 代表道路抽象长度,from、to 子节点分别表示道路两端端点。
读文件时按 road 逐条解析,取出 len 和两端坐标构造临时 CrossRoad,再调 addRoad 写进图。物理上一条路双向可通行,模型里拆成两条方向相反的有向边,方便后面按 inRoadID / outRoadID 描述「从哪条路进、从哪条路出」。
2. 解析道路数据
a. 构建交通图
路口不是简单一个点:同一个经纬度路口上,可能有多条进路、多条出路。我在 CrossRoad 里用 vector<Node> JunctionRoad 存方向道路节点——每个 Node 记一对 (inRoadID, outRoadID),再带一个 atan2,表示从该路口看出去的方向角,后面靠右行规则把节点分成四组(注释里画的 1–8 那套)。
赋值道路端点。
/**
* 路口类,记录着该路口的点坐标,以及其相连的方向道路节点组
* | |
* | | |
* | 1 2 |
* | | |
* -------- --------
* 3 5
* - - - - - - - -
* 4 6
* -------- --------
* | | |
* | 7 8 |
* | | |
* | |
* 如上图(1,2), (3,4), (5,6), (7,8)在同一个方向,我将其称为四组方向道路节点Node.
* 其中,Node.inRoadID=1, Node.outRoadID=2;
* Node.inRoadID=4, Node.outRoadID=3;
* ...
* 根据车辆靠右行原则以此类推.
*/
class CrossRoad {
public:
CrossRoad(float fLon,float fLat) : m_fLat(fLat),m_fLon(fLon){};
/**
* 重载运算符 (==) 判断两个路口是否为同一个
*/
bool operator==(CrossRoad &crossRoad);
/**
* 添加道路节点ID
* @param in 入度
* @param out 出度
* @param atan2 该点与方向道路的atan2值
*/
void addNode(int in,int out,double atan2);
public:
//唯一标示符
int m_nID;
//经纬度的定义
float m_fLon, m_fLat;
vector<Node> JunctionRoad;
//该路口的交通灯
TrafficLight m_CTrafficLight_Light;
};
operator== 用经纬度判「是不是同一个路口」,避免 XML 里重复端点被当成两个节点。addNode 在 addRoad 末尾调用,把新边的入边、出边 id 和方向角登记到路口上。
CrossRoad A,B
添加道路到交通图
void addRoad(Graph &Map_Graph, CrossRoad A, CrossRoad B, double length) {
// 初始化A,B路口的索引位置为-1
int CrossRoadSiteB = -1, CrossRoadSiteA = -1;
auto CrossRoadNum = Map_Graph.m_CrossRoad_v.size(), RoadNum = Map_Graph.m_Road_v.size();
//循环判断是否有重合点
for (int i = 0; i < CrossRoadNum; i++) {
if (Map_Graph.m_CrossRoad_v[i] == A) {
CrossRoadSiteA = i;
}
if (Map_Graph.m_CrossRoad_v[i] == B) {
CrossRoadSiteB = i;
}
}
//如果不存在与A点重合的路口,添加路口,保存路口索引
if (CrossRoadSiteA == -1) {
Map_Graph.m_CrossRoad_v.push_back(A);
CrossRoadSiteA = CrossRoadNum++;
Map_Graph.m_CrossRoad_v[CrossRoadSiteA].m_nID = CrossRoadSiteA;
}
if (CrossRoadSiteB == -1) {
Map_Graph.m_CrossRoad_v.push_back(B);
CrossRoadSiteB = CrossRoadNum++;
Map_Graph.m_CrossRoad_v[CrossRoadSiteB].m_nID = CrossRoadSiteB;
}
int RoadSiteA = RoadNum, RoadSiteB = RoadNum + 1;
// 确定A,B路的site,加入模型图
Road roadA(RoadSiteA, CrossRoadSiteA, CrossRoadSiteB, length);
Map_Graph.m_Road_v.push_back(roadA);
Road roadB(RoadSiteB, CrossRoadSiteB, CrossRoadSiteA, length);
Map_Graph.m_Road_v.push_back(roadB);
// 对接A,B路口节点数据
Map_Graph.m_CrossRoad_v[CrossRoadSiteA].addNode(RoadSiteA, RoadSiteB,
atan2((B.m_fLat - A.m_fLat), (B.m_fLon - A.m_fLon)));
Map_Graph.m_CrossRoad_v[CrossRoadSiteB].addNode(RoadSiteB, RoadSiteA,atan2((A.m_fLat - B.m_fLat), (A.m_fLon - B.m_fLon)));
}
addRoad 干了几件事:先在已有路口向量里找 A、B 是否已存在;不存在就 push_back 并赋 m_nID(用向量下标当路口编号)。然后连续 push 两条 Road,索引分别是 RoadNum 和 RoadNum+1。最后在 A、B 两个路口上各 addNode 一次,atan2(Δlat, Δlon) 区分从该路口看向对方的方位。
踩坑记录:早期没做路口去重,同一条物理路被解析成四个端点,图直接炸;改成 operator== 按坐标合并后才正常。另外 CrossRoadNum++ 写在赋值语句里,新手容易看错——只有新插入路口时才递增,已存在的路口只更新索引。
解析的结果如下
跑完导入后,会把道路表、路口邻接关系 dump 成文本方便肉眼核对:
核对没问题就可以进 思路篇(二) 给每个 CrossRoad 初始化 m_CTrafficLight_Light 了。路径离线计算见 番外篇,不在这篇展开。
版权声明: 本文首发于 指尖魔法屋-毕业设计思路篇(一)之交通图的构建(https://blog.thinkmoon.cn/post/70-graduation-design-traffic-practice-cplusplus/) 转载或引用必须申明原指尖魔法屋来源及源地址!