GEOG 485:
GIS Programming and Automation

Lesson 3 Practice Exercise A Solution

PrintPrint

Below is one possible solution to Practice Exercise A with comments to explain what is going on. If you find a more efficient way to code a solution, please share it through the discussion forums. Please note that in order to make the changes to "CitiesLayer" permanent, you have to write the layer back to disk using the arcpy.CopyFeatures_management(...) function. This is not shown in the solution here.

# This script determines the percentage of cities in the
#  state with park and ride facilities

import arcpy
arcpy.env.overwriteOutput = True

cityBoundaries = "D:\\Data\\Geog485\\Lesson3PracticeExerciseA\\Washington.gdb\\CityBoundaries"
parkAndRide = "D:\\Data\\Geog485\\Lesson3PracticeExerciseA\\Washington.gdb\\ParkAndRide"
parkAndRideField = "HasParkAndRide"   # Name of column with Park & Ride information
citiesWithParkAndRide = 0             # Used for counting cities with Park & Ride

try:
    # Make a feature layer of all the park and ride facilities
    arcpy.MakeFeatureLayer_management(parkAndRide, "ParkAndRideLayer")

    # Make a feature layer of all the cities polygons   
    arcpy.MakeFeatureLayer_management(cityBoundaries, "CitiesLayer")

except:
    print "Could not create feature layers"

try:      
    # Narrow down the cities layer to only the cities that contain a park and ride
    arcpy.SelectLayerByLocation_management("CitiesLayer", "CONTAINS", "ParkAndRideLayer")

    # Create an update cursor and loop through the selected records
    with arcpy.da.UpdateCursor("CitiesLayer", (parkAndRideField,)) as cursor:
        for row in cursor:
            # Set the park and ride field to TRUE and keep a tally
            row[0] = "True"
            cursor.updateRow(row)
            citiesWithParkAndRide +=1
   
# Delete the feature layers even if there is an exception (error) raised
finally: 
    arcpy.Delete_management("ParkAndRideLayer")
    arcpy.Delete_management("CitiesLayer")
    
# Count the total number of cities (this tool saves you a loop)
numCitiesCount = arcpy.GetCount_management(cityBoundaries)
numCities = int(numCitiesCount.getOutput(0))

# Calculate the percentage and print it for the user
percentCitiesWithParkAndRide = ((1.0 * citiesWithParkAndRide) / numCities) * 100

print str(percentCitiesWithParkAndRide) + " percent of cities have a park and ride."

Below is a video offering some line-by-line commentary on the structure of this solution:

Click for a transcript of "3A" video.

PRESENTER: This video shows one solution to Lesson 3, Practice Exercise A, which determines the percentage of cities in the State of Washington that have park and ride facilities. The script begins by importing the arcpy site package.

And then I put this line 5 to overwrite Output equals True, which is useful when you're writing scripts like these where you'll be doing some testing and you'll often want to overwrite the output files. Otherwise ArcGIS will return an error message saying that it cannot overwrite an output that already exists, so that's a tip to take from this code.

In lines 7 and 8 I have string variables that I'm creating that have the paths to the feature classes I'm going to work with, the cityBoundaries feature class and the park AndRide feature class, both from the Washington file geo database. I'm also going to be updating a field in the cityBoundaries feature class called HasParkAndRide. I'll be setting that to true or false. And so I make a variable for that in line 9. And you'll see that used later.

And then in line 10 I'm starting a counter that I'm going to use in my code to count the number of cities with a park and ride. And I start that off at zero.

In line 12, I move into it a try-except block of code. In this solution I show you some examples of how you might do error handling and return messages. Usually if you leave these out, your script will return nice error messages to you anyways, that you can use to figure out what line has crashed. However, if you distribute your scripts to an end user, they will want to see nicer messages.

So the try and except blocks can help that. You can put your own custom messages in like I did in line 20. But for now we have a try block.

And we're going to try to make two feature layers. We'll make a feature layer that has all the park and rides, and a feature layer that has all of the city boundaries. And for both of these, in line 14 and 17, the MakeFeatureLayer tool has two parameters that we supply, the name of the feature class that's going to act as the feature layer and then the second parameter is a name that we will use to refer to this feature layer throughout this script and only this script, so this is a name that is in the computer's memory. For example, in line '14 for the park and ride layer, we just call it ParkAndRideLayer. We can name that part of it anything we want.

So we've got these two feature layers that we can now perform selections on. And in line 24 we're going to do a location selection to select all cities that contain features from the park and ride layer. If we were to do this in ArcMap, which sometimes it's helpful to do to see what's going on, here I have the park and rides, symbolized by black dots, and the city boundaries that are grey lines with orange fill.

And in ArcMap I can do the same thing where I select by location. And essentially we're selecting features from the city boundaries that completely contain the layer of park and rides. You can see all those elements there. And then this is the selection of cities that do have a park and ride facility. So line 24 is showing how you would do that with code.

And once you've got that selection, then you can then do things with it. So for example in ArcMap if I wanted to work with these cities, I could open the attribute table. And I see that some are selected. And then I could just work on those selected rows to do something.

And so we're going to do that in Python by opening an update cursor. And the cursor is just going to work on that narrowed-down layer of cities. So it's important to note that in line 24 you started with the cities layer that had all the cities. And after you run line 24, that layer is only going to contain a selected subset of those cities. And that's what is going to be applied in your update cursor.

So this update cursor will not update everything. It will just update the cities that are currently selected. And it will only be able to update the fields that you specify in the second parameter. So where I've put parkAndRideField and then a comma, that is a tuple, a Python tuple, with one item in it that represents the field I'm going to edit. And that is the HasParkAndRide field. So cast your minds back up here with line number 9. That's where I created that variable that I'm passing in down in line 27.

And so what I have is a cursor that can then go row by row. And I do that using a for loop, which is in line 28. And in line 30, I take that HasParkAndRide field and I set it to true.

Now why do I say 0 there in square brackets? 0 is the index position of the field that I passed in in that tuple in line 27. So in line 27 I passed in a tuple with just one field, ParkAndRideField.

And because it has one thing in it, that's at index position 0. And that's why I say row 0. If I was updating three or four fields, I would have row and then the field index position in a bracket. It could be 1, 2, 3, and so on.

After I've made those changes, I need to call updateRow in order for them to be applied. And this is a mistake that a lot of beginners make. So in line 31, I'm calling updateRow.

And in line 32, I'm incrementing the counter that I'm keeping of the number of cities with a park and ride facility found. So I just add 1 to the counter. The plus equals 1 is a shortcut for saying take this number and add 1.

After looping through all those things, in line 35 I start a finally block, which would run whether or not the above code failed. And it cleans up those feature layers so they're not stuck in the computer's memory. I could put those in an except block perhaps, but I wanted to put them in finally because those need to be cleaned up whether or not the script crashes. So that's what's happening in line 36 and 37.

Now that I've done all those things, it's time to do some math to figure out the percentage of cities that have a park and ride facility. So in line 40 I'm running the GetCount tool in ArcGIS. You can run this tool directly on a feature class. So here I don't pass in a feature layer. I just pass in the string path to a feature class.

Notice in 40 I referenced that cityBoundaries variable. That's a variable that I created up in line 7. And it just has the path to the data.

When you use the GetCount tool, you have to also call a getOutput method to get the actual result and convert it to an integer. Now this is a little complex. Don't worry if you don't understand it all. But just follow the examples that are in this code or also the ArcGIS Help code for the GetCount tool.

So what we have in the end, in line 41, is a variable called numCities that is the total number of cities in the entire data set. And then we've already been keeping this counter of cities that have a park and ride. So to find the percentage, all we need to do is divide one by the other. And that's what's going on in line 44.

Now, there's a little extra things in the equation here. We take 1.0 and multiply it by the number of cities with a park and ride, because we want to do some decimal division. If we didn't do that, it would perform integer division by default. And we wouldn't get a nice percentage figure. So we take the cities with park and ride, divide it by the number of cities, and then we multiply it by 100 to get a nice interpretable percentage figure. And then we can print out the result at the end of the script in line 46.