5

I am trying to get a number of points in polygons based on row values, and write them into attribute table of polygons. I have polygon (Zones), and points (Objects with fields such as schools, markets, hotels, shops) which are located in those polygons. I want to select points based on their attributes (for example i want to know how many schools are in my polygon, and write it into a field. and repeat that process for other attributes for how many hotels/markets/shops are there in polygon zones, etc).

There was a similar question and this answer was good to Count number of attributes for unique field values, but only for selection by location. That script returned count from all number of objects in polygon zones, and it was not based on attributes, just on location.

I managed to modify the script, from the given link. This is the code, and I get result, sum of all points and it was written into row:

import arcpy
Objects = r'D:\GIS_Temp\Points.shp'
Zones = r'D:\GIS_Temp\Zones.shp'
Objects_lyr = arcpy.MakeFeatureLayer_management(Objects,r'in_memory\Objects_lyr')
Zones_lyr = arcpy.MakeFeatureLayer_management(Zones,r'in_memory\Zones_lyr')
fields = ['Descrip','Point_Sum']
with arcpy.da.UpdateCursor(Zones_lyr,fields) as cur:
    for row in cur:
        Zones = row[0]
        where = '"Descrip" = \'{}\''.format(Zones)
        arcpy.SelectLayerByAttribute_management(Zones_lyr,'NEW_SELECTION',where)
        arcpy.SelectLayerByLocation_management(Objects_lyr,'INTERSECT',Zones_lyr)        
        Point_Sum = int(arcpy.GetCount_management(Objects_lyr).getOutput(0)) 
        row[1] = Point_Sum
        cur.updateRow(row)
print('Operation complete.')

And this is the result:

enter image description here

But I want to update Zones, based on intersecting Objects, but based on attributes, and update rows in Zones, with number of intersecting objects (Hotels, Shops, Markets, Schools).

What am I missing i my code. I suppose it has something to with selection by attributes, or with loop as suggested in comments, I should create loop within a loop, but I am not sure where to put it, and how. I understand why, but do not know how.

EDIT:

I tried to modify code, like this, so I made selection by attribute individually and try to put them in their own row, but with no luck. Code ok, there are not any errors, but it does not update anything.

Is there any modification to this, which would speed up my workflow, in case I have more than four values (like in this example; schools, etc)? This is the code:

import arcpy
Objects = r'D:\GIS_Temp\Points.shp'
Zones = r'D:\GIS_Temp\Zones.shp'
ObjectsSCHOOLS_lyr = arcpy.MakeFeatureLayer_management(Objects,r'SCHOOLS', "\"Name\" = 'Schools'")
ObjectsMARKETS_lyr = arcpy.MakeFeatureLayer_management(Objects,r'MARKETS', "\"Name\" = 'Markets'")
ObjectsHOTELS_lyr = arcpy.MakeFeatureLayer_management(Objects,r'HOTELS', "\"Name\" = 'Hotels'")
ObjectsSHOPS_lyr = arcpy.MakeFeatureLayer_management(Objects,r'SHOPS', "\"Name\" = 'Shops'")
Zones_lyr = arcpy.MakeFeatureLayer_management(Zones,r'in_memory\Zones_lyr')
fields = ['Descrip','Schools_Nr', 'Markets_Nr', 'Hotels_Nr', 'Shops_Nr']
with arcpy.da.UpdateCursor(Zones_lyr,fields) as cur:
    for row in cur:
        state = row[0]
        where = '"Descrip" = \'{}\''.format(Zones)
        arcpy.SelectLayerByAttribute_management(Zones_lyr,'NEW_SELECTION',where)
        arcpy.SelectLayerByLocation_management(ObjectsSCHOOLS_lyr,'INTERSECT',Zones_lyr)        
        Schools_Nr = int(arcpy.GetCount_management(ObjectsSCHOOLS_lyr).getOutput(0)) 
        row[1] = Schools_Nr
        cur.updateRow(row)
    for row in cur:
        state = row[0]
        where = '"Descrip" = \'{}\''.format(Zones)
        arcpy.SelectLayerByAttribute_management(Zones_lyr,'NEW_SELECTION',where)
        arcpy.SelectLayerByLocation_management(ObjectsMARKETS_lyr,'INTERSECT',Zones_lyr)        
        Markets_Nr = int(arcpy.GetCount_management(ObjectsMARKETS_lyr).getOutput(0)) 
        row[1] = Markets_Nr
        cur.updateRow(row)
    for row in cur:
        state = row[0]
        where = '"Descrip" = \'{}\''.format(Zones)
        arcpy.SelectLayerByAttribute_management(Zones_lyr,'NEW_SELECTION',where)
        arcpy.SelectLayerByLocation_management(ObjectsHOTELS_lyr,'INTERSECT',Zones_lyr)        
        Hotels_Nr = int(arcpy.GetCount_management(ObjectsHOTELS_lyr).getOutput(0)) 
        row[1] = Hotels_Nr
        cur.updateRow(row)
    for row in cur:
        state = row[0]
        where = '"Descrip" = \'{}\''.format(Zones)
        arcpy.SelectLayerByAttribute_management(Zones_lyr,'NEW_SELECTION',where)
        arcpy.SelectLayerByLocation_management(ObjectsSHOPS_lyr,'INTERSECT',Zones_lyr)        
        Shops_Nr = int(arcpy.GetCount_management(ObjectsSHOPS_lyr).getOutput(0)) 
        row[1] = Shops_Nr
        cur.updateRow(row)         
print('Operation complete.')
  • ArcPy code could certainly be written to do this, but for help writing code can you always include a code attempt, please? – PolyGeo May 30 '16 at 6:58
  • @PolyGeo, code is inserted now – Dean7 May 30 '16 at 15:10
  • 2
    You are almost there, you need a loop within a loop. The master loop is your update cursor loop. You select all points intersecting a zone polygon. You then need a search cursor on your point layer to count number of shops/hotels/schools etc. I would probably store that information in a dictionary. Finally you step through the dictionary updating the current zone fields. – Hornbydd May 30 '16 at 16:49
  • I am not exactly sure where and how to put search cursor for points, and integrate it with main loop. – Dean7 May 30 '16 at 17:32
  • 2
    You are still working with one cursor, do a search on "arcpy nested cursor" and you will find some Q/A showing this. – artwork21 May 31 '16 at 18:15
3
+50

You can refer to the following code, I tested it. Hope it can help you.

import arcpy

arcpy.env.overwriteOutput = True

Objects = r'H:\Input\Points.shp'
Zones = r'H:\Input\Zones.shp'

# Get unique field values (Define a function)
def getUniqueValues(fc,field_name):
    unique_Values = set()
    rows = arcpy.SearchCursor(fc)
    for row in rows:
        unique_Values.add(row.getValue(field_name))
    del row,rows
    return unique_Values

# Get values count by dictionary
def getValuesCount(lyr,fieldName,lst_Values):
    dic_Values_Count = {}
    for value in lst_Values:
        dic_Values_Count[value] = 0
    rows = arcpy.SearchCursor(lyr)
    for row in rows:
        value = row.getValue(fieldName)
        dic_Values_Count[value] += 1
    del row,rows
    return dic_Values_Count

# Get Names list
set_obj_Names = getUniqueValues(Objects,'Name')
# Get Unique Zone_Name
set_zone_Names = getUniqueValues(Zones,'Zone_Name') # If you want to use Descrip, you can replace 'Zone_Name to 'Descrip'

# Delete exists names
obj_Names = list(set_obj_Names)
obj_Names.append('Point_Sum')
arcpy.DeleteField_management(Zones,obj_Names)

# Add need fields
for field_Name in obj_Names:
    field_Type = "TEXT"
    if field_Name == 'Point_Name':
        field_Type = 'SHORT'
    arcpy.AddField_management(Zones,field_Name,field_Type)

# Main

Objects_lyr = arcpy.MakeFeatureLayer_management(Objects,r'Objects_lyr')
Zones_lyr = arcpy.MakeFeatureLayer_management(Zones,r'Zones_lyr')
# Get Count and save it into temp dictionary
whereField = arcpy.AddFieldDelimiters(Zones,'Zone_Name')
dic_ZoneInfo = {}
for zone_Name in set_zone_Names:
    whereClause = whereField + " = '" + zone_Name + "'"
    arcpy.SelectLayerByAttribute_management(Zones_lyr,'NEW_SELECTION',whereClause)
    arcpy.SelectLayerByLocation_management(Objects_lyr,'INTERSECT',Zones_lyr)
    Point_Sum  =  int(arcpy.GetCount_management(Objects_lyr).getOutput(0))
    dicTemp = getValuesCount(Objects_lyr,'Name',obj_Names)
    dicTemp['Point_Sum'] = Point_Sum
    dic_ZoneInfo[zone_Name] = dicTemp
del Objects_lyr,Zones_lyr

# Update Counts and Point_Sum
updateRows = arcpy.UpdateCursor(Zones)
for row in updateRows:
    zone_Name = row.getValue('Zone_Name')
    dic_Values_Count = dic_ZoneInfo[zone_Name]
    new_Fields = obj_Names
    new_Fields.append('Point_Sum')
    for field_Name in new_Fields:
        row.setValue(field_Name,dic_Values_Count[field_Name])
    updateRows.updateRow(row)
del row,updateRows

enter image description here

  • I copied your code (I just replaced paths to my data), and I get an error: >>> Runtime error ... Traceback (most recent call last): ... File "<string>", line 58, in <module> ... File "<string>", line 26, in getValuesCount ... UnboundLocalError: local variable 'row' referenced before assignment – Dean7 Jun 1 '16 at 22:03
  • This code just created new empty rows in polygons, but with no value in them, – Dean7 Jun 1 '16 at 22:11
  • But I don' really need new fields, just update existing ones. Other user suggested nested cursors, and dictionaries. Could that be implemented in my old code? Because I do not get where is error with your code. – Dean7 Jun 1 '16 at 22:43
  • 1
    You try replace all "row" and "rows" to other word(eg.row_Unique,rows_Unique,row_Count,rows_Count) in getUniqueValues and getValuesCount function. – T J Jun 1 '16 at 22:46
  • 1
    I tested my code with ArcGIS 10.2.1. What version is you use? You try for statement to while. I am outside now. You can reference searchCursor or updateCursor in the ArcGIS help. – T J Jun 1 '16 at 23:44
0

As I had few errors with the code, I removed the

del row, rows

from line 20, which is the part of the code for getting values count. Now it appears to be working. If i do not delete this part, I get this error:

Runtime error Traceback (most recent call last): File "", line 41, in File "", line 20, in getValuesCount UnboundLocalError: local variable 'row' referenced before assignment

But, everything else from accepted answer is working. Maybe this is something in my code, but this is working, at least for me:

import arcpy
arcpy.env.overwriteOutput = True
Objects = r'D:\GIS_Temp\Points.shp'
Zones = r'D:\GIS_Temp\Zones.shp'
def getUniqueValues(fc,field_name):
    unique_Values = set()
    rows = arcpy.SearchCursor(fc)
    for row in rows:
        unique_Values.add(row.getValue(field_name))
    del row,rows
    return unique_Values
def getValuesCount(lyr,fieldName,lst_Values):
    dic_Values_Count = {}
    for value in lst_Values:
        dic_Values_Count[value] = 0
    rows = arcpy.SearchCursor(lyr)
    for row in rows:
        value = row.getValue(fieldName)
        dic_Values_Count[value] += 1
    # here was delete row rows part, and it was deleted and now it works
    return dic_Values_Count
set_obj_Names = getUniqueValues(Objects,'Name')
set_zone_Names = getUniqueValues(Zones,'Zone_Name')
obj_Names = list(set_obj_Names)
obj_Names.append('Point_Sum')
arcpy.DeleteField_management(Zones,obj_Names)
for field_Name in obj_Names:
    field_Type = "TEXT"
    if field_Name == 'Point_Name':
        field_Type = 'SHORT'
    arcpy.AddField_management(Zones,field_Name,field_Type)
Objects_lyr = arcpy.MakeFeatureLayer_management(Objects,r'Objects_lyr')
Zones_lyr = arcpy.MakeFeatureLayer_management(Zones,r'Zones_lyr')
whereField = arcpy.AddFieldDelimiters(Zones,'Zone_Name')
dic_ZoneInfo = {}
for zone_Name in set_zone_Names:
    whereClause = whereField + " = '" + zone_Name + "'"
    arcpy.SelectLayerByAttribute_management(Zones_lyr,'NEW_SELECTION',whereClause)
    arcpy.SelectLayerByLocation_management(Objects_lyr,'INTERSECT',Zones_lyr)
    Point_Sum  =  int(arcpy.GetCount_management(Objects_lyr).getOutput(0))
    dicTemp = getValuesCount(Objects_lyr,'Name',obj_Names)
    dicTemp['Point_Sum'] = Point_Sum
    dic_ZoneInfo[zone_Name] = dicTemp
del Objects_lyr,Zones_lyr
updateRows = arcpy.UpdateCursor(Zones)
for row in updateRows:
    zone_Name = row.getValue('Zone_Name')
    dic_Values_Count = dic_ZoneInfo[zone_Name]
    new_Fields = obj_Names
    new_Fields.append('Point_Sum')
    for field_Name in new_Fields:
        row.setValue(field_Name,dic_Values_Count[field_Name])
    updateRows.updateRow(row)
del row,updateRows
  • Then I also tested my code on ArcGIS 10, there are no errors. It could also be other reasons. Although some strange, but delete this line of code can run, and do not cause data is locked, you can use this first. If you know the cause of the error, please let me know. – T J Jun 6 '16 at 1:30
  • I don't know, there could be various reasons. I will try to see what is the cause of error of course. – Dean7 Jun 6 '16 at 12:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.