Select to view content in your preferred language

Update attributes of selected

3058
20
09-10-2020 11:06 AM
2Quiker
Frequent Contributor

Trying to update some well data from another layer. I am trying to do this in Arcmap (10.6) with a tool/script (3.6.10 Python).

I need to transfer attributes from WellParcels to WellsPoints. I would like to do this with out starting a Edit session in python if possible as this tends to slow down the process...? I would manual start an edit session in ArcMap then run the tool/script when I have points selected, I currently have the following but error out on line 44. I am not sure this is the best/fastest way of doing it, so if someone has a different idea please share how I would do this with my data.

import arcpy,os

arcpy.env.overwriteOutput = True

arcpy.env.workspace = r'Database Connections\Connection to blah.sde'
ptSelection = "WellPoints"  
pointLayer = arcpy.env.workspace + os.sep + "WellPoints"  
parcel = 'WellParcels' 
sjpoints = "In_memory\sjpoints"


try: 
    ptCount = int(arcpy.GetCount_management(ptSelection).getOutput(0))

    dsc = arcpy.Describe(ptSelection)     
    selection_set = dsc.FIDSet             
    if len(selection_set) == 0:           
        print "There are no features selected"        
                     
    elif ptCount >= 1:
        arcpy.SelectLayerByLocation_management(parcel,"INTERSECT",ptSelection)
        arcpy.MakeFeatureLayer_management(parcel,"parcel_lyr")        
        
        arcpy.SpatialJoin_analysis(ptSelection, "parcel_lyr" , sjpoints)
        #sourceFieldsList = 'WellID', 'dept', 'type', ' Geothermal' 'GeoWaterUs', 'Temp', 'TempDate'
        #valueDict = {r[0]:(r[1:]) for r in arcpy.da.SearchCursor(sjpoints, sourceFieldsList)}

        fldList = ['WellID', 'dept', 'type', ' Geothermal' 'GeoWaterUs', 'Temp', 'TempDate']          
        fldDict ={}
        print (fldDict)

        targetFields = ['W_ID', 'dept', 'type', ' Geothermal' 'GeoWaterUs', 'Temp', 'TempDate']         
                      
        with arcpy.da.UpdateCursor(ptSelection, targetFields) as cursor:
            for row in cursor
            row[0] = ['W_ID']
            row[1] = ['dep']
            row[2] = ['type']
            row[3] = ['Geothermal']
            row[4] = ['GeoWaterUs']
            row[5] = ['Temp']
            row[6] = ['TempDate']
            #Attributes from Wellparcels     
            curosr.append(fldDict['Account', 'dept', 'type', ' Geothermal' 'GeoWaterUs', 'Temp', 'TempDate'])     
except Exception, e:     
    # If an error occurred, print line number and error message     
    import traceback, sys     
    tb = sys.exc_info()[2]     
    print("Line %i" % tb.tb_lineno)  
    arcpy.AddMessage("Line %i" % tb.tb_lineno)  
    print(e.message)  
    arcpy.AddMessage(e.message)  ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
20 Replies
2Quiker
Frequent Contributor

Sorry about that. How do I get this to work on multiple selected? It works great with one feature selected but not with multiple.

0 Kudos
DanPatterson
MVP Esteemed Contributor

You have to cycle through the selection,

A couple of print statements would make it apparent whether you are getting just the first or just the last


... sort of retired...
0 Kudos
2Quiker
Frequent Contributor

I am having a hard time looping through the selections, I am not sure how to pass it to the rest of my code. Then again I am not sure my approach is the best...?

ptCount = int(arcpy.GetCount_management(ptSelection).getOutput(0))

dsc = arcpy.Describe(ptSelection)     
selection_set = dsc.FIDSet             
if len(selection_set) == 0:           
    print "There are no features selected"
             
elif ptCount >= 1:

    oidList = []

    with arcpy.da.SearchCursor(ptSelection, ['SHAPE@','OID@']) as cursor:
        for row in cursor:
            oidList.append(row[0])
            oid = row[1]
            arcpy.AddMessage(oid) # does not print anything in window.
            
    del cursor

    for OID in oidList:
        arcpy.SelectLayerByLocation_management(parcel,"INTERSECT",ptSelection,"OBJECTID = {}".format(str(row[1])))
        arcpy.MakeFeatureLayer_management(parcel,"parcel_lyr") 

##next??‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
RandyBurton
MVP Alum

To assist in understanding your project, the goal is to update attributes in the well points layer with data from the parcels layer feature the well is located in, correct?  Does each parcel contain only one well or can it contain several wells?  Do you need to only update a few wells where the data is missing, or do you want to update all wells? Could you share some sample data?

0 Kudos
2Quiker
Frequent Contributor

Randy,

I have attached the point and taxlots, I would like to update just the selected well points, maybe one or maybe 50, 100+, not all wells need to be updated just missing ones or the ones I select, some parcel may have more than one well. I am not sure if failed attempt to do it is the best and fastest way, maybe spatial join, also some records can have 0 or null, blanks...? The other question is there a way to update the fields on the points from the taxlots if they exist/match without having to type each field out? Thanks for the help Randy!

0 Kudos
RandyBurton
MVP Alum

I tested the following code inside Desktop 10.5 with parcels and points layers in the map.  It may give you some ideas.  I did not use a where clause for the update cursor, but one could be used to limit the rows to ones where the Well ID is null or '' (zero length).

points = "WellPoints"
parcels = "WellParcels"

parcelFlds = ['WellID', 'dept', 'type', 'Geothermal', 'GeoWaterUs', 'Temp', 'TempDate']
pointFlds = ['W_ID', 'dept', 'type', 'Geothermal', 'GeoWaterUs', 'Temp', 'TempDate', 'SHAPE@']


with arcpy.da.UpdateCursor(points, pointFlds) as cursor:
    for row in cursor:
        arcpy.management.SelectLayerByLocation(parcels, "INTERSECT", row[7], "", "NEW_SELECTION")
        with arcpy.da.SearchCursor(parcels, parcelFlds) as parcelsCursor:
            for parcelRow in parcelsCursor:
                # print parcelRow
                row[0] = parcelRow[0]
                row[1] = parcelRow[1]
                row[2] = parcelRow[2]
                row[3] = parcelRow[3]
                row[4] = parcelRow[4]
                row[5] = parcelRow[5]
                row[6] = parcelRow[6]
                cursor.updateRow(row) # update row

I also noticed a few typos in your code - perhaps copy/paste errors.  A missing comma and an extra space:

>>> fldList = ['WellID', 'dept', 'type', ' Geothermal' 'GeoWaterUs', 'Temp', 'TempDate']
>>> fldList
['WellID', 'dept', 'type', ' GeothermalGeoWaterUs', 'Temp', 'TempDate']
>>> 

Hope this helps.

0 Kudos
RandyBurton
MVP Alum

I did some testing with the data you provided using Desktop 10.5.    I did notice that the field names didn't match with what you were trying in your scripts, so that may be something to watch out for.  Here's a script for you to try, It uses your shape files, so you may need to make adjustments if you are using a file/server geodatabase.  It is basically the approach in my earlier post.

import arcpy

points = "GeoWellTemp" # layers in map
parcels = "WellTaxlots"

# if outside ArcMap (or inside ArcMap with an empty document), add these extra steps
ptsPath = r'C:\Path\to\GeoWellTemp.shp'
parPath = r'C:\Path\to\WellTaxlots.shp'
arcpy.MakeFeatureLayer_management(ptsPath, points)
arcpy.MakeFeatureLayer_management(parPath, parcels)
# end extra steps

# parcel and point fields are paired; geometry is added at end for points
parcelFlds = ['WellID', 'W_DEPTH', 'Geothermal', 'GeoWaterUs', 'TEMP', 'TEMP_DATE']
pointFlds = ['WellID', 'W_DEPTH', 'Geothermal', 'GeoWaterUs', 'TEMP', 'TEMP_DATE', 'SHAPE@']

# clear any selected features
arcpy.SelectLayerByAttribute_management(points, "CLEAR_SELECTION")
arcpy.SelectLayerByAttribute_management(parcels, "CLEAR_SELECTION")

where_clause = 'WellID IS NULL OR WellID = 0' # where clause if needed
with arcpy.da.UpdateCursor(points, pointFlds, where_clause) as cursor:
    for row in cursor:
        # row[6] is the point's geometry - SHAPE@
        arcpy.management.SelectLayerByLocation(parcels, "INTERSECT", row[6], "", "NEW_SELECTION")
        with arcpy.da.SearchCursor(parcels, parcelFlds) as parcelsCursor:
            for parcelRow in parcelsCursor:
                # print parcelRow
                row[0] = parcelRow[0] # WellID
                row[1] = parcelRow[1] # W_DEPTH
                row[2] = parcelRow[2] # Geothermal
                row[3] = parcelRow[3] # GeoWaterUS
                row[4] = parcelRow[4] # TEMP
                row[5] = parcelRow[5] # TEMP_DATE
                cursor.updateRow(row) # update row

# last parcel selected can be cleared
arcpy.SelectLayerByAttribute_management(parcels, "CLEAR_SELECTION")‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

I'm not sure how fast this approach will be if you have lots of points to update.  I did some testing with a spatial join and field mapping.  I can share that approach, if you're interested.

2Quiker
Frequent Contributor

Randy,

This is very helpful thank you. If you don't mind sharing the spatial join and field mapping code that would be great.

0 Kudos
RandyBurton
MVP Alum

Here's the approach I was experimenting with that uses a spatial join (and field mapping).

# inside ArcMap with layers in a map
points = "GeoWellTemp"
parcels = "WellTaxlots"
# outside ArcMap, use full path to feature layer or shapefile
# points = r'C:\Path\to\GeoWellTemp.shp'
# parcels = r'C:\Path\to\WellTaxlots.shp'

# fields needed for join (assumes field names exist)
pointsFlds = ['OBJECTID'] # alternatively use the FID field
parcelFlds = ['WellID', 'W_DEPTH', 'Geothermal', 'GeoWaterUs', 'TEMP', 'TEMP_DATE']

# set-up the field mappings
fieldmappings = arcpy.FieldMappings()

# fields in target (points) layer are done first
for f in pointsFlds: # field mapping for points layer
    fm = arcpy.FieldMap()
    fm.addInputField(points, f)
    fieldmappings.addFieldMap(fm)

# field mapping for points layer with same field names as parcels
# these fields will be renamed to avoid confusion
for f in parcelFlds: 
    try:
        fm = arcpy.FieldMap() # reset field map
        fm.addInputField(points, f) # checking points for matching field
        outname = fm.outputField
        outname.name = outname.name + "_99" # append _99 to end
        outname.aliasName = outname.aliasName + "_99"
        fm.outputField = outname
        fieldmappings.addFieldMap(fm)
    except:
        pass

# then do fields in join (parcels) layer
for f in parcelFlds: # field mapping for parcels layer
    fm = arcpy.FieldMap()
    fm.addInputField(parcels, f)
    fieldmappings.addFieldMap(fm)

# now the spatial join
join = 'in_memory\sj_test' # location/name of join

if arcpy.Exists(join):  # delete any old joins
    arcpy.Delete_management(join)

# join one to many will create a JOIN_FID which with the TARGET_FID could be used in checking the linking of the features
arcpy.SpatialJoin_analysis(target_features = points, join_features = parcels, out_feature_class = join,
                           join_operation="JOIN_ONE_TO_MANY", join_type="KEEP_ALL", field_mapping = fieldmappings,
                           match_option="INTERSECT", search_radius="", distance_field_name="")

joinFlds = pointsFlds + parcelFlds # points fields has the objectid or fid field 
valueDict = {r[0]:(r[1:]) for r in arcpy.da.SearchCursor(join, joinFlds)}

# update the points feature
with arcpy.da.UpdateCursor(points, joinFlds) as updateRows:
    for updateRow in updateRows:
        keyValue = updateRow[0]
        if keyValue in valueDict:
            for n in range (1,len(joinFlds)):
                updateRow[n] = valueDict[keyValue][n-1]
            updateRows.updateRow(updateRow)

# clean up
if arcpy.Exists(join):
    arcpy.Delete_management(join)

del valueDict‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The field mappings took some experimenting.  I first tried a spatial join and examined the code the tool creates (copy as python snippet).  This provided a way to examine the default field map the tool generates.  The points and parcels layers had a number of fields that used the same name. Normally, the target feature (points, in this case) would use the original field name, and the join feature (parcels) would append an underscore with a number to rename any fields sharing a name in the target feature.  As I wanted the original field names for the join feature, I renamed the target feature's fields when setting up the field mapping.  I think the identical names caused some issues for the spatial join tool, as it would use the values of the target feature when I selected only the fields from the join feature.

I ended up performing three steps to assemble the field map.  First, I selected the object id field from the target feature to be used for linking to the points feature.  The second step was to select and rename any fields in the target feature that had identical names in the join feature that were to be used.  The final step was to select the fields in the join feature that were used to update the points feature.

I used an in_memory location for the spatial join; it needed to be a temporary join anyway.  I used a 'one to many' join to have access to the JOIN_FID field that is created which would assist in linking the two layers together for any debugging necessary.

The last steps were to load the join layer's data into a dictionary and to use it in an update cursor.

I think this approach is a bit faster than the one in my previous post.  But it took much more time to get debugged and working.  Hope this helps.

2Quiker
Frequent Contributor

Thanks for sharing Randy, The code worked great for the data I attached but after hours it took me a while to figure out that my original data had fields that quite didn't match the taxlots fields. So it's my fault for not providing the correct data I was trying to cut down the data size and I apologize. For example, the points have fieldA, field1, field2, field3 and the point taxlots have field_A, field_1, field_2, field_3.  The code works great if I only need those field but like I mentioned I need something that will also include fields that don't match.

So for the fields that don't match I need to transfer

field_A --> fieldA

field_1 --> field1

field_2 --> field1

field_3 --> field1

0 Kudos