本篇文章将描述判断Shape线数据的连通性问题。Shape线数据描述出来的是一幅图,判断图上两点是否连通通常使用深度遍历或者广度遍历。整个算法的过程分为下面几个部分:
- 构建图
- 判断起始点和终点是否连通
- 如果连通,获得连通的通路
- 遍历分为有向图遍历和无向图遍历
如果单独判断图的连通话,那么问题就很简单。但是Shape数据是不含拓扑关系,而且数据量不知,所以不能使用矩阵的方式来存储连通性。所以构建图也是重点工作之一。因为在构建图的过程中,或多或少的可以判断起始点和终点是否连接了。这样就能减少避免等全图全部构建后再判断连通性,从而提高算法的性能。
Shape构建的图有一点特殊性,就是它是基于位置的。也就是说如果连通,那么连通中的点大部分在一个Buffer内。就好像北京到上海的线路选择不会考虑先跑到新疆然后再折回到上,所以考虑的数据可以是起始点和终点构成的一个Buffer中。至于这个Buffer如何抉择,暂时没有深度的考虑。或许可以基于起始点和终点构建一个如下图的一个椭圆Buffer。
所以,整个算法调整为下面几个步骤。
- 读取Shape的Geo包围盒。并对这个包围盒进行分割,划分多个块,从而加快构建图
- 依次读取一条线段记录(Shape线数据保存的是线数据,如果考虑Buffer,可以进行裁剪)
- 根据这条记录的位置快速找到所在的块,并和已经存在的数据建立拓扑关系。
- 如果这条记录是起始点记录,在建立拓扑的时候,它所连接的点设置属性:ReachToStart=true,表明它连接的点是可以连通到起始点的。
- 如果这条记录是终点记录,那么处理过程类似起始点记录的处理
- 如果这个记录是普通节点,那么判断它连接的点是否可以到起始点,如果可以那么设置ReachToStart=true。类似的处理是否可以连通到终点。
- 如果一个节点是新设置为ReachToStart=true,那么它可以到起始点的信息扩散到它连接点。类似的处理ReachToEnd=true的问题。
- 如果在处理过程中,发现一个节点既可以ReachToStart也可以ReachToEnd,表明已经判断起始点和终点是可以连接的。
- 判断可以连通之后,存在ReachToStart或者ReachToEnd节点构建的是一个连通图,只要简单的深度遍历就可以找到中间连通节点。
可以看出,整个过程将构建拓扑和判断连通性融合在一起,从而减少构建完全拓扑后再判断连通的处理。同样利用可达的扩散性,不需要对记录进行排序,依次读取记录即可。同时,算法利用数据的空间性,构建空间块和构建Buffer,加快构建拓扑结构。
本算法只识别是否是否连通,并不考虑最短路径和连通全集。如果需要,那么整个算法的性能跟先构建图,再判断连通性没有差异,因为需要对所有的数据都读取一遍而且不能使用Buffer.
下面粘贴部分代码供参考。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WorldWind;
using WorldWind.PipeSystem.DataSource;
using WorldWind.Renderable.Shape;
namespace Connection
{
/// <summary>
/// 一个节点的信息
/// </summary>
public class NodeInfo
{
#region 熟悉
private bool m_ReachToStart = false;
private bool m_ReachToEnd = false;
private bool m_IsStartNode = false;
private bool m_IsEndNode = false;
#endregion
/// <summary>
/// 节点的位置
/// </summary>
public Shapefile_Point NodePos;
/// <summary>
/// 包含本节点的线段集合
/// </summary>
public List<ShapeRecord> BelongSRs = new List<ShapeRecord>();
/// <summary>
/// 前一个可达起始点的节点
/// </summary>
public NodeInfo PreStartReachNode = null;
#region 对外属性
public bool IsStartNode
{
get { return m_IsStartNode; }
set
{
m_IsStartNode = value;
m_ReachToStart = value;
}
}
public bool IsEndNode
{
get { return m_IsEndNode; }
set
{
m_IsEndNode = value;
m_ReachToEnd = value;
}
}
public bool ReachToStart
{
get { return m_ReachToStart; }
set { m_ReachToStart = value; }
}
public bool ReachToEnd
{
get { return m_ReachToEnd; }
set { m_ReachToEnd = value; }
}
/// <summary>
/// 连接到本节点的数据
/// </summary>
public List<NodeInfo> IncomeSRs = new List<NodeInfo>();
/// <summary>
/// 本节点连接出的数据
/// </summary>
public List<NodeInfo> OutcomeSRs = new List<NodeInfo>();
#endregion
#region 级联
public NodeInfo SetPreStartNode()
{
if (this.IsEndNode)
return this;
foreach (NodeInfo item in IncomeSRs)
{
if (this.PreStartReachNode == item)
{
continue;
}
if (item.PreStartReachNode == null)
{
item.PreStartReachNode = this;
NodeInfo startNode = item.SetPreStartNode();
if (startNode != null)
{
return startNode;
}
}
}
foreach (NodeInfo item in OutcomeSRs)
{
if (this.PreStartReachNode == item)
{
continue;
}
if (item.PreStartReachNode == null)
{
item.PreStartReachNode = this;
NodeInfo startNode = item.SetPreStartNode();
if (startNode != null)
{
return startNode;
}
}
}
return null;
}
#endregion
#region 其他
public bool EqualPos(Shapefile_Point pos)
{
if (NodePos.X == pos.X
&& NodePos.Y == pos.Y
&& NodePos.Z == pos.Z)
{
return true;
}
return false;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append(BelongSRs[0].RecordIndex.ToString());
for (int i = 1; i < BelongSRs.Count; i++)
{
sb.Append(',' + BelongSRs[i].RecordIndex.ToString());
}
return sb.ToString();
}
#endregion
}
public class NodeTree
{
#region 属性
private GeographicBoundingBox m_box;
private List<NodeArea> m_nodeAreas = new List<NodeArea>();
private NodeInfo m_convergeNode = null;
private NodeInfo m_startNode;
private NodeInfo m_endNode;
private bool m_flowDirection = false;
#endregion
#region 对外属性
public NodeInfo EndNode
{
get { return m_endNode; }
}
public NodeInfo StartNode
{
get { return m_startNode; }
}
/// <summary>
/// 是否考虑流向
/// </summary>
public bool FlowDirection
{
get { return m_flowDirection; }
set { m_flowDirection = value; }
}
#endregion
#region 构造函数
public NodeTree(GeographicBoundingBox box)
{
m_box = box;
}
#endregion
private void GetNodeAreeIndex(Shapefile_Point point, out int rowIndex, out int columnIndex)
{
rowIndex = (int)((point.Y - m_box.South) / 100 + 1);
columnIndex = (int)((point.X - m_box.West) / 100 + 1);
}
public bool AddNode(ShapeRecord BelongSR, bool isStartSR, bool isEndSR)
{
NodeInfo leftNode = InsertNode(BelongSR, 0);
NodeInfo rightNode = InsertNode(BelongSR, 1);
if (isStartSR)
{
leftNode.IsStartNode = true;
rightNode.IsStartNode = true;
m_startNode = rightNode;
}
if (isEndSR)
{
leftNode.IsEndNode = true;
rightNode.IsEndNode = true;
m_endNode = leftNode;
}
leftNode.OutcomeSRs.Add(rightNode);
rightNode.IncomeSRs.Add(leftNode);
if (m_flowDirection == false)
{
//如果考虑方向,那么Right是无法到Left的
leftNode.ReachToStart |= (rightNode.ReachToStart);
}
leftNode.ReachToEnd|=(rightNode.ReachToEnd);
rightNode.ReachToStart |= (leftNode.ReachToStart);
if (m_flowDirection == false)
{
//如果考虑方向,那么Right是无法到Left的
rightNode.ReachToEnd |= (leftNode.ReachToEnd);
}
if (leftNode.ReachToEnd && leftNode.ReachToStart)
{
m_convergeNode = leftNode;
return true;
}
if (rightNode.ReachToEnd && rightNode.ReachToStart)
{
m_convergeNode = rightNode;
return true;
}
if (leftNode.ReachToStart)
{
if (EffectStart(leftNode))
return true;
}
if (leftNode.ReachToEnd)
{
if (EffectEnd(leftNode))
return true;
}
if (rightNode.ReachToStart)
{
if (EffectStart(rightNode))
return true;
}
if (rightNode.ReachToEnd)
{
if (EffectEnd(rightNode))
return true;
}
return false;
}
#region 获得
public List<NodeInfo> GetPathNodes()
{
m_endNode = m_startNode.SetPreStartNode();
List<NodeInfo> items = new List<NodeInfo>();
items.Add(m_endNode);
NodeInfo preNode = m_endNode.PreStartReachNode;
while (preNode != null)
{
items.Add(preNode);
preNode = preNode.PreStartReachNode;
}
return items;
}
#endregion
#region 病毒传染
/// <summary>
/// currentNode相关的节点,因为currentNode能够通往起点而能够通往起点
/// </summary>
/// <param name="currentNode"></param>
/// <returns></returns>
private bool EffectStart(NodeInfo currentNode)
{
if (m_flowDirection == false)
{
if (EffectStart(currentNode, currentNode.IncomeSRs))
return true;
}
if (EffectStart(currentNode, currentNode.OutcomeSRs))
return true;
return false;
}
/// <summary>
/// SRS所有的数据因为currentNode从起点来,而能够从起点来
/// </summary>
/// <param name="currentNode"></param>
/// <param name="SRS"></param>
/// <returns></returns>
private bool EffectStart(NodeInfo currentNode, List<NodeInfo> SRS)
{
foreach (NodeInfo item in SRS)
{
if (item.ReachToStart == true)
continue;
item.ReachToStart = true;
if (item.ReachToEnd && item.ReachToStart)
{
m_convergeNode = item;
return true;
}
EffectStart(item);
}
return false;
}
/// <summary>
/// currentNode相关的节点,因为currentNode能够通往目的地而能够通往目的地
/// </summary>
/// <param name="currentNode"></param>
/// <returns></returns>
private bool EffectEnd(NodeInfo currentNode)
{
if (EffectEnd(currentNode, currentNode.IncomeSRs))
return true;
if (m_flowDirection == false)
{
if (EffectEnd(currentNode, currentNode.OutcomeSRs))
return true;
}
return false;
}
/// <summary>
/// SRS所有的数据因为currentNode能够到目的地
/// </summary>
/// <param name="currentNode"></param>
/// <param name="SRS"></param>
/// <returns></returns>
private bool EffectEnd(NodeInfo currentNode, List<NodeInfo> SRS)
{
foreach (NodeInfo item in SRS)
{
if (item.ReachToEnd == true)
continue;
item.ReachToEnd = true;
if (item.ReachToEnd && item.ReachToStart)
{
m_convergeNode = item;
return true;
}
EffectEnd(item);
}
return false;
}
#endregion
#region 插入一个节点
private NodeInfo InsertNode(ShapeRecord BelongSR, int pointIndex)
{
int rowIndex = 0;
int columnIndex = 0;
GetNodeAreeIndex(BelongSR.PolyLine.Points[pointIndex], out rowIndex, out columnIndex);
NodeInfo leftNode = null;
foreach (NodeArea item in m_nodeAreas)
{
if (item.AreaCodeColumnIndex == columnIndex &&
item.AreaCodeRowIndex == rowIndex)
{
leftNode = item.AddNode(BelongSR.PolyLine.Points[pointIndex], BelongSR);
break;
}
}
if (leftNode == null)
{
NodeArea na = new NodeArea(rowIndex, columnIndex);
leftNode = na.AddNode(BelongSR.PolyLine.Points[pointIndex], BelongSR);
m_nodeAreas.Add(na);
}
return leftNode;
}
#endregion
}
public class NodeArea
{
public int AreaCodeRowIndex;
public int AreaCodeColumnIndex;
public List<NodeInfo> Nodes;
#region 构造函数
public NodeArea(int rowIndex, int columnIndex)
{
AreaCodeRowIndex = rowIndex;
AreaCodeColumnIndex = columnIndex;
Nodes = new List<NodeInfo>();
}
#endregion
public NodeInfo AddNode(Shapefile_Point point, ShapeRecord BelongSR)
{
foreach (NodeInfo node in Nodes)
{
if (node.EqualPos(point))
{
node.BelongSRs.Add(BelongSR);
return node;
}
}
NodeInfo nnode = new NodeInfo();
nnode.BelongSRs.Add(BelongSR);
nnode.NodePos = point;
Nodes.Add(nnode);
return nnode;
}
}
}
下面是调用的代码
NodeTree nt = new NodeTree(shp.SourceGeoBB);
nt.FlowDirection = this.checkBox_FlowDirection.Checked;
int index=0;
this.listBox1.Items.Clear();
bool isConnected = false;
foreach (ShapeRecord sr in shp.DataRecord)
{
bool isStartNode = (index == m_startIndex);
bool isEndNode = (index == endIndex);
if (nt.AddNode(sr, isStartNode, isEndNode))
{
isConnected = true;
break;
}
index++;
}
上面代码中,部分类没有写出来的是自定义类,读者可以自行实现。最后附上效果图