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?
Solved! Go to Solution.
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!
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.
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.
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.
That is correct.
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...
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()
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!
That is good news! Thanks for sharing and I'm glad it worked!
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.