Hello! I am working on a Python project, and I am just about done with it, but I am struggling with formatting my Get Count function. I am writing a script to get the number of sales within a 1/2 mile of feedlots, but to finish it off, I need to return the number of feedlots that are within that half mile of the sales. So if a particular sale point has 1-3 feedlots within a half mile, I need to return that number, but right now, I am getting the number of sales instead (basically the opposite of what I need). I have tried swapping the feature classes in my function, but I only get '1' as a result. Is there a better way to write my code, or am I missing something?
I think there's an order of operations problem here.
First thing: I don't think "select_feedlot" is necessary; you don't use it and also it's redundant for what you're doing anyway.
The way a cursor works is by going through each record in a table, one at a time. So, doing a select by attribute on the same table to get the exact same record is not a good use of time.
This is also the cause of the other, larger, problem, which is that all of your select by locations are plugging in the entire feedPoint_lyr, not just the current record you're trying to analyze for. I'm assuming that all of your sales are within 2640 feet of SOME feedlot, so if you plug in every feedlot, you're going to get every sale.
To get around this, use the SHAPE@ keyword (not sure if that's the right term for it) in your cursor's fields, and feed each record's geometry to SelectByLocation
with arcpy.da.SearchCursor(feedPoint_lyr, ['PIN', 'SHAPE@']) as cursor:
for row in cursor:
# Each row is returned as a tuple: (PIN, geometry)
# feed it the second item in the tuple
sales = arcpy.management.SelectLayerByLocation(
address_lyr, 'WITHIN_A_DISTANCE',
row[1], '2640 FEET'
)
Alrighty! I'm still fairly new to Python, so I figured it was most likely an order of operations problem..
For formatting purposes, the code sample you shared, would that just substitute my 'for row in arcpy though the selections? My end goal is to create a csv output that has the sale and feedlot parcels, permits of the feedlots, etc, so I'm wanting to try and loop through all of my records I believe.
No problem!
The way you have it set up isn't wrong.
Cursors are conventionally done the way I did it (peep the documentation for arcpy.da.SearchCursor, although the documentation for the older arcpy.SearchCursor doesn't do it) because it's supposed to be used as a context manager, meaning that it closes itself when it's done, removing any locks and freeing up resources. This sort of construction is great in case of errors; if you use it to open a file and then something breaks, it closes the file as part of the error reporting process.
That being said, it's been discovered that cursors don't actually behave like this, at least in file geodatabases, so you should still add in a del when you're done as a best practice (even though it's supposed to be doing it behind the scenes for you automatically when you use a with.
with arcpy.da.SearchCursor(lay, fields) as cursor:
for row in cursor:
#code
pass
del cursor
Alfred's covered almost all of the basics you need here really well.
One thing I feel is important to explicitly point out where his code meaningfully differs from yours is that he has only one level of SearchCursor. This is very deliberate, and decidedly a best practice.
Nesting different SearchCursors or UpdateCursors can cause your code to be monumentally slower than you'd expect, because the inner cursor runs through its entire table/view once for every record in the outer cursor.
Simply put, consider the following:
with arcpy.da.SearchCursor(tbl_one, fieldList_one) as cursor1:
for rowA in cursor1:
with arcpy.da.SearchCursor(tbl_two, fieldList_two) as cursor2:
for rowB in cursor2:
#do stuff
If you have 100 records in tbl_one and 100 records in tbl_two, you'd end up in a loop of 10,000 iterations. This problem also grows exponentially, as you can probably see.
Even working with a smaller inner table like you were, this is often incredibly inefficient. There are very narrow cases where it might appear okay, at first glance. But I would argue that it's almost always a red flag that your underlying/theoretical procedure needs some revisiting.
The one exception typically cited is InsertCursor. Because of the way that one works, it's both fine and frequently standard practice to nest it inside either a SearchCursor or UpdateCursor.
That makes sense!
I'm still running into some issues with getting it to work, as well as an AttributeError: _exit_ issue.. Here is what the bulk of my script looks like for a little bit more context...
For the lot_site dict and sale_site dict, I haven't had any issues with how they are set up or anything of the sorts. My script has been running very well other than when I try and troubleshoot my GetCount objectives.
Um, would it be possible for you to post it as a codeblock?
(1) Code formatting ... the Community Version - Esri Community
def mapping(s, t, u, r):
lot_site = dict()
sale_site = dict()
parcel_site =[]
spatial_ref = arcpy.Describe(u).spatialReference
for row in arcpy.da.SearchCursor(u, ['CountyPIN', 'SHAPE@X', 'SHAPE@Y']):
if row[0] in t:
lot_site.update({row[0] : (row[1], row[2])})
for row in arcpy.da.SearchCursor(r,['PIN','SHAPE@X', 'SHAPE@Y']):
if row[0] not in lot_site and row[0] in t:
lot_site.update({row[0] : (row[1], row[2])})
fc_feedlots = arcpy.CreateFeatureclass_management('in_memory', 'FeedPoint', 'POINT', '', '', '', spatial_ref)
arcpy.AddField_management(fc_feedlots, 'PIN', 'TEXT')
with arcpy.da.InsertCursor(fc_feedlots, ['SHAPE@', 'PIN']) as cur:
for k, v in lot_site.iteritems():
feed_point = [v[0], v[1]]
cur.insertRow([feed_point, k])
feedPoint_lyr = arcpy.MakeFeatureLayer_management(fc_feedlots, 'feedlot_lyr')
address_lyr = arcpy.MakeFeatureLayer_management(u, 'Address_lyr')
x = 0
for row in arcpy.da.SearchCursor(feedPoint_lyr, ['SHAPE@', 'PIN']):
select_feedlot = arcpy.SelectLayerByAttribute_management(feedPoint_lyr, "NEW_SELECTION", "PIN = '{}'".format(row[0]))
sales = arcpy.management.SelectLayerByLocation(
address_lyr, 'WITHIN_A_DISTANCE',
row[0], '2640 FEET'
)
for sale in arcpy.da.SearchCursor(sales, ['CountyPIN']):
if sale[0] in s:
x += 1
#sales_check(sale[0], sale_site)
saleParcel = sale[0]
feedParcel = row[0]
feedPerm = t.get(row[0])[0]
adj = s.get(sale[0])[0]
netsale = s.get(sale[0])[1]
pt = s.get(sale[0])[2]
au_state = t.get(row[0])[1]
ratio = s.get(sale[0])[3]
count = int(arcpy.management.GetCount(feedPoint_lyr)[0])
sale_site.update({x : [saleParcel, feedParcel, feedPerm, adj, netsale, pt, au_state, ratio, count]})
arcpy.Delete_management('in_memory')
print("Sale_site dictionary has been updated using Feedlots and Sales info")
return sale_site
My apologies! I didn't know that this was an option, I hope this is helpful!!
Also, where is the error happening in this?
Everything has been working up until the Select Layer by Location within that half mile to get the information regarding what points are within that ROI. My original script that I have that works just fine but provides the incorrect feedlot count is as follows:
#Identifies all sales within a 1/2 mile radius of feedlots, and creates a diction that will be passed into a new CSV output function.
# S- Sales Listing, T- Feedlot Location, U- Address Points, R- Tax Parcel polygons, V- Output CSV file
# K is refrencing the Keys within the Dictionaries.
def mapping(s, t, u, r):
lot_site = dict()
sale_site = dict()
parcel_site =[]
spatial_ref = arcpy.Describe(u).spatialReference
for row in arcpy.da.SearchCursor(u, ['CountyPIN', 'SHAPE@X', 'SHAPE@Y']):
if row[0] in t:
lot_site.update({row[0] : (row[1], row[2])})
for row in arcpy.da.SearchCursor(r,['PIN','SHAPE@X', 'SHAPE@Y']):
if row[0] not in lot_site and row[0] in t:
lot_site.update({row[0] : (row[1], row[2])})
fc_feedlots = arcpy.CreateFeatureclass_management('in_memory', 'FeedPoint', 'POINT', '', '', '', spatial_ref)
arcpy.AddField_management(fc_feedlots, 'PIN', 'TEXT')
with arcpy.da.InsertCursor(fc_feedlots, ['SHAPE@', 'PIN']) as cur:
for k, v in lot_site.iteritems():
feed_point = [v[0], v[1]]
cur.insertRow([feed_point, k])
feedPoint_lyr = arcpy.MakeFeatureLayer_management(fc_feedlots, 'feedlot_lyr')
address_lyr = arcpy.MakeFeatureLayer_management(u, 'Address_lyr')
x = 0
for row in arcpy.da.SearchCursor(feedPoint_lyr, ['PIN']):
select_feedlot = arcpy.SelectLayerByAttribute_management(feedPoint_lyr, "NEW_SELECTION", "PIN = '{}'".format(row[0]))
sales = arcpy.SelectLayerByLocation_management(address_lyr, 'WITHIN_A_DISTANCE', feedPoint_lyr, '2640 FEET', 'NEW_SELECTION')
for sale in arcpy.da.SearchCursor(sales, ['CountyPIN']):
if sale[0] in s:
x += 1
y = 1
#sales_check(sale[0], sale_site)
saleParcel = sale[0]
feedParcel = row[0]
feedPerm = t.get(row[0])[1]
adj = s.get(sale[0])[0]
netsale = s.get(sale[0])[1]
au_state = t.get(row[0])[0]
ratio = s.get(sale[0])[2]
count = y
sale_site.update({x : [saleParcel, feedParcel, feedPerm, adj, netsale, au_state, ratio, count]})
arcpy.Delete_management('in_memory')
return sale_site
This script works, but again just provides me the incorrect feedlot count within a half mile of the sales points. When I run the script, it's only giving me '1' as a value for the feedlot count, but as we discussed, it's because the loop is only considering that one feedlot record or something of the sorts. I just need to find a way to manage a GetCount function into this script so that I can reach my final results.
Thank you for all the help you've been providing so far!!!