使用ArcGIS Desktop和Python在两个要素类中的相交要素之间寻找角度?[关闭]


19

我有两个相交的线要素类。我想使用ArcGIS 10和Python查找每个相交点的角度。

有人可以帮忙吗?


我已经使用arcpy在Python脚本中复制了whuber的方法(谢谢),但是角度计算存在问题。在Esri ArcMap(字段计算器)中完成后,它可以正确计算。在python脚本中(再次使用字段计算器)进行计算时,其计算错误(以十进制表示)。这不仅仅是从弧度到度的转换。下面是用于计算视场角的arcpy函数。要素类已投影(British National Grid)。我是否需要采取其他步骤来计算python中远离地图文档的角度
Andy

Answers:


13

有一个相对简单的工作流程。 它克服了两个要素可能在多个点相交的潜在问题。它不需要脚本(但可以很容易地变成脚本)。它主要可以从ArcGIS菜单完成。

想法是利用一层相交点,每条相交的折线对分别有一个点。您需要在这些交点处获得一小段每个相交的折线。使用这些零件的方向来计算它们的相交角。

步骤如下:

  1. 确保每个折线要素在其属性表中都有唯一的标识符。稍后将使用它来将折线的某些几何属性连接到相交点表。

  2. Geoprocessing | Intersect获取点(确保指定要输出的)。

  3. 地理处理|缓冲区可让您少量缓冲点。使其真正很小,以使缓冲区中每行的部分不会弯曲。

  4. 地理处理(应用两次)将原始折线图层限制为仅缓冲区。由于这会为其输出生成新的数据集,因此后续操作将不会修改原始数据(这是一件好事)。

    数字

    这是发生的情况的示意图:以浅蓝色和浅红色显示的两个折线图层产生了暗交点。在这些点周围,黄色显示了微小的缓冲区。较深的蓝色和红色部分显示了将原始特征剪切到这些缓冲区的结果。该算法的其余部分适用于暗段。(您在这里看不到它,但是一条微小的红色折线在一条公共点与两条蓝线相交,从而在两条蓝色折线之间产生了缓冲区。实际上,这是在红蓝相交的两个重叠点周围的两个缓冲区。因此,此图总共显示了五个缓冲区。)

  5. 使用AddField工具在每个裁剪的图层中创建四个新字段:[X0],[Y0],[X1]和[Y1]。它们将保持点坐标,因此使它们加倍并赋予它们很多精度。

  6. “计算几何”(通过右键单击每个新的字段标题调用)使您能够计算每个剪切多段线的起点和终点的x和y坐标:将它们放入[X0],[Y0],[X1] ,和[Y1]。这是针对每个裁剪的图层完成的,因此需要进行8次计算。

  7. 使用AddField工具在相交点图层中创建一个新的[Angle]字段。

  8. 加入基于共同对象标识符的截取表到交点表。(通过右键单击图层名称并选择“联接和关联”来执行联接。)

    此时,点相交表具有9个新字段:两个字段名为[X0],依此类推,另一个字段名为[Angle]。 别名 [X0],[Y0],[X1]和[Y1]字段属于联接表之一。让我们称它们为“ X0a”,“ Y0a”,“ X1a”和“ Y1a”。

  9. 使用字段计算器来计算相交表中的角度。这是用于计算的Python代码块:

    dx = !x1!-!x0!
    dy = !y1!-!y0!
    dxa = !x1a!-!x0a!
    dya = !y1a!-!y0a!
    r = math.sqrt(math.pow(dx,2) + math.pow(dy,2))
    ra = math.sqrt(math.pow(dxa,2) + math.pow(dya,2))
    c = math.asin(abs((dx*dya - dy*dxa))/(r*ra)) / math.pi * 180

    当然,场计算表达式仅仅是

    c

尽管该代码块很长,但数学很简单:(dx,dy)是第一个折线的方向向量,而(dxa,dya)是第二个折线的方向向量。它们的长度r和ra(通过勾股定理计算)用于将它们归一化为单位向量。(零长度应该没有问题,因为修剪会产生正长度的特征。)楔形积的大小dx dya-dydxa(除以r和ra之后)是该角度的正弦值。(使用楔形乘积而不是通常的内积应为接近零的角度提供更好的数值精度。)最后,将角度从弧度转换为度。结果将介于0到90之间。请注意直到最后都避免使用三角函数:这种方法倾向于产生可靠且易于计算的结果。

一些点可能在相交层中多次出现。如果是这样,它们将获得与它们关联的多个角度。

此解决方案中的缓冲和剪切操作比较昂贵(第3步和第4步):当涉及数百万个交点时,您不想这样做。我之所以推荐它,是因为(a)简化了在相交点附近沿每条折线查找两个连续点的过程,并且(b)缓冲是如此基础,因此在任何GIS中都很容易实现-无需额外的许可高于基本ArcMap水平-通常会产生正确的结果。(其他“地理处理”操作可能不太可靠。)


1
这可能有效,但是您不能在代码块中引用字段名称,因此您必须将代码包装在函数中,然后使用字段名称作为参数来调用它。
mvexel 2012年

@mv感谢您的观察。也可以使用VBS代替Python-VBS 解析代码块中的字段名称。
ub

1
使用函数包装器时,它实际上就像是一种魅力。我发现在ArcGIS 10中,当使用Python时,您无需为变量加上别名,可以在字段引用中添加联接表名称,例如!table1.x0!
mvexel 2012年

6

我相信您需要创建python脚本。

您可以使用地理处理工具和arcpy来完成。

以下是对您有用的主要工具和想法:

  1. 使用工具Intersect将两条折线(称为PLINE_FC1,PLINE_FC2)要素类相交(结果需要点要素-POINT_FC)。您将获得POLINE_FC点中来自PLINE_FC1,PLINE_FC2的ID。
  2. 拆分PLINE_FC1通过POINT_FC使用工具分割线在点。结果,您将拆分多段线-它的主要优点是,您可以只取该线的第一个/最后一个顶点,将其与下一个/上一个顶点(坐标差)进行比较,然后计算角度。因此,您将在相交点处具有直线角度。这里有一个问题-您必须手动运行此工具多次才能实现输出的写入方式。我的意思是,如果需要折线,请将其拆分,将两条结果折线写入输出,然后继续进行下一条折线并重复。或者可以将这部分(拆分的结果)写入不同的内存要素类,然后附加到输出中。这是主要问题-了解如何编写输出,以便能够在拆分后仅过滤每个折线的第一部分。另一个可能的解决方案是遍历所有结果分割的折线SearchCursor并仅接受第一个遇到的问题(通过源折线PLINE_FC1的ID)。
  3. 为了获得角度,您将需要使用arcpy访问结果折线的顶点。将结果角度写入点(POINT_FC)。
  4. 对PLINE_FC2重复步骤2-3。
  5. 减去角度属性(在POINT_FC中)并获得结果。

可能很难对第2步进行编码(某些工具也需要ArcInfo许可)。然后,您也可以尝试分析每条折线的顶点(交点后按ID将它们分组)。

这是这样做的方法:

  1. 取第一个交点POINT_FC。获取其坐标(point_xpoint_y
  2. 通过其ID,从PLINE_FC1获取相应的源折线。
  3. 取第一(vert0_xvert0_y)和第二(vert1_xvert1_y它)verteces。
  4. 对于第一个顶点,计算该顶点和相交点之间的线的切线: tan0 = (point_y - vert0_y) / (point_x - vert0_x)
  5. 为第二个顶点计算相同的事物: tan1 = (vert1_y - point_y) / (vert1_x - point_x)
  6. 如果tan1等于tan2,则您已找到线的两个顶点,它们之间有相交点,并且可以计算该线的相交角。否则,您必须继续进行下一对顶点(第二,第三对),依此类推。
  7. 对每个交叉点重复步骤1-6。
  8. 对第二条折线要素类PLINE_FC2重复步骤1-7。
  9. 从PLINE_FC1和PLINE_FC2中减去角度属性并获得结果。

1

最近,我试图自己做。

我的线索特征是基于线的交点周围的圆点以及距相距一米距离的点。输出是折线要素类,该类具有交点和角度上的角度编号属性。

请注意,应该对线进行平面化处理才能找到相交点,并且必须使用正确的线长显示来设置空间参考(我的位置是WGS_1984_Web_Mercator_Auxiliary_Sphere)。

在ArcMap控制台中运行,但可以轻松地转换为工具箱中的脚本。该脚本仅在TOC中使用线层,仅此而已。

import arcpy
import time

mxd = arcpy.mapping.MapDocument("CURRENT")
df = mxd.activeDataFrame


line = ' * YOUR POLYLINE FEATURE LAYER * ' # paste the name of line layer here    

def crossing_cors(line_layer):
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    arcpy.env.overwriteOutput = True
    sr = arcpy.Describe(line_layer).spatialReference

    dict_cors = {}
    dang_list = []

    with arcpy.da.UpdateCursor(line_layer, ['SHAPE@', 'OID@']) as uc:
        for row in uc:
            if row[0] is None:
                uc.deleteRow()

    with arcpy.da.UpdateCursor(line_layer, 'SHAPE@', spatial_reference = sr) as uc:
        for row in uc:
            line = row[0].getPart(0)
            for cor in line:
                coord = (cor.X, cor.Y)
                try:
                    dict_cors[coord] += 1
                except:
                    dict_cors[coord] = 1
    cors_only = [f for f in dict_cors if dict_cors[f]!=1]
    cors_layer = arcpy.CreateFeatureclass_management('in_memory', 'cross_pnt', "POINT", spatial_reference = sr)
    arcpy.AddField_management(cors_layer[0], 'ANGLE_NUM', 'LONG')
    with arcpy.da.InsertCursor(cors_layer[0], ['SHAPE@', 'ANGLE_NUM']) as ic:
        for x in cors_only:
            pnt_geom = arcpy.PointGeometry(arcpy.Point(x[0], x[1]), sr)
            ic.insertRow([pnt_geom, dict_cors[x]])
    return cors_layer

def one_meter_dist(line_layer):
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    arcpy.env.overwriteOutput = True
    sr = arcpy.Describe(line_layer).spatialReference

    dict_cors = {}
    dang_list = []
    cors_list = []
    with arcpy.da.UpdateCursor(line_layer, 'SHAPE@', spatial_reference = sr) as uc:
        for row in uc:
            line = row[0]
            length_line = line.length 
            if length_line > 2.0:
                pnt1 = line.positionAlongLine(1.0)
                pnt2 = line.positionAlongLine(length_line - 1.0)
                cors_list.append(pnt1)
                cors_list.append(pnt2)
            else:
                pnt = line.positionAlongLine(0.5, True)
    cors_layer = arcpy.CreateFeatureclass_management('in_memory', 'cross_one_meter', "POINT", spatial_reference = sr)
    ic = arcpy.da.InsertCursor(cors_layer[0], 'SHAPE@')
    for x in cors_list:
        ic.insertRow([x])
    return cors_layer

def circles(pnts):

    import math
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    arcpy.env.overwriteOutput = True
    sr = df.spatialReference

    circle_layer = arcpy.CreateFeatureclass_management('in_memory', 'circles', "POINT", spatial_reference = sr)


    ic = arcpy.da.InsertCursor(circle_layer[0], 'SHAPE@')
    with arcpy.da.SearchCursor(pnts, 'SHAPE@', spatial_reference = sr) as sc:
        for row in sc:
            fp = row[0].centroid
            list_circle =[]
            for i in xrange(0,36):
                an = math.radians(i * 10)
                np_x = fp.X + (1* math.sin(an))
                np_y = fp.Y + (1* math.cos(an))
                pnt_new = arcpy.PointGeometry(arcpy.Point(np_x,np_y), sr)

                ic.insertRow([pnt_new])
    del ic 
    return circle_layer

def angles(centers, pnts, rnd):
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    sr = df.spatialReference

    line_lyr = arcpy.CreateFeatureclass_management('in_memory', 'line_angles', "POLYLINE", spatial_reference = sr)
    arcpy.AddField_management(line_lyr[0], 'ANGLE', "DOUBLE")
    arcpy.AddField_management(line_lyr[0], 'ANGLE_COUNT', "LONG")

    ic = arcpy.da.InsertCursor(line_lyr[0], ['SHAPE@', 'ANGLE', 'ANGLE_COUNT'])

    arcpy.AddField_management(pnts, 'ID_CENT', "LONG")
    arcpy.AddField_management(pnts, 'CENT_X', "DOUBLE")
    arcpy.AddField_management(pnts, 'CENT_Y', "DOUBLE")
    arcpy.Near_analysis(pnts, centers,'',"LOCATION") 

    with arcpy.da.UpdateCursor(line, ['SHAPE@', 'OID@']) as uc:
        for row in uc:
            if row[0] is None:
                uc.deleteRow()

    with arcpy.da.UpdateCursor(pnts, [u'ID_CENT', u'CENT_X', u'CENT_Y', u'NEAR_FID', u'NEAR_DIST', u'NEAR_X', u'NEAR_Y'], spatial_reference = sr) as uc:
        for row in uc:
            row[0] = row[3]
            row[1] = row[5]
            row[2] = row[6]
            uc.updateRow(row)
            if row[4] > 1.1:
                uc.deleteRow()


    arcpy.Near_analysis(pnts, rnd,'',"LOCATION")     

    list_id_cent = []
    with arcpy.da.UpdateCursor(pnts, [u'ID_CENT', u'CENT_X', u'CENT_Y', u'NEAR_FID', u'NEAR_DIST', u'NEAR_X', u'NEAR_Y', 'SHAPE@'], spatial_reference = sr) as uc:
        for row in uc:
            pnt_init = (row[-1].centroid.X, row[-1].centroid.Y)
            list_id_cent.append([(row[1], row[2]), row[3], pnt_init])

    list_id_cent.sort()
    values = set(map(lambda x:x[0], list_id_cent))
    newlist = [[y for y in list_id_cent if y[0]==x] for x in values]

    dict_cent_angle = {}

    for comp in newlist:
        dict_ang = {}
        for i, val in enumerate(comp):

            curr_pnt = comp[i][2]
            prev_p = comp[i-1][2]
            init_p = comp[i][0]


            angle_prev = math.degrees(math.atan2(prev_p[1]-init_p[1], prev_p[0]-init_p[0]))
            angle_next = math.degrees(math.atan2(curr_pnt[1]-init_p[1], curr_pnt[0]-init_p[0]))

            diff = abs(angle_next-angle_prev)%180


            vec1 = [(curr_pnt[0] - init_p[0]), (curr_pnt[1] - init_p[1])]
            vec2 = [(prev_p[0] - init_p[0]), (prev_p[1] - init_p[1])]

            ab = (vec1[0] * vec2[0]) + (vec1[1] * vec2[1]) 
            mod_ab = math.sqrt(math.pow(vec1[0], 2) + math.pow(vec1[1], 2)) * math.sqrt(math.pow(vec2[0], 2) + math.pow(vec2[1], 2))
            cos_a = round(ab/mod_ab, 2)

            diff = math.degrees(math.acos(cos_a))

            pnt1 = arcpy.Point(prev_p[0], prev_p[1])
            pnt2 = arcpy.Point(init_p[0], init_p[1])
            pnt3 = arcpy.Point(curr_pnt[0], curr_pnt[1])


            line_ar = arcpy.Array([pnt1, pnt2, pnt3])
            line_geom = arcpy.Polyline(line_ar, sr)

            ic.insertRow([line_geom , diff, len(comp)])
    del ic

    lyr_lst = [f.name for f in arcpy.mapping.ListLayers(mxd)]
    if 'line_angles' not in lyr_lst:
        arcpy.mapping.AddLayer(df, arcpy.mapping.Layer(line_lyr[0]))


centers = crossing_cors(line)

pnts = one_meter_dist(line)

rnd = circles(centers)

angle_dict = angles(centers, pnts, rnd)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.