Street Centerline "From and To Streets"

3916
29
Jump to solution
06-20-2017 11:52 AM
KevinCross2
New Contributor III

I am trying to update our street center line information that does not have the "From Street" or "To Street" field populated.  All our data has is "On Street".   Is there an "easy" way to write a script or is there a set of tools that can be used to help populate this information automatically?  

0 Kudos
29 Replies
KevinCross2
New Contributor III

Sorry for the late reply.  I've been on vacation and we've been in the process of changing vendors for our GIS hosting.  I ran the script on my full data set and it looks like it did a great job breaking things where I needed them to.  There looks to be some clean up on things, but that's to be expected.  I appreciate your help on this.  This has saved me a lot of man hours doing it manually!

XanderBakker
Esri Esteemed Contributor

Hi Kevin Cross , glad it you found the script useful. It might be necessary to do some editing on the data before and afterwards, since it does not account for all situations. Did you catch any other inconveniences? In case the script solved your question, please mark that post as the correct answer.

0 Kudos
KevinCross2
New Contributor III

The only "inconvenience" I found was when it found more than one "From or To" Street it put multiple street names in the field.  I'd like to see it be able to work " West to East" or "North to South" orientation depending on which way the "On" street is going and use only 1 street as the "From or To" Street. 

Below is a sample of what I'm getting.  

0 Kudos
XanderBakker
Esri Esteemed Contributor

OK, so if I understand this correctly... Let's look at the following record:

Blair Ln goes from Cook Dr to the streets Blairmont Dr and E. Blairmont Dr.:

Would this be "West to East" and therefore "Blairmont Dr" as To Street?

I do see that there are multiple streets that mention the same street twice. This could be corrected.

KevinCross2
New Contributor III

That is correct.  

0 Kudos
XanderBakker
Esri Esteemed Contributor

The easiest first correction (eliminate multiple streets with the same name) would be to change lines 49 and 53:

# line 49:
                from_streets = ', '.join(list(set([dct[o][1] for o in from_oids])))

# line 53:
                to_streets = ', '.join(list(set([dct[o][1] for o in to_oids])))

I will look at the " West to East" or "North to South" orientation to see what I come up with...

XanderBakker
Esri Esteemed Contributor

Hi kevin.cross ,

Here is another go on the N+W preference (please run on a copy of your data):

#-------------------------------------------------------------------------------
# Name:        from_to_street.py
# Purpose:
#
# Author:      xbakker
#
# Created:     21/06/2017
#-------------------------------------------------------------------------------
import arcpy

def main():
    fc = r'C:\GeoNet\FromToStreet\gdb\GeoNet Street Sample.gdb\LebStreetSample_MP2SP'
    fld_fullname = 'Fullname'
    fld_from = 'From_Street'
    fld_to = 'To_Street'
    tolerance = 5  # 5 feet distance tolerance for finding intersections
    min_angle = 45  # to detect angle intersections
    sr = arcpy.Describe(fc).spatialReference

    flds = ('OID@', 'SHAPE@', fld_fullname)
    dct = {r[0]: [r[1], r[2]] for r in arcpy.da.SearchCursor(fc, flds)}
    print "dict filled..."

    cnt = 0
    dct_res = {}  # fill with results
    for oid, lst in dct.items():
        polyline = lst[0]
        full_name = lst[1]
        if full_name != ' ':
            cnt += 1
            if cnt % 50 == 0:
                print "Processing:", cnt
            from_pnt = polyline.firstPoint
            to_pnt = polyline.lastPoint
            # get a list of candidates for from and to points
            from_oids = SelectFromToCandidates(oid, full_name, from_pnt, dct, tolerance, sr)
            to_oids = SelectFromToCandidates(oid, full_name, to_pnt, dct, tolerance, sr)
            # print "  - before filter: ",  full_name, oid, from_oids, to_oids
            if len(from_oids) > 0:
                from_oids = FilterListOnAngles(dct, oid, from_oids, min_angle)
            if len(to_oids) > 0:
                to_oids = FilterListOnAngles(dct, oid, to_oids, min_angle)

            # implement the  " West to East" or "North to South" orientation pref
            if len(from_oids) > 1:
                seg_from = polyline.segmentAlongLine(0.0, 0.05, True)
                from_oids = AnalyzeOrientationNWvsSE(dct, from_oids, seg_from, from_pnt)
            if len(to_oids) > 1:
                seg_to = polyline.segmentAlongLine(0.95, 1.0, True)
                to_oids = AnalyzeOrientationNWvsSE(dct, to_oids, seg_to, to_pnt)

            # print "  - after filter: ", full_name, oid, from_oids, to_oids
            if len(from_oids) == 0:
                from_streets = ''
            else:
                from_streets = ', '.join(list(set([dct[o][1] for o in from_oids])))
            if len(to_oids) == 0:
                to_streets = ''
            else:
                to_streets = ', '.join(list(set([dct[o][1] for o in to_oids])))

            if from_streets in [' ' , ' ,  ']:
                from_streets = ''
            if to_streets in [' ' , ' ,  ']:
                to_streets = ''

            # print oid, " - From:", from_streets, from_oids
            # print oid, " - To  :", to_streets, to_oids
            dct_res[oid] = [oid, from_streets, to_streets]

    print "update cursor..."
    cnt = 0
    updates = 0
    flds = ('OID@', fld_from, fld_to)
    with arcpy.da.UpdateCursor(fc, flds) as curs:
        for row in curs:
            cnt += 1
            oid = row[0]
            if oid in dct_res:
                result = dct_res[oid]
                curs.updateRow(tuple(result))
                updates += 1

            if cnt % 50 == 0:
                print"processing row:", cnt, " - updates:", updates


def AnalyzeOrientationNWvsSE(dct, candidates, seg, pnt):
    best_angle = None
    best_candidate = None
    sr = seg.spatialReference
    direction_main = GetDirectionOfSegment(seg)
    angle_main = GetAngleSegment(seg)

    pntg = arcpy.PointGeometry(pnt, sr)
    for candidate in candidates:
        polyline = dct[candidate][0]

        # get start segment and end segment of polyline
        start1 = polyline.segmentAlongLine(0, 10, False)
        end1 = polyline.segmentAlongLine(polyline.length-10, polyline.length, False)

        # determine distance start segment and end segment to intersecting line
        d_start = start1.distanceTo(pntg)
        d_end = end1.distanceTo(pntg)
        if d_start < d_end:
            # start is closer to intersecting line
            seg1 = start1
        else:
            # end is closer to intersecting line
            seg1 = end1

        angle_cand = GetAngleSegment(seg1)
        direction_cand = GetDirectionOfSegment(seg1)
        score = GetScoreAngle(direction_main, direction_cand)
        if best_angle == None:
            best_angle = [angle_cand, direction_cand, score]
            best_candidate = candidate
        else:
            if score < best_angle[2]:
                best_angle = [angle_cand, direction_cand, score]
                best_candidate = candidate
    return [best_candidate]

def GetScoreAngle(direction_main, direction_cand):
    dct = {'N': ['W', 'SW', 'NW', 'E', 'NE', 'SE', 'S', 'N'],
           'S': ['W', 'NW', 'SW', 'E', 'SE', 'NE', 'N', 'S'],
           'W': ['N', 'NE', 'NW', 'S', 'SE', 'SW', 'E', 'W'],
           'E': ['N', 'NW', 'NE', 'S', 'SW', 'SE', 'W', 'E'],
           'NW': ['N', 'W', 'NE', 'SW', 'S', 'E', 'SE', 'NW'],
           'SW': ['NW', 'N', 'W', 'SE', 'S', 'E', 'NE', 'SW'],
           'NE': ['NW', 'W', 'N', 'SE', 'S', 'E', 'SW', 'NE'],
           'SE': ['N', 'W', 'NW', 'SW', 'NE', 'S', 'E', 'SE']}

    lst = dct[direction_main]
    return lst.index(direction_cand)

def GetDirectionOfSegment(seg):
    angle = GetAngleSegment(seg)
    return GetAngleDirection(angle)

def GetAngleDirection(angle):
    angle_defs = [('N', -22.5, 22.5), ('NE', 22.5, 67.5), ('E', 67.5, 112.5),
                  ('SE', 112.5, 157.5), ('S', 157.5, 180.1), ('S', -180, -157.5),
                  ('SW', -157.5, -112.5), ('W', -112.5, -67.5), ('NW', -67.5, -22.5)]

    direction = 'Not Set'
    for angle_def in angle_defs:
        if angle >= angle_def[1] and angle < angle_def[2]:
            direction = angle_def[0]
            break
    return direction

def FilterListOnAngles(dct, oid, candidates, angle_tolerance):
    polyline = dct[oid][0]
    oids_ok = []
    for candidate in candidates:
        polyline_cand = dct[candidate][0]
        if ValidateAngle(polyline, polyline_cand, angle_tolerance, oid):
            oids_ok.append(candidate)
    return oids_ok


def ValidateAngle(polyline1, polyline2, angle_tolerance, oid):
    try:
        # get start segment and end segment
        start1 = polyline1.segmentAlongLine(0, 1, False)
        end1 = polyline1.segmentAlongLine(polyline1.length-1, polyline1.length, False)
        # sr = polyline1.spatialReference

        # determine distance start segment and end segment to intersecting line
        d_start = start1.distanceTo(polyline2)
        d_end = end1.distanceTo(polyline2)
        if d_start < d_end:
            # start is closer to intersecting line
            pntg = polyline2.queryPointAndDistance(polyline1.firstPoint, False)[0]
            seg1 = start1
        else:
            # end is closer to intersecting line
            pntg = polyline2.queryPointAndDistance(polyline1.lastPoint, False)[0]
            seg1 = end1

        # get position of projected point on intersecting line
        distance_along = polyline2.measureOnLine (pntg, False)
        # and extract segment
        seg2 = polyline2.segmentAlongLine(max([distance_along -1, 0]),
                            min([distance_along +1, polyline2.length]), False)

        # compare
        compare_angles = [seg1, seg2]
        angle_segs = GetAngleSegments(compare_angles)
        # print "angle_segs 1", angle_segs

        while angle_segs < 0:
            angle_segs += 180

        while angle_segs > 360:
            angle_segs -= 360

        # print "angle_segs 2", angle_segs
        if IsBetween(angle_segs, angle_tolerance, 180 - angle_tolerance) or IsBetween(angle_segs, angle_tolerance + 180, 360 - angle_tolerance):
            return True
        else:
            # print " - NOT IsBetween...", angle_segs
            return False
    except Exception as e:
        print "ValidateAngle ERROR @ OID:", oid
        return False

def IsBetween(val, val_min, val_max):
    if val >= val_min and val <= val_max:
        return True
    else:
        return False

def GetAngleSegment(seg):
    pntg1 = arcpy.PointGeometry(seg.firstPoint, seg.spatialReference)
    pntg2 = arcpy.PointGeometry(seg.lastPoint, seg.spatialReference)
    angle = GetAngle(pntg1, pntg2)
    return angle

def GetAngleSegments(segments):
    try:
        angles = []
        for seg in segments:
            pntg1 = arcpy.PointGeometry(seg.firstPoint, seg.spatialReference)
            pntg2 = arcpy.PointGeometry(seg.lastPoint, seg.spatialReference)
            angle = GetAngle(pntg1, pntg2)
            angles.append(angle)

        #print "angles", angles
        angle_between = angles[0] - angles[1]
        return angle_between
    except:
        return None

def GetAngle(pntg1, pntg2):
    '''determine angle of line based on start and end points geometries'''
    return pntg1.angleAndDistanceTo(pntg2, method='PLANAR')[0]

def SelectFromToCandidates(cur_oid, cur_full_name, pnt, dct, tolerance, sr):
    oids = []
    pntg = arcpy.PointGeometry(pnt, sr)
    for oid, lst in dct.items():
        full_name = lst[1]
        if oid != cur_oid:
            # don't include the current polyline in results
            if full_name != cur_full_name:
                # don't include street with the same name
                polyline = lst[0]
                if polyline.distanceTo(pntg) <= tolerance:
                    oids.append(oid)
    return list(set(oids))

if __name__ == '__main__':
    main()
KevinCross2
New Contributor III

Xander,

I just ran this on my original database and it worked FLAWLESS!!  Thank you very much for your help on this.  I appreciate it!

0 Kudos
XanderBakker
Esri Esteemed Contributor

That is good news! Thanks for sharing and I'm glad it worked! 

0 Kudos
ahagopian_coj
Occasional Contributor

I know this post is a bit old but I am trying to use your python script to do the same thing - calculate to and from streets per street block/segment.  I keep getting an error and can't figure it out.  PYTHON ERROR.PNG

0 Kudos