我有一个线要素(参见图片),它代表使用Stream_to_Feature工具创建的一条河流。属性表包含代表不同行的几条记录-问题是最长的行(在视觉上易于区分)在表中没有表示为单行,实际上是由许多较小的行组成的。线条似乎相互接触,尽管它们彼此不交叉。
如何合并这些线,然后使用ArcObjects或可以转换为ArcObjects的手动方法确定最长的线的长度?更好的解决方案将包括摆脱所有支流,而让我只留一条河道作为一条线。
我有一个线要素(参见图片),它代表使用Stream_to_Feature工具创建的一条河流。属性表包含代表不同行的几条记录-问题是最长的行(在视觉上易于区分)在表中没有表示为单行,实际上是由许多较小的行组成的。线条似乎相互接触,尽管它们彼此不交叉。
如何合并这些线,然后使用ArcObjects或可以转换为ArcObjects的手动方法确定最长的线的长度?更好的解决方案将包括摆脱所有支流,而让我只留一条河道作为一条线。
Answers:
首先,有一点背景说明为什么这不是一个难题。流经河流的流量保证了它的线段(如果正确数字化)始终可以定向形成有向无环图(DAG)。反过来,当且仅当它是DAG时,才可以使用称为拓扑排序的技术对图进行线性排序。拓扑排序速度很快:其时间和空间要求均为O(| E | + | V |)(E =边数,V =顶点数),这与它获得的结果一样好。创建这样的线性排序将使查找主流床变得容易。
那么,这里是算法的草图。溪流的河床位于其主要河床上。沿着连接到嘴的每个分支向上游移动(如果嘴是汇合处,则可能不止一个),然后递归找到通往该分支的主要床。选择总长度最大的分支:这就是沿主床的“反向链接”。
为了更清楚地说明这一点,我提供了一些(未经测试的)伪代码。输入是一组线段(或弧线)S(包含数字化流),每个线段都有两个不同的端点start(S)和end(S)以及正长度length(S);和河口p,这是一个点。输出是将嘴与最远的上游点结合在一起的一系列片段序列。
我们将需要处理“标记段”(S,p)。它们由段S之一及其两个端点p之一组成。我们将需要找到所有与探测点q共享一个端点的线段S,并用其他端点标记这些线段,然后返回集合:
Procedure Extract(q: point, A: set of segments): Set of marked segments.
如果找不到这样的段,则Extract必须返回空集。 副作用是, Extract必须从集合A中删除它返回的所有段,从而修改A本身。
我没有提供Extract的实现:您的GIS将提供选择与q共享端点的段S的功能。标记它们只是将start(S)和end(S)与q进行比较,然后返回两个端点都不匹配的问题。
现在,我们准备解决问题。
Procedure LongestUpstreamReach(p: point, A: set of segments): (Array of segments, length)
A0 = A // Optional: preserves A
C = Extract(p, A0) // Removes found segments from the set A0!
L = 0; B = empty array
For each (S,q) in C: // Loop over the segments meeting point p
(B0, M) = LongestUpstreamReach(q, A0)
If (length(S) + M > L) then
B = append(S, B0)
L = length(S) + M
End if
End for
Return (B, L)
End LongestUpstreamReach
过程“ append(S,B0)”将段S 粘贴在数组B0的末尾并返回新数组。
(如果流确实是一棵树:没有岛屿,湖泊,辫子等,那么您就可以省去将A复制到A0的步骤了。)
通过形成LongestUpstreamReach返回的段的并集来回答原始问题。
为了说明这一点,让我们考虑原始地图中的流。假设将其数字化为七个弧的集合。弧形a从上游的点0(在地图的顶部,在下图的右侧,已旋转)的嘴到点1处的第一汇合处。这是一个长弧,说长8个单位。弧b分支到左侧(在地图中)并且很短,大约2个单位长。弧c向右分支,大约4个单位长,依此类推。以“ b”,“ d”和“ f”表示在地图上从上到下的左侧分支,以“ a”表示, “ c”,“ e”和“ g”这两个其他分支,并对顶点0到7进行编号,我们可以抽象地将图表示为弧的集合
A = {a=(0,1), b=(1,2), c=(1,3), d=(3,4), e=(3,5), f=(5,6), g=(5,7)}
我将假定它们具有长度8,2,4,1,2,2,2为一通过克分别。嘴是顶点0。
第一个示例是对Extract(5,{f,g})的调用。它返回标记的段集{(f,6),(g,7)}。请注意,顶点5位于弧f和g(地图底部的两个弧)的交汇处,并且(f,6)和(g,7)用上游端点标记这些弧中的每一个。
下一个示例是对LongestUpstreamReach(0,A)的调用。采取的第一个动作是对Extract(0,A)的调用。此返回包含标记的段落(A,1)的一组和它消除段一个从该组A0,其现在等于{B,C,d,E,F,G}。循环有一个迭代,其中(S,q)=(a,1)。在此迭代期间,将对LongestUpstreamReach(1,A0)进行调用。递归地,它必须返回序列(g,e,c)或(f,e,c):两者同等有效。的长度(M),它返回是4 + 2 + 2 = 8。(注意,LongestUpstreamReach并没有修改A0。)在循环结束时,段一已附加到流化床中,并且长度已增加到8 + 8 =16。因此,第一个返回值由序列(g,e,c,a)或(f,e,c,a)组成,对于第二种返回值,两种情况下的长度均为16。这显示LongestUpstreamReach如何从嘴巴向上游移动,在每个汇合处选择距离最长的分支,并跟踪沿其路线遍历的线段。
在有许多辫子和孤岛的情况下,可以实现更有效的实现,但是对于大多数目的,如果严格按照所示方式实现LongestUpstreamReach,则几乎不会浪费任何精力,因为在每个汇合处,各个分支中的搜索之间没有重叠:计算时间(和堆栈深度)将与分段总数成正比。