导航路径规划之二 路网的存储结构
图的两种基本存储结构:邻接矩阵和邻接表,下面分别介绍。
1.1 邻接矩阵
设有一个具有n个节点的有向图G=(V,E),V=(v1,v2…vn).最简单的图的存储结构是一个n×n的0-1阶矩阵A=(aij)n*n来定义,其中:
使用邻接矩阵存储路网的最大优点在于它容易确定某一给定节点射入和发出的弧的集合。如对于节点vi来说,邻接矩阵第i行中的每一个”1”就相当于从节点vi发出一条弧,而第i列中的每个1就相当于有一条弧射入节点vi.
由于邻接矩阵有n的平方个元素,故采用邻接矩阵存储路网的空间代价是O(n3),而与弧的实际数量无关。由于实际路网是一个大型的稀疏图,数据的冗余很大,不适宜采用。
邻接表是另一种常用的图的存储结构。常用于稀疏图。它为稀疏图G的每一个节点分配一个链表,共计m个链表。节点vi∈V对应的链表包含的元素是邻接于节点vi的节点集。
根据定义,稀疏图有m=O(n)条边,因此,用一个邻接表描述稀疏图只需要O(n)个存储空间,显然优于邻接矩阵。我们使用改造的邻接表来表达路网。
路网作为大型稀疏网络,具有交叉口转向限制,因此普通的邻接表存储结构不能满足,需要针对路网的特点以及导航中路线优化的要求,寻求合适的存储结构存储路网,达到存储量尽可能小,同时便于路线优化算法对其进行操作,还能够正确表达交叉口转向限制,合理存储节点权重等要求。
当前最常用的路网存储结构是一种称为“前向关联边”的链表结构。其核心是在于将同一个节点发出的所有弧存放在一起。首先分配一个长度为n的数组,该数组每一个元素对应一个节点,每个节点中有一个指针LinkEdgesPointer,记录由此节点发出的第一条弧在整个弧集中的起始位置,此外,还用一个长度为m的数组LinkEdges存储LinkEdgePointer指针所指向的弧
这种存储结构的一个明显好处是节省存储空间。仅仅使用一个长度为n的数组和一个长度为m的数组,而通常的存储结构需要两个长度为m的数组(一般n<<m).此外,该结构还可方便地对某一给定的节点发出的所有弧进行操作。
更重要的是,该存储结构拥有一个其他存储结构所不具备的特性,即在表示路网的同时,还暗含了交叉口行为,换句话说,车辆在临近给定交叉口时允许采取的操作,如直行,左转、右转等也通过该存储结构隐含的表达出来,具体说,当车辆由弧(i,j)即将到达节点j时,他下一步可采取的操作(下一个可能到达的节点)可以在LinkedEdges数组中找到,其在LinkedEdges数组中的起始位置由该节点的LinkEdgesPointer给出。例如图1-9所示的交叉路口,可以用如图1-10所示的结构图来表示。
图1-9 交叉路口
图1-10 没有禁则关系的交叉路口的表达方法
考虑转向限制,有的交叉路口是禁止左转的,如下图1-11。这种情况下,上面的前向关联边就无法表达这种关系,因此还需要对上面的结构加以改造。
图1-11 有禁行关系的交叉路口
可以在每个节点中添加一个标志信息,该标示信息提示在该节点处有没有转向限制,同时将转向禁止的入边和出边(也可以是进入的节点和出去的节点,如图1-11的进入的节点2和出去的节点2)存储起来,这样在路径规划的时候,当扩展到这样的像节点1的时候,如果下一步扩展的节点是3,则需要检查进入的节点是否是节点2,如果是的话,则表示扩展失败,因为此交叉路口禁止左转。
根据上面的设计思路,可以设计路网的节点与路段的数据结构如下:
节点信息,Nodes(结构体)
描述 | 占用空间 | 指派空间 | 名称 |
X | 4字节 | 4字节 | m_nX |
Y | 4字节 | 4字节 | m_nY |
邻接图符节点号(只在图框点中有,其他0) | 2字节(编译时需判断同一图符内nodeid号是否会大于32767,是则溢出,需修改) | 4字节(高17位存邻接图符号,低15位存节点号) | m_nAdjNode |
邻接图符号(只在图框点中有,其他0) | 17位 | ||
主节点或子节点号码(当前节点如果是子节点则是主点号码,如果是主点则是记录内部路的子点号码,简单路口空,非路口节点空) | 2字节 | 2字节 | m_snCrossNode |
种别代码(收费站,图框点等6种) | 3位 | 1字节(从高位到低位3,2,1,1,1) | m_ucAttribute |
路口标识(非路口,主点,子点,简单路口) | 2位 | ||
红绿灯(有无) | 1位 | ||
交通禁行(有无) | 1位 | ||
路牌(有无)暂填0 | 1位 | ||
接续link | 1字节 | 1字节 | m_ucLinkNum |
接续link地址 | 4字节 | 4字节 | m_psnLinkId |
与节点关联路段:接续link(数组)
描述 | 空间 |
接续link号 | 2字节 |
Edge是弧,该信息分为2部分,第一部分是路径规划需要的信息,第二部分是其他模块需要的信息,这样在路径规划的时候,只需要导入第一部分的信息,从而加快规划的速度,第二部分的信息在其他模块需要的时候再导入到内存中。
Edge(结构体)
描述 | 空间 | 指派空间 | 名称 | |
Part1 | ||||
起点号码 | 2字节 | 2字节 | m_snStartNode | |
终点号码 | 2字节 | 2字节 | m_snEndNode | |
道路长度 | 2字节 | 2字节 | m_snLength | |
道路等级 | 4位 | 1字节 | m_ucLevel | |
通行方向 | 2位 | 1字节(2,1) | m_ucDrection | |
是否收费 | 1位 | |||
Part2 | ||||
道路名称编号 | 2字节 | 2字节 | m_snNameId | |
道路属性 | 5位 | 1字节(5,2) | m_ucAttribute | |
车道数(大于4就取4) =通往起点的车道数+通往终点的车道术-所有附加车道数 | 2位 | |||
起点入角度 | 9位 | 1字节(低8位) | m_ucStartAngle | |
终点入角度 | 9位 | 1字节(低8位) | m_ucEndAngle | |
|
| 1字节(最低位是起点入角度的最高位,次低位是终点入角度的最高位) | m_ucAngleEx |
typedef struct MAP_POINT_TAG
{
int m_nx;
int m_ny;
}MAP_POINT;
typedef struct MAP_SHORT_POINT_TAG
{
short m_snX;
short m_snY;
}MAP_SHORT_POINT;
typedef struct EDGE_EX_TAG
{
int m_nEdgeAttr; //路段属性
short m_snEdgeAngle; //路段角度
short m_snEdgeAngleRever; //路段对角
int m_nSharpOffset; //路段形状点数据的偏移地址
int m_nEdgeNameOffset; //路段名称数据的偏移地址
unsigned char m_ucNum_Slane; //车道数
unsigned char m_ucWidth; //路段宽度
}EDGE_EX;
typedef struct EDGE_TAG
{
unsigned short m_nEdgeLen; //路段长度
unsigned char m_ucEdgeType; //路段等级
unsigned char m_ucDirection; //通行方向(通行方向+收费设置)
unsigned short m_nStartNodeId; //开始节点ID
unsigned short m_nEndNodeId; //结束节点ID
EDGE_EX * m_pstEdgeEx;
}EDGE;
typedef struct NODE_TAG
{
unsigned int m_nAdjMapId:17; //联接图幅号 如果当前节点是图框点,则存储联接图幅号
unsigned int m_nAdjNodeId:15; //联接节点号
unsigned short m_nNodeId; //节点ID 如果当前节点是子节点,则存储该节点的主节点
//主节点或子节点号码(当前节点如果是子节点则是主点号码,如果是
//主点则是记录内部路的子点号码,简单路口空,非路口节点空)
unsigned char m_usnType:2; //节点类型 分主节点 子节点 单一路口 和不是路口的节点
unsigned char m_ucTurnLeft:1; //是否有禁止左转
unsigned char m_ucLed:1; //是否有红绿灯
unsigned char m_ucritical:1; //是否是图框点
unsigned char m_ucGuidePost:1; //是否有路牌
unsigned char m_ucTollGate:1; //是否是收费站
unsigned char m_ucTube:1; //是否是隧道
unsigned char m_ucRelNodedCount:6;//关联节点数
unsigned char m_ucCrossJCT:1; //平面交叉点JCT
unsigned char m_ucCrossInOut:1; //平面交叉点出入口
unsigned int m_nLongtitude; //经度
unsigned int m_nLatitude; //纬度
union {
struct {
unsigned int m_nMapID:13;
unsigned int m_nNodeID:19;
}LowerLevel;
Struct {
Unsigned int m_nMapID:17;
Unsigned int m_nNodeID:15;
}UpperLevel;
}
unsigned short *m_pRelNodeID; //关联节点数组
}NODE;
typedef struct FORBID_TURN_TAG //禁止转向数据结构
{
int m_nInEdgeId; //进路段号
int m_nNodeId; //节点 按节点的大小顺序存储 可用二分查找
int m_nOutEdgeId; //出路段号
}FORBID_TURN;
typedef struct MAP_DATA_TAG //图幅数据
{
unsigned short m_unNodeNum; //节点个数
unsigned short m_unLinkedEdgeNum; //与节点连接的边的个数
struct Edge * Edges; //边的信息
} MAP_DATA;