5

I have two shapefiles: "broken_cities" and "us_cities." "us_cities" contains point data representing cities in the US, and the x and y coordinates are correct. "broken_cities" contains point data representing 10 cities in the US whose X and Y coordinates are incorrect. There is no X and Y coordinate fields in the attribute table of either.

The goal is to update the broken cities X and Y coordinates using the correct coordinates from "us_cities." I know I should use an update cursor, but I don't know how to combine the search cursor and the update cursor. I am aware that my code does not work at all because I'm not sure of the logic of the workflow.

import arcpy

#set workspace
arcpy.env.workspace= r"Y:\Personal\jsmith\EGData.gdb"

fields=["OID@", "SHAPE@X", "SHAPE@Y"] #tokens to access

#create search cursors to look at values
broken_scur=arcpy.da.SearchCursor("cities_broken",fields)
cities_scur=arcpy.da.SearchCursor("us_cities", fields)

#OIDs of the 10 broken cities
broken_city_OIDs=[66, 68, 61, 74, 86, 80, 116, 94, 96, 97]

for row in cities_scur:
    for row1 in broken_scur:
        if row1[0]==broken_city_OIDs:
            row1[1]==row[1]
            row1[2]==row[2]
1
  • 1
    Don't trust the OIDs in a shapefile, but considering your workspace is a geodatabase they're not shapefiles, they're file geodatabase feature classes (yes, it makes a BIG difference). If you already have a shapefile that is correct why are you modifying a broken one? Is there any matching field? Like name or zipcode? You are using a seach cursor, which is read-only, so you can't do row1[1]=row[1] (note not == here, that's conditional). Commented Nov 29, 2017 at 3:01

2 Answers 2

4

If you want to search all of values in a layer with SearchCursor and use the result for updatecursor, use dictionaries instead of nested cursors.

OID is not a good reference to compare two layers. I gave an example and compare city names.You maybe want to use another field (better field).

If you want to update some of features you know them, use where_clause(An optional expression that limits the records returned). For more information about building a where clause, see this link.

There is a good tutorial about using where clause in SearchCursors: Retrieving records using an attribute query.

If you want to learn more about python dictionaries, This tutorial is a good source.

import arcpy
sourceFieldsList = ["CITY_NAME", "SHAPE@X", "SHAPE@Y"]
# Use list comprehension to build a dictionary from a da SearchCursor
valueDict = {r[0]: (r[1:]) for r in arcpy.da.SearchCursor("D://test.gdb//cities", sourceFieldsList)}

updateFC = "cities_1"

updateFieldsList = ["CITY_NAME", "SHAPE@X", "SHAPE@Y"]

with arcpy.da.UpdateCursor("D://test.gdb//cities_1", updateFieldsList) as updateRows:
    for updateRow in updateRows:
        # store the Join value of the row being updated in a keyValue variable
        keyValue = updateRow[0]
        print keyValue
        # verify that the keyValue is in the Dictionary
        if keyValue in valueDict:
            # print keyValue
            # transfer the value stored under the keyValue from the dictionary to the updated field.
            updateRow[1] = valueDict[keyValue][0]
            updateRow[2] = valueDict[keyValue][1]
            updateRows.updateRow(updateRow)
2
  • 1
    Clean code--well done! I like the heavy comments describing the workflow.
    – Aaron
    Commented Nov 29, 2017 at 4:07
  • I totally agree, OID is not a good reference to compare two datasets by, especially if they're shapefiles. You have to watch your loading, if for example the first dataset is an enterprise (or file) geodatabase of all land parcels for a state then arbitrarily loading them can lead to a memory overflow.. you've only got 4 GiB to play with, less when you consider that python.exe and arcpy have to reside in that space also. Commented Nov 29, 2017 at 4:17
3

I would do it this way:

import arcpy
arcpy.env.workspace= r"Y:\Personal\jsmith\EGData.gdb"

fields=["OID@", "SHAPE@XY"] #tokens to access
D = arcpy.Describe("us_cities") # describe the US Cities to get the OID field name

with arcpy.da.UpdateCursor("cities_broken",fields) as UCur:
    # get ready to update each row
    for URow in UCur:
        # create a definition query
        dQ = "{} = {}".format(D.OIDFieldName,URow[0])
        with arcpy.da.SearchCursor("us_cities",fields,dQ) as SCur:
            # get the matching feature (if any)
            for SRow in SCur:
                # should only do this once or not at all if the OID isn't found
                URow[1] = SRow[1]
                UCur.updateRow(URow) # Important!! or the changes aren't saved

Your first cursor is an update cursor but the 2nd is a search cursor as you're only looking, not changing any values. Use the power of a definition query (where clause) in the docs to limit the matching row.. that is assuming the OID in broken cities matches the OID in US Cities.

I'm using "SHAPE@XY" but you could also just use "SHAPE@" to copy the geometry; SHAPE@XY is a tuple of (X,Y) but SHAPE@ is a geometry object, still copyable but only to a geometry field.

It's also important that you pick up the difference between assignment (=) and conditional (==). When you say X = 1 now the value of X is equal to 1, but if you say X == 1 then X does not change, it will either be True or False depending on the value of X; python will skip right over this without giving an error and you'll end up going nuts trying to find why X is none when you go to use it later.

By popular demand, a dictionary solution

import arcpy
arcpy.env.workspace= r"Y:\Personal\jsmith\EGData.gdb"

fields=["OID@", "SHAPE@XY"] #tokens to access

broken_city_OIDs=[66, 68, 61, 74, 86, 80, 116, 94, 96, 97]

Dict = {} # new empty dictionary
with arcpy.da.SearchCursor("us_cities",fields) as SCur:
    for SRow in SCur:
        if SRow[0] in broken_city_OIDs:
            Dict[SRow[0]] = SRow[1] # put the geometry tuple in the dictionary indexed by the OID

with arcpy.da.UpdateCursor("cities_broken",fields) as UCur:
    # get ready to update each row
    for URow in UCur:
        if URow[0] in broken_city_OIDs:
            # don't just assume that the key is in the dictionary, check!
            if URow[0] in Dict:
                URow[1]=Dict[URow[0]] # copy the dictionary geometry tuple to the updated row
                UCur.updateRow(URow)  # Important!! or the changes aren't saved

This is far more efficient than the nested loop. The process has two iterations

  1. Search through the match 'to' data and copy the necessary values into the dictionary.
  2. Update the required features with the in memory copy of the values stored in the dictionary.

Dictionaries are a little more advanced, they are like a list but store the values in a Key:Value pair, where a list is addressed from 0 numerically a dictionary can store keys of any type, you can have keys of strings and numbers in the same dictionary.. the only thing to watch for is that you don't inadvertently overwrite an existing value for a given key, a key must be unique.

10
  • 1
    Nested cursor is not a good solution.It decreases performance. Instead of Searchcursor use Dictionary.
    – BBG_GIS
    Commented Nov 29, 2017 at 3:17
  • 3
    In this case a nested cursor is a simple but effective solution. If the OP is having trouble with assignment/condition equals then introducing a dictionary method is not going to be instructive, just confusing, @wetland. Considering there's less than a dozen features I doubt performance will be an issue in this case, but you're right, if this method was used on a much larger dataset then it would be inefficient. Commented Nov 29, 2017 at 3:21
  • 1
    The "broken_cities" layer contains more than just the 10 cities with incorrect X and Y coordinates. It contains all of the same points as "us_cities," but 10 of these cities have the wrong X and Y coordinates. And yes, I agree that modifying a broken one makes no sense if one already works, but it's to practice using cursors. Commented Nov 29, 2017 at 3:32
  • 1
    @BeginnerProgrammer, if it's just the 10 that have incorrect coordinates, then I'd consider using a definition query in the updatecursor as well, to limit the number of features being traversed. Of course, that means, you'll need an attribute in your cities_broken feature class to identify them.
    – Fezter
    Commented Nov 29, 2017 at 3:42
  • 2
    @Aaron, just because you asked nicely I've put one in. Dictionaries are very handy but can be a little much to deal with for a beginner with python. Commented Nov 29, 2017 at 4:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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