Calculating the inner angle

2714
6
12-09-2017 06:28 PM
Ahmad_Muhamad_Senousi_Ahmad
New Contributor II

I have some polygons and I want to calculate the inner angle of each polygon. Now, I have the geometry of every line of polygons in the same attribute table as shown in the following image.
I want to calculate the inner angle using the following equation:


inner_angle= the bearing angle of the previous line - the bearing angle of the following line +180 (in degrees).

How can I write this in the code block in the model builder and do this for each polygon respectively?

0 Kudos
6 Replies
DanPatterson_Retired
MVP Emeritus

screen grab of your first 2 polygons? just to confirm something

and you are working with full circle angles and not angles from north I presume

0 Kudos
Ahmad_Muhamad_Senousi_Ahmad
New Contributor II

Yes. The attached image just illustrates geometry table of points.
I just need code execute that :
_______________________________

for each polygon >>>>>>( Can the iterate feature in model builder do this loop ( for each polygon)?)
 for i = 1: last line in the polygon 
      if i < the last line in the polygon 
         angle = the line bearing @ i - the line bearing @ (i+1) +180 

         if angle >360

            angle= angle -360

         end

        if angle < 0

            angle = angle +360

        end
     else 
     angle =   the line bearing @ i - the line bearing @ first line

         if angle >360

            angle= angle -360

         end

        if angle < 0

            angle = angle +360

        end
     end 

 end 

_____________________________

The desired result as follow 

I hope that I illustrated the problem clearly.

DanPatterson_Retired
MVP Emeritus

If you just want the sum of the interior angles of a polygon... convex, concave, whatever... it might be easier to

  • retrieve the polygon from the shape field
  • devolve it to points
  • calculate the interior angles from the rolling triplets of points making up the polygon then
  • sum the result and return

I guess I am not sure why you need the individual angles within the polygon and why you converted the polygon to its edges to get this

0 Kudos
XanderBakker
Esri Esteemed Contributor

I would probably create a script tool that would be based on the following parameters:

  • the featureclass
  • the input polygon ID field
  • the input line bearing field
  • the "input" inner angle field (should exist or should be created by the tool) where the results will be written
  • the same input featureclass as output parameter defined so you can connect the output to other tools in your model

The code would be something like this:

def main():
    import arcpy
    from collections import OrderedDict

    # read parameters
    fc = arcpy.GetParameterAsText(0)  # r'C:\GeoNet\InnerAngle\data.gdb\angles'
    fld_pol_id = arcpy.GetParameterAsText(1)  # 'polygonID'
    fld_bearing = arcpy.GetParameterAsText(2)  # 'line_bearing'
    fld_inner = arcpy.GetParameterAsText(3)  # 'inner_angle'

    # create a lst of the data
    fld_oid = 'OID@'
    flds = (fld_oid, fld_pol_id, fld_bearing)
    dct_data = OrderedDict((r[0], [r[1], r[2]]) for r in arcpy.da.SearchCursor(fc, flds))

    # create a list with polygon IDs
    pol_ids = list(set([data[0] for oid, data in dct_data.items()]))

    # process the polygons and store results in dct
    dct_res = {}
    for pol_id in pol_ids:
        dct_bearing = OrderedDict((oid, data[1]) for oid, data in dct_data.items() if data[0] == pol_id)
        lst_bearings = dct_bearing.values()
        cnt = 0
        for oid, bearing in dct_bearing.items():
            if cnt == len(lst_bearings)-1:
                inner_angle = lst_bearings[cnt] - lst_bearings[0] + 180
            else:
                inner_angle = lst_bearings[cnt] - lst_bearings[cnt+1] + 180
            if inner_angle > 360:
                inner_angle -= 360
            if inner_angle < 0:
                inner_angle += 360
            dct_res[oid] = inner_angle
            cnt += 1

    # update the fc with the results
    flds = (fld_oid, fld_inner)
    with arcpy.da.UpdateCursor(fc, flds) as curs:
        for row in curs:
            oid = row[0]
            if oid in dct_res:
                row[1] = dct_res[oid]
                curs.updateRow(row)

    # set output
    arcpy.SetParameterAsText(4, fc)


if __name__ == '__main__':
    main()

This is what a test generated:

The reason that this will not be possible using a simple feature iteration is that when writing the current feature the line bearing of the next feature must be known.

DanPatterson_Retired
MVP Emeritus

So.. if you want to give this a shot and you are working with single part shapes since I have experimented a whole 5 minutes.  I pulled this from elsewhere and cobbled a framework around the base code.

Actually it is more useful without the lines 30 and 31.  It is designed to return all the angles of a poly* feature.  If the feature is convex then it returns a boring 360 degrees for polygons.  Concave closed-loop shapes should differ from 360

For polylines (or could be modified just to accept sequential point objects), you can get all the angles.  The code can be modified ever so slightly to calculate the sequential differences in lengths and you can use those to derived 'weighted direction', but I will leave that out for this discussion.

Yes... to call it use the expression

angles_poly(!Shape!)
import numpy as np
import arcpy
def angles_poly(a, inside=True, in_deg=True):
    """Sequential angles from a poly* shape
    """
    a = a.getPart()
    a =np.asarray([[i.X, i.Y] for j in a for i in j])
    if len(a) < 2:
        return None
    elif len(a) == 2:  # **** check
        ba = a[1] - a[0]
        return np.arctan2(*ba[::-1])
    else:
        angles = []
        if np.allclose(a[0], a[-1]):  # closed loop
            a = a[:-1]
            r = (-1,) + tuple(range(len(a))) + (0,)
        else:
            r = tuple(range(len(a)))
        for i in range(len(r)-2):
            p0, p1, p2 = a[r[i]], a[r[i+1]], a[r[i+2]]
            ba = p1 - p0
            bc = p1 - p2
            cr = np.cross(ba, bc)
            dt = np.dot(ba, bc)
            ang = np.arctan2(np.linalg.norm(cr), dt)
            angles.append(ang)
    if in_deg:
        angles = np.degrees(angles)
    angle = np.sum(angles)
    return angle
DanPatterson_Retired
MVP Emeritus

hmmm, fun stuff ... modified hexagons, which followed the (n-2)*180 rule 

Curious as to what you are using this for?  A convexity index?

:----------------------------------------------------------------------
:Angle report....
....
.... snip ....
....
(2) number of angles... (7)
:array points...
[[  300025.98  5000035.  ]
 [  300034.96  5000033.03]
 [  300043.3   5000035.  ]
 [  300043.3   5000025.  ]
 [  300034.64  5000020.  ]
 [  300025.98  5000025.  ]
 [  300025.98  5000035.  ]]

:interior angles [  77.61  154.32   76.71  120.    120.    120.  ]
:sum interior... 668.641029246