Lesson 3 Practice Exercises
Lesson 3 Practice Exercises jed124Lessons 3 and 4 contain practice exercises that are longer than the previous practice exercises and are designed to prepare you specifically for the projects. You should make your best attempt at each practice exercise before looking at the solution. If you get stuck, study the solution until you understand it.
Don't spend so much time on the practice exercises that you neglect Project 3. However, successfully completing the practice exercises will make Project 3 much easier and quicker for you.
Data for Lesson 3 practice exercises
The data for the Lesson 3 practice exercises is very simple and, like some of the Project 2 practice exercise data, was derived from Washington State Department of Transportation datasets. Download the data here.
Using the discussion forums
Using the discussion forums is a great way to work towards figuring out the practice exercises. You are welcome to post blocks of code on the forums relating to these exercises.
When completing the actual Project 3, avoid posting blocks of code longer than a few lines. If you have a question about your Project 3 code, please email the instructor, or you can post general questions to the forums that don't contain more than a few lines of code.
Getting ready
If the practice exercises look daunting to you, you might start by practicing with your cursors a little bit using the sample data:
- Try to loop through the CityBoundaries and print the name of each city.
- Try using an SQL expression with a search cursor to print the OBJECTIDs of all the park and rides in Chelan county (notice there is a County field that you could put in your SQL expression).
- Use an update cursor to find the park and ride with OBJECTID 336. Assign it a ZIP code of 98512.
You can post thoughts on the above challenges on the forums.
Lesson 3 Practice Exercise A
Lesson 3 Practice Exercise A jed124In this practice exercise, you will programmatically select features by location and update a field for the selected features. You'll also use your selection to perform a calculation.
In your Lesson3PracticeExerciseA folder, you have a Washington geodatabase with two feature classes:
- CityBoundaries - Contains polygon boundaries of cities in Washington over 10 square kilometers in size.
- ParkAndRide - Contains point features representing park and ride facilities where commuters can leave their cars.
The objective
You want to find out which cities contain park and ride facilities and what percentage of cities have at least one facility.
- The CityBoundaries feature class has a field "HasParkAndRide," which is set to "False" by default. Your job is to mark this field "True" for every city containing at least one park and ride facility within its boundaries.
- Your script should also calculate the percentage of cities that have a park and ride facility and print this figure for the user.
You do not have to make a script tool for this assignment. You can hard-code the variable values. Try to group the hard-coded string variables at the beginning of the script.
For the purposes of these practice exercises, assume that each point in the ParkAndRide dataset represents one valid park and ride (ignore the value in the TYPE field).
Tips
You can jump into the assignment at this point, or read the following tips to give you some guidance.
- Make two feature layers: "CitiesLayer" and "ParkAndRideLayer."
- Use SelectLayerByLocation with a relationship type of "CONTAINS" to narrow down your cities feature layer list to only the cities that contain park and rides.
- Create an update cursor for your now narrowed-down "CitiesLayer" and loop through each record, setting the HasParkAndRide field to "True."
- To calculate the percentage of cities with park and rides, you'll need to know the total number of cities. You can use the GetCount tool to get a total without writing a loop. Beware that this tool has an unintuitive return value that isn't well documented. The return value is a Result object, and the count value itself can be retrieved through that object by appending [0] (see the second code example from the Help). This will return the count as a string though, so you'll want to cast to an integer before trying to use it in an arithmetic expression.
- Similarly, you may have to play around with your Python math a little to get a nice percentage figure. Don't get too hung up on this part.
Lesson 3 Practice Exercise A Solution
Lesson 3 Practice Exercise A Solution jed124Below 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
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseA\Washington.gdb"
cityBoundariesFC = "CityBoundaries"
parkAndRideFC = "ParkAndRide"
parkAndRideField = "HasParkAndRide" # Name of column with Park & Ride information
citiesWithParkAndRide = 0 # Used for counting cities with Park & Ride
try:
# Narrow down the cities layer to only the cities that contain a park and ride
citiesLayer = arcpy.SelectLayerByLocation_management(cityBoundariesFC, "CONTAINS", parkAndRideFC)
# 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
except:
print ("There was a problem performing the spatial selection or updating the cities feature class")
# Delete the feature layers even if there is an exception (error) raised
finally:
arcpy.Delete_management(citiesLayer)
del row, cursor
# Count the total number of cities (this tool saves you a loop)
numCitiesCount = arcpy.GetCount_management(cityBoundariesFC)
numCities = int(numCitiesCount[0])
# Get the number of cities in the feature layer
#citiesWithParkAndRide = int(citiesLayer[2])
# Calculate the percentage and print it for the user
percentCitiesWithParkAndRide = (citiesWithParkAndRide / numCities) * 100
print (str(round(percentCitiesWithParkAndRide,1)) + " percent of cities have a park and ride.")
Below is a video offering some line-by-line commentary on the structure of this solution:
Video: One Solution to Lesson 3, Practice Exercise A (9:37)
Lesson 3, Practice Exercise A
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.
This solution uses the syntax currently shown in the ArcGIS Pro documentation for Select Layer By Location, in which the FeatureLayer returned by the tool is stored in a variable.
Looking at this solution, I start by importing the arcpy site package. Then I added this line 6 to set the overwriteOutput property to 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.
On line 7, I set the workspace to the Washington file geodatabase that stores the data used in the exercise. That enables me to refer to the input feature classes using only their names, not their full paths. This script doesn’t produce any new output feature classes, but if it did, I could likewise specify them using only their names and not their full paths.
So, in lines 8 & 9, I define variables to refer to the CityBoundaries and ParkAndRide feature classes.
The purpose of this script is to update a field in the CityBoundaries feature class called HasParkAndRide with either a True or False value. So on line 10 I define a parkAndRideField variable to store the name of that field I’m going to be updating.
I also want to report the percentage of cities containing a park and ride at the end of the script through a print statement, so on line 11 I define a variable to store a count of those cities and I initialize that variable to 0.
Now, before I talk about the meat of the script, it’s often helpful to walk through your processing steps manually in Pro. So here I have the ParkAndRides, symbolized by black dots, and the city boundaries that are the orange polygons.
So, I can use the Select By Location tool and specify that I want to select the CityBoundaries features that contain ParkAndRide features. You can see all the city features selected, and I can open the attribute table to see the selected tabular records. I've got 74 of 108 selected. And then over here is the HasParkAndRide field that I would want to update to True for these selected records.
Getting back to my script, I’m going to implement the same Select By Location tool. It’s going to return to me a Feature Layer of cities that contain a ParkAndRide, which I store in my citiesLayer variable. I then open up an update cursor on that Feature Layer. This update cursor will only be able to update the fields that I specify in the second parameter. I could include any number of fields here, as a tuple, but for this scenario I really only need to include one. And the name of that field is stored in my parkAndRideField variable, which I had defined up on line 10.
And so what I have is a cursor that I can iterate through row by row. And I do that using a for loop, which starts on line 19. And on line 21, 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 that tuple in line 18. The first, and in this case the only, field in the tuple is at position 0.
If I was updating three or four fields, I would have row and then the field index position inside brackets. It could be 1, 2, 3, and so on.
After I've made my changes, I need to call updateRow() in order for them to be applied. A mistake that a lot of beginners make is they forget to do this step. So in line 22, I'm calling updateRow().
And in line 23, 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 syntax is a shortcut for saying take the number in this variable and add 1.
I'm using try/except here to provide a custom error message. An important part of this script that goes along with this try/except block is this finally block on lines 28-30. This contains code that I want to run whether or not the try code failed. I don’t want to leave a lock in place on the cities feature class, so I delete the Feature Layer based on it on line 29. I also use a Python del statement to clear the row and cursor objects out of memory, for the same reason I used arcpy’s Delete tool: to make sure no locks are left on the feature classes.
Now, I could have put these statements in the try block, say after the update cursor, and assuming there were no problems in running my code, those clean-up statements would get executed too. However, let’s say there was a problem in my update cursor. If the script crashes there, then my clean-up statements won’t get executed, and if I try to run the script again, I could run into a problem with the cities feature class having a lock on it. Putting the clean-up statements in the finally block ensures they’ll get executed in all situations.
Now that I've done the important stuff, it's time to do some math to figure out the percentage of cities that have a park and ride facility. So in line 33, I'm running the GetCount tool in ArcGIS. When you use the GetCount tool, it’s a bit unintuitive because what it returns isn’t a number, but a result object. What you can do with that object isn’t well documented, so you can just follow on the example here and use [0] to get at the count in string form, and use the int() function to convert that string to an integer.
So what we have in the end, on line 34, is a variable called numCities that is the total number of cities in the entire data set. And then, we've already been keeping the 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 40.
Now, you may have noticed line 37 refers to the citiesWithParkAndRide variable, but is commented out. It turns out that the object returned by SelectLayerByLocation (and SelectLayerByAttribute) isn’t just the Feature Layer of selected cities. Like the GetCount tool we just saw, the Select tools actually return a Result object. This object can be treated kind of like a list. If you want the Feature Layer, you can specify [0] or simply not include a number in brackets at all. But you can also get a count of the selected features by specifying [2]. You can see this in the documentation of the tool, in the Derived Output section.
So, it turns out we don’t really need to implement our own counter inside the loop through the cursor rows. We can get the count through the Result object, and thus we could uncomment line 37 and comment out lines 23 and 11. We decided to include the counter approach in this solution because as a programmer, there are bound to be times where you really do need to maintain your own counter, and we wanted you to see an example of how to do that.
Finally, we can print out the result at the end of the script in line 42. The percentage is a number with several digits after the decimal point, so the print statement first rounds the number to one digit after the decimal and then converts that value to a string so that it can be concatenated with the literal text.
Here is a different solution to Practice Exercise A, which uses the alternate syntax discussed in the lesson:
# This script determines the percentage of cities in the
# state with park and ride facilities
import arcpy
arcpy.env.overwriteOutput = True
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseA\Washington.gdb"
cityBoundariesFC = "CityBoundaries"
parkAndRideFC = "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(parkAndRideFC, "ParkAndRideLayer")
# Make a feature layer of all the cities polygons
arcpy.MakeFeatureLayer_management(cityBoundariesFC, "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
except:
print ("There was a problem performing the spatial selection or updating the cities feature class")
# Delete the feature layers even if there is an exception (error) raised
finally:
arcpy.Delete_management("ParkAndRideLayer")
arcpy.Delete_management("CitiesLayer")
del row, cursor
# Count the total number of cities (this tool saves you a loop)
numCitiesCount = arcpy.GetCount_management(cityBoundariesFC)
numCities = int(numCitiesCount[0])
# Calculate the percentage and print it for the user
percentCitiesWithParkAndRide = (citiesWithParkAndRide / numCities) * 100
print (str(round(percentCitiesWithParkAndRide,1)) + " percent of cities have a park and ride.")
Below is a video offering some line-by-line commentary on the structure of this solution:
Video: Alternate Solution to Lesson 3, Practice Exercise A. (2:47)
This video shows an alternate solution to Lesson 3, Practice Exercise A, which uses the SelectLayerByLocation tool’s older syntax.
This solution starts out much like the first one in terms of the variables defined at the beginning.
Where it differs is that instead of specifying feature classes as inputs to SelectLayerByLocation, it specifies FeatureLayer objects. These objects are created on lines 15 and 18 using the MakeFeatureLayer tool.
We 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, the MakeFeatureLayer tool has two parameters that we supply, first the name of the feature class that's going to act as the source of the features and then the second parameter is a name that we will use to refer to this FeatureLayer throughout the rest of the script. That’s one aspect of this that takes some getting used to: that the object you’re creating gets referred to using a string. In this case, ParkAndRideLayer and CitiesLayer.
Another thing to remember is that you’re not creating a new dataset on disk with this tool. A FeatureLayer is an object that exists only temporarily in memory while the script runs.
So we've got these two feature layers that we can now perform selections on. And in line 25, we're going to use SelectLayerByLocation to select all cities that contain features from the ParkAndRideLayer.
It’s important to note that when using the tool with this approach, the CitiesLayer comes into line 25 referring to all 108 city features and after having the selection applied to it in line 25, it refers to just the 74 features that contain a ParkAndRide.
I then go on to open an update cursor on that FeatureLayer so that I can iterate over those 74 features.
The rest of the script is mostly the same, though there are a couple of differences:
1. I’m not storing the Result object returned by SelectLayerByLocation in this version, so I’m not able to retrieve the count of selected features from that object as we saw in the first solution. I really do need to implement my counter variable in this version. And
2. This version creates 2 FeatureLayers, so when doing the clean-up, I want to delete both of these layers.
Lesson 3 Practice Exercise B
Lesson 3 Practice Exercise B jed124If you look in your Lesson3PracticeExerciseB folder, you'll notice the data is exactly the same as for Practice Exercise A...except, this time the field is "HasTwoParkAndRides."
The objective
In Practice Exercise B, your assignment is to find which cities have at least two park and rides within their boundaries.
- Mark the "HasTwoParkAndRides" field as "True" for all cities that have at least two park and rides within their boundaries.
- Calculate the percentage of cities that have at least two park and rides within their boundaries and print this for the user.
Tips
This simple modification in requirements is a game changer. The following is one way you can approach the task. Notice that it is very different from what you did in Practice Exercise A:
- Create an update cursor for the cities and start a loop that will examine each city.
- Make a feature layer with all the park and ride facilities.
- Make a feature layer for just the current city. You'll have to make an SQL query expression in order to do this. Remember that an UpdateCursor can get values, so you can use it to get the name of the current city.
- Use SelectLayerByLocation to find all the park and rides CONTAINED_BY the current city. Your result will be a narrowed-down park and ride feature layer. This is different from Practice Exercise A where you narrowed down the cities feature layer.
- One approach to determine whether there are at least two park and rides is to run the GetCount tool to find out how many features were selected, then check if the result is 2 or greater. Another approach is to create a search cursor on the selected park and rides and see if you can count at least two cursor advancements.
- Be sure to delete your feature layers before you loop on to the next city. For example: arcpy.Delete_management("ParkAndRideLayer")
- Keep a tally for every row you mark "True" and find the average as you did in Practice Exercise A.
Lesson 3 Practice Exercise B Solution
Lesson 3 Practice Exercise B Solution jed124Below is one possible solution to Practice Exercise B 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.
# This script determines the percentage of cities with two park
# and ride facilities
import arcpy
arcpy.env.overwriteOutput = True
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseB\Washington.gdb"
cityBoundariesFC = "CityBoundaries"
parkAndRideFC = "ParkAndRide"
parkAndRideField = "HasTwoParkAndRides" # Name of column for storing the Park & Ride information
cityIDStringField = "CI_FIPS" # Name of column with city IDs
citiesWithTwoParkAndRides = 0 # Used for counting cities with at least two P & R facilities
numCities = 0 # Used for counting cities in total
# Make an update cursor and loop through each city
with arcpy.da.UpdateCursor(cityBoundariesFC, (cityIDStringField, parkAndRideField)) as cityRows:
for city in cityRows:
# Create a query string for the current city
cityIDString = city[0]
whereClause = cityIDStringField + " = '" + cityIDString + "'"
print("Processing city " + cityIDString)
# Make a feature layer of just the current city polygon
currentCityLayer = arcpy.SelectLayerByAttribute_management(cityBoundariesFC, "NEW_SELECTION", whereClause)
try:
# Narrow down the park and ride layer by selecting only the park and rides in the current city
selectedParkAndRideLayer = arcpy.SelectLayerByLocation_management(parkAndRideFC, "CONTAINED_BY", currentCityLayer)
# Count the number of park and ride facilities selected
numSelectedParkAndRide = int(selectedParkAndRideLayer[2])
# If more than two park and ride facilities found, update the row to TRUE
if numSelectedParkAndRide >= 2:
city[1] = "TRUE"
# Don't forget to call updateRow
cityRows.updateRow(city)
# Add 1 to your tally of cities with two park and rides
citiesWithTwoParkAndRides += 1
numCities += 1
except:
print("Problem determining number of ParkAndRides in " + cityIDString)
finally:
# Clean up feature layers
arcpy.Delete_management(selectedParkAndRideLayer)
arcpy.Delete_management(currentCityLayer)
del city, cityRows
# Calculate and report the number of cities with two park and rides
if numCities != 0:
percentCitiesWithParkAndRide = (citiesWithTwoParkAndRides / numCities) * 100
print (str(round(percentCitiesWithParkAndRide,1)) + " percent of cities have two park and rides.")
else:
print ("Error with input dataset. No cities found.")
The video below offers some line-by-line commentary on the structure of the above solution:
Video: Solution to Lesson 3, Practice Exercise B (8:57)
This video shows one possible solution to Lesson 3, Practice Exercise B, which calculates the number of cities in the data set with at least two park and ride facilities using selections in ArcGIS. It also uses an update cursor to update a field indicating whether the city has at least two park and rides. The beginning of this script is very similar to Practice Exercise A, so I won't spend a lot of time on the beginning. Lines 4 through 10 are pretty familiar from that exercise.
In lines 11 and 12, I'm setting up variables to reference some fields that I'm going to use. Part of the exercise is to update a field called HasTwoParkAndRides to be either True or False. And so, I'm setting a variable to refer to that field.
And in line 12, I set up a variable for the city FIPS code. We're going to be querying each city one by one, and we need some field with unique values for each city. We might use the city name, but that could be dangerous in the event that you have a dataset where two cities happen to have the same name. So we'll use the FIPS code instead.
In lines 13 and 14, I'm setting up some counters that will be used to count the number of cities we run into that have at least two park and rides, and then the number of cities total respectively.
On line 17, I open an update cursor on the cities feature class. In specifying the fields tuple, I’m going to need the FIPS code, so I can create a Feature Layer for the city in each iteration of the loop through the cursor and I need the HasTwoParkAndRides field since that’s the field I want to set to True or False.
So I'm going to loop through each city here and select the number of facilities that occur in each city and count those up. And I'm going to be working with two fields using this cursor. And I pass those in a tuple. That's cityIDStringField and parkAndRideField. Those are the variables that I set up earlier in the script in lines 11 and 12.
It might be helpful at this point to take a look at what we're doing conceptually in ArcGIS Pro. So, the first thing we're going to do is for each city is make an attribute query for that city FIPS. And so, for example, if I were to do this for the city of Seattle, its FIPS code is this long number here.
If I were to run a query for the cities with that FIPS code, I’d end up with a selection containing just one city. The next step then is to select the ParkRide features that intersect the selected features in the cities layer. If that step selects 2 or more features, then I want to record True in my True/False field. If it’s 0 or 1, then I want to record False.
And I want to repeat this for each of the 108 cities. So, this is definitely a good candidate for automation.
So getting back to the script, I’ve called my cursor cityRows and set up a loop in which I called the iterator variable, or loop variable, city.
In line 20, we get the FIPS code for the city that's currently being looked at on the current iteration of the loop. We use 0 in square brackets there because that's the index position of the CityIDStringField that we put in the tuple in line 17. It was the first thing in the tuple, so it has index position 0.
And in line 22, we're setting up a string to store a query for that city FIPS. And so, we need the field name equals the city ID. The city ID has to be in single quotes so we use string concatenation to plug the field name and city ID into a string expression. Note that because this script takes a while to run, I also have a print statement here that includes the city ID to help track the script’s progress as it runs.
On line 25, I use SelectLayerByAttribute to create a Feature Layer representing just the current city. I store a reference to that layer in a variable called currentCityLayer.
I then use that layer in a call to the SelectLayerByLocation tool in which I select the ParkAndRide features that are contained by the feature in currentCityLayer. The result of that selection is stored in the selectedParkAndRideLayer variable.
As discussed in Exercise A, the third item in the Result object is the count of features in the Feature Layer. So on line 32, I get that count using the expression selectedParkAndRideLayer[2].
And then in line 35, I do a check to see if that number is greater than or equal to 2. If it is, then I’m going to assign a value of True to the ParkAndRide field. So that's where I look back at how I defined the cursor and see that the parkAndRideField was the second of the two fields in the fields tuple, so I need to use the expression city[1] to refer to that field. I set that field equal to True and then call updateRow() so that my change gets applied to the data set.
As in exercise A, I want to report the percentage of cities meeting the selection criteria, so I increment my citiesWithTwoParkAndRides variable by 1 when numSelectedParkAndRide is >= 2.
On line 44, I increment the numCities variable by 1. I want to increment this variable on each pass through the loop, not just for cities with 2 or more ParkAndRides, so note that line 44 is indented one less level than line 42 (in other words, so it’s not part of the if block).
Next, note the finally block on lines 47-50. Here we are disposing of the objects held in the currentCityLayer and selectedParkAndRideLayer variables on each iteration of the loop since we no longer need those objects and would like to free up the memory that they take up.
After the loop, we use the Python del statement to kill the objects held in the city and cityRows variables so we avoid retaining unnecessary locks on the data after the script completes.
So, after doing the cleanup, now we're going to calculate the percentage of cities that have at least two park and rides. Now, you can't divide by zero, so we're doing a check in line 55 for that. But if everything checks out OK, which it would unless we had a major problem with our data set, we go ahead in line 56 and divide the number of cities with two park and rides by the total number of cities. And then, we multiply by 100 to get a nice percentage figure, which we then report in the print statement on line 57.
Here is a different solution to Practice Exercise B, which uses the alternate syntax discussed in the lesson.
# This script determines the percentage of cities with two park
# and ride facilities
import arcpy
arcpy.env.overwriteOutput = True
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseB\Washington.gdb"
cityBoundariesFC = "CityBoundaries"
parkAndRideFC = "ParkAndRide"
parkAndRideField = "HasTwoParkAndRides" # Name of column for storing the Park & Ride information
cityIDStringField = "CI_FIPS" # Name of column with city IDs
citiesWithTwoParkAndRides = 0 # Used for counting cities with at least two P & R facilities
numCities = 0 # Used for counting cities in total
# Make a feature layer of all the park and ride facilities
arcpy.MakeFeatureLayer_management(parkAndRideFC, "ParkAndRideLayer")
# Make an update cursor and loop through each city
with arcpy.da.UpdateCursor(cityBoundariesFC, (cityIDStringField, parkAndRideField)) as cityRows:
for city in cityRows:
# Create a query string for the current city
cityIDString = city[0]
whereClause = cityIDStringField + " = '" + cityIDString + "'"
print("Processing city " + cityIDString)
# Make a feature layer of just the current city polygon
arcpy.MakeFeatureLayer_management(cityBoundariesFC, "CurrentCityLayer", whereClause)
try:
# Narrow down the park and ride layer by selecting only the park and rides
# in the current city
arcpy.SelectLayerByLocation_management("ParkAndRideLayer", "CONTAINED_BY", "CurrentCityLayer")
# Count the number of park and ride facilities selected
selectedParkAndRideCount = arcpy.GetCount_management("ParkAndRideLayer")
numSelectedParkAndRide = int(selectedParkAndRideCount[0])
# If more than two park and ride facilities found, update the row to TRUE
if numSelectedParkAndRide >= 2:
city[1] = "TRUE"
# Don't forget to call updateRow
cityRows.updateRow(city)
# Add 1 to your tally of cities with two park and rides
citiesWithTwoParkAndRides += 1
numCities += 1
except:
print("Problem determining number of ParkAndRides in " + cityIDString)
finally:
# Clean up feature layer
arcpy.Delete_management("CurrentCityLayer")
# Clean up feature layer
arcpy.Delete_management("ParkAndRideLayer")
del city, cityRows
# Calculate and report the number of cities with two park and rides
if numCities != 0:
percentCitiesWithParkAndRide = (citiesWithTwoParkAndRides / numCities) * 100
print (str(round(percentCitiesWithParkAndRide,1)) + " percent of cities have two park and rides.")
else:
print ("Error with input dataset. No cities found.")
The video below offers some line-by-line commentary on the structure of the above solution:
Video: Alternate Solution to Lesson 3, Practice Exercise B (2:10)
This video shows an alternate solution to Lesson 3, Practice Exercise B. The general outline of this script is much like the first solution. As we saw with Exercise A, this version of the script uses the MakeFeatureLayer tool to create feature layers used in the call to the SelectLayerByLocation tool rather than feature classes.
Line 17 creates a feature layer called ParkAndRideLayer which references all of the ParkAndRide points in the state.
Then I create an update cursor on the city boundaries feature class, just like in the earlier solution. In this solution, though, note that line 28 creates a feature layer (“CurrentCityLayer”) by passing whereClause as an argument to the MakeFeatureLayer tool. This means the feature layer will refer to a different city on each pass through the loop.
The two feature layers are then passed as inputs to the SelectLayerByLocation tool on line 33, finding the ParkAndRides within the current city and putting that selection into the ParkAndRideLayer.
Then unlike the first solution, which got the number of selected ParkAndRides out of the Result object returned by the SelectLayerbyLocation tool, in this solution, I use the Get Count tool to get the number of ParkAndRides.
Also, note that in the finally block, I use the Delete tool to clean up just "CurrentCityLayer", not "ParkAndRideLayer". That's because the object held in "ParkAndRideLayer" is the same and is needed throughout all iterations of the loop, whereas the object in "CurrentCityLayer" is different on each iteration and is only needed in that iteration. Thus, it's a good idea to dispose of it as part of the loop.
The rest of the script is then pretty much the same.
Testing these scripts on a ~2-year-old Dell laptop running Windows 10 with 16GB of RAM yielded some very different results. In 5 trials of both scripts, the first one needed an average of 240 seconds to complete. The second one (using the older syntax) needed only 83 seconds. This may seem counterintuitive, though it's interesting to note that the newer syntax was the faster performing version of the scripts for the other 3 exercises (times shown in seconds):
| Exercise | Old | New |
|---|---|---|
| A | 0.99 | 0.45 |
| B | 83.67 | 240.33 |
| C | 1.78 | 0.85 |
| D | 1.46 | 0.92 |
You can check the timing of the scripts on your machine by adding the following lines just after the import arcpy statement:
import time process_start_time = time.time()
and this line at the end of the script:
print ("--- %s seconds ---" % (time.time() - process_start_time))If you're interested in learning more about testing your script's performance along with methods for determining how long it takes to execute various parts of your script (e.g., to explore why there is such a difference in performance between the two Exercise B solutions), you should consider enrolling in our GEOG 489 class.
Lesson 3 Practice Exercise C
Lesson 3 Practice Exercise C jed124This practice exercise uses the same starting data as Lesson 3 Practice Exercise A. It is designed to give you practice with extracting data based on an attribute query.
The objective
Select all park and ride facilities with a capacity of more than 500 parking spaces and put them into their own feature class. The capacity of each park and ride is stored in the "Approx_Par" field.
Tips
Use the SelectLayerByAttribute_management tool to perform the selection. You will set up a SQL expression and pass it in as the third parameter for this tool.
Once you make the selection, use the Copy Features tool to create the new feature class. You can pass a feature layer directly into the Copy Features tool, and it will make a new feature class from all features in the selection.
Remember to delete your feature layers when you are done.
Lesson 3 Practice Exercise C Solution
Lesson 3 Practice Exercise C Solution jed124Below is one approach to Lesson 3 Practice Exercise C. The number of spaces to query is stored in a variable at the top of the script, allowing for easy testing with other values.
# Selects park and ride facilities with over a certain number of parking spots
# and exports them to a new feature class using CopyFeatures
import arcpy
parkingSpaces = 500
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseC\Washington.gdb"
arcpy.env.overwriteOutput = True
# Set up the SQL expression to query the parking capacity
parkingQuery = "Approx_Par > " + str(parkingSpaces)
# Select the park and rides that applies the SQL expression
parkAndRideLayer = arcpy.SelectLayerByAttribute_management("ParkAndRide", "NEW_SELECTION", parkingQuery)
# Copy the features to a new feature class and clean up
arcpy.CopyFeatures_management(parkAndRideLayer, "BigParkAndRideFacilities")
arcpy.Delete_management(parkAndRideLayer)The video below offers some line-by-line commentary on the structure of the above solution:
Video: Solution to Lesson 3, Practice Exercise C (3:32)
Solution to Lesson 3, Practice Exercise C
This video describes a solution to Lesson 3, Practice Exercise C, which requires selecting park and ride facilities that meet a certain minimum number of parking spaces, and then, copying them to their own new feature class.
After importing the arcpy site package in line 4, we set up a variable representing that parking threshold. So in this case, we set that equal to 500 parking spaces.
Putting the variable at the top of the script is helpful in case we want to adjust the value and test with other values. It’s easy to find and not buried down later in our code.
In line 7, I'm setting up the arcpy workspace to be equal to my file geodatabase location.
Line 11 is probably the most critical line of this script. It's setting up the SQL query expression to get park and ride facilities that have greater than the number of parking spaces that was specified above on line 6. So if you were to look at this in Pro, where the park and ride facilities are the black dots, we would do a Select By Attributes. And we query on that attribute Approx_Par is greater than 500. And that would give us those large park and rides that are primarily found in the Seattle and Tacoma areas.
Notice that you need to convert that integer value of 500 into a string so that you can concatenate it with the rest of the query expression. However, you don't need to enclose that value in quotes because when you build queries based on numeric values, the number is not put in quotes.
Once you have that query string all set up, you can run SelectLayerByAttribute to get a ParkAndRide Feature Layer with just those selected park and rides that meet the criterion.
So, there are three parameters to pass in here. The first one is the name of the feature class that we're operating on. Because we set up the workspace, we can just put the name of the feature class rather than its full path. We also could have stored the name of the feature class in a variable and plugged that variable in here.
The second parameter is the selection method (whether we want to create a new selection, add to the existing selection, select from the current selection, etc.). Here we want to create a new selection.
And finally, the third parameter is that SQL query expression.
The tool returns to us a Feature Layer containing the selected ParkAndRide features, which we store in the parkAndRideLayer variable.
Once we have that feature layer, we can run the Copy Features tool to make a brand new feature class with just these selected features. So the two parameters here-- the first one is the variable that holds the feature layer that’s the source of the features we want to copy. The second parameter, BigParkAndRideFacilities, is the name of the new feature class that we want to create. And so, once we've done that, we have a new feature class, and we can run the Delete tool to clean up our feature layer. And that's all we need to do in this script.
Below is an alternate approach to the exercise.
# Selects park and ride facilities with over a certain number of parking spots
# and exports them to a new feature class using CopyFeatures
import arcpy
parkingSpaces = 500
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseC\Washington.gdb"
arcpy.env.overwriteOutput = True
# Set up the SQL expression to query the parking capacity
parkingQuery = "Approx_Par > " + str(parkingSpaces)
# Make a feature layer of park and rides that applies the SQL expression
arcpy.MakeFeatureLayer_management("ParkAndRide", "ParkAndRideLayer", parkingQuery)
# Copy the features to a new feature class and clean up
arcpy.CopyFeatures_management("ParkAndRideLayer", "BigParkAndRideFacilities")
arcpy.Delete_management("ParkAndRideLayer")The video below offers some line-by-line commentary on the structure of the above solution:
Video: Alternate Solution to Lesson 3, Practice Exercise C (1:18)
This video describes an alternate solution to Lesson 3, Practice Exercise C. This solution differs from the first one starting on line 14. Instead of using SelectLayerByAttribute and storing its returned Feature Layer in a variable, this version of the script uses the MakeFeatureLayer tool to create the Feature Layer that can be referred to later using the string “ParkAndRideLayer”.
Looking closely at the parameters supplied in line 14, it’s important to note that the first parameter, “ParkAndRide” is specifying the name of a feature class. Found where? Found in the workspace, which we set to the Washington geodatabase on line 7.
The second parameter is also a string, but it’s just the name that we’re giving to the temporary, in-memory feature layer we’re creating.
So in this version of the script, when we want to write the selected features to disk, we plug that named feature layer into the CopyFeatures statement rather than a variable.
And likewise, when we want to clean up the feature layer, we again specify that same name, as a string, rather than a variable.
Lesson 3 Practice Exercise D
Lesson 3 Practice Exercise D jed124This practice exercise requires applying both an attribute selection and a spatial selection. It is directly applicable to Project 3 in many ways. The data is the same that you used in exercises A and C.
The objective
Write a script that selects all the park and ride facilities in a given city and saves them out to a new feature class. You can test with the city of 'Federal Way'.
Tips
Start by making a feature layer from the CityBoundaries feature class that contains just the city in question. You'll then need to make a feature layer from the park and ride feature class and perform a spatial selection on it using the "WITHIN" operation. Then use the Copy Features tool as in the previous lesson to move the selected park and ride facilities into their own feature class.
Lesson 3 Practice Exercise D Solution
Lesson 3 Practice Exercise D Solution jed124Below is one possible approach to Lesson 3 Practice Exercise D. Notice that the city name is stored near the top of the script in a variable so that it can be tested with other values.
# Selects park and ride facilities in a given target city and
# exports them to a new feature class
import arcpy
targetCity = "Federal Way" # Name of target city
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseD\Washington.gdb"
arcpy.env.overwriteOutput = True
parkAndRideFC = "ParkAndRide" # Name of P & R feature class
citiesFC = "CityBoundaries" # Name of city feature class
# Set up the SQL expression of the query for the target city
cityQuery = "NAME = '" + targetCity + "'"
# Select just the target city
cityLayer = arcpy.SelectLayerByAttribute_management(citiesFC, "NEW_SELECTION", cityQuery)
# Select all park and rides in the target city
parkAndRideLayer = arcpy.SelectLayerByLocation_management(parkAndRideFC, "CONTAINED_BY", cityLayer)
# Copy the features to a new feature class and clean up
arcpy.CopyFeatures_management(parkAndRideLayer, "TargetParkAndRideFacilities")
arcpy.Delete_management(parkAndRideLayer)
arcpy.Delete_management(cityLayer)See the video below for some line-by-line commentary on the above solution:
Solution to Lesson 3, Practice Exercise D (4:06)
In this video, I'll be explaining one solution to Lesson 3 Practice Exercise D, in which we need to select park and ride facilities in a given target city and export them to a new feature class.
This solution will have a lot of elements that were used in Exercises B and C. These patterns should start to look familiar to you.
In line 4, I import the arcpy site package, and then in line 6, I set up a variable to represent the city on which I want to query for park and rides. I'm going to be doing an attribute query for this city, and then I'm going to follow it up with a spatial query to just get the park and rides that fall within that selected city. And putting line 6 at the top like this allows me to change the city, so I can test with different values without hunting through my code and finding where that is.
In line 7, I set up the workspace, which is going to be my file geodatabase. I'll be working with 3 different feature classes, all in the same geodatabase, so setting the workspace like this makes it easier for me to refer to those feature classes.
In lines 8 and 9, I set up variables to store the names of the two input feature classes.
The rest of the code, some of it could go within try, except, and finally blocks. For simplicity, I've left those out here, although for code quality, you're asked to include those in appropriate places in your project submissions.
What I do next on line 13 is set up the SQL query string to get just the city of Federal Way on its own. So, I'm doing this attribute query here, similar to what was done in Exercise B. I'm querying on the NAME field for the city. The expression as a whole being a string needs to be in quotes. And I use string concatenation to plug the name of the target city inside single quotes.
Once I have my query string, on line 16 I can perform a selection in which I end up with a feature layer of just that target city.
And by now, the use of SelectLayerByAttribute should be familiar to you. The first parameter is the name of the feature class that I'm querying. And remember, in line 10, I set up a variable for that. The second parameter is the selection method I want to use. And then the third parameter is the SQL query expression, and it's what will narrow down the cities to, in this case, just the city of Federal Way.
Now that I have a feature layer representing Federal Way, I can now do a Select Layer by Location so that I can get just the park and rides that fall within that city. So, I pass in the ParkAndRide feature class, the type of spatial relationship which I'm using, which is Contained By, which you've seen in previous practice exercises.
And then it’s the city layer that I want to use to do the selection. So, that's the third parameter. Once I have the selection made, then I can copy those selected features into a new feature class. And just like you saw in Practice Exercise C, I'm using the Copy Features tool, and I specify my ParkAndRide feature layer as the source of the features that I want to copy.
And then the second parameter is the name of the new feature class that will be created. Because I only specified a name and not a path, the new feature class is going to be saved in the workspace, which is the Washington file geodatabase. And I'm going to call that feature class TargetParkAndRideFacilities.
Now at the end of your code, preferably within a Finally block or somewhere at the end, you're going to delete the feature layers to clean them up. And in this case, we have two feature layers to clean up.
And that's all it takes to complete this exercise.
Here is an alternate solution for this exercise:
# Selects park and ride facilities in a given target city and
# exports them to a new feature class
import arcpy
targetCity = "Federal Way" # Name of target city
arcpy.env.workspace = r"C:\PSU\geog485\L3\PracticeExerciseD\Washington.gdb"
arcpy.env.overwriteOutput = True
parkAndRideFC = "ParkAndRide" # Name of P & R feature class
citiesFC = "CityBoundaries" # Name of city feature class
# Set up the SQL expression of the query for the target city
cityQuery = "NAME = '" + targetCity + "'"
# Make feature layers for the target city and park and rides
arcpy.MakeFeatureLayer_management(citiesFC, "CityLayer", cityQuery)
arcpy.MakeFeatureLayer_management(parkAndRideFC, "ParkAndRideLayer")
# Select all park and rides in the target city
arcpy.SelectLayerByLocation_management("ParkAndRideLayer", "CONTAINED_BY", "CityLayer")
# Copy the features to a new feature class and clean up
arcpy.CopyFeatures_management("ParkAndRideLayer", "TargetParkAndRideFacilities")
arcpy.Delete_management("ParkAndRideLayer")
arcpy.Delete_management("CityLayer")See the video below for some line-by-line commentary on the above solution:
Alternate Solution to Lesson 3, Practice Exercise D (1:36)
This video shows an alternate solution to Lesson 3 Practice Exercise D.
Everything is the same in this version down until line 16. On that line, I use MakeFeatureLayer to create a feature layer containing just the target city. I give that feature layer object a name of “CityLayer”.
Then on line 17, I create another FeatureLayer, this one of all the park and ride facilities. In this line, I only pass in two parameters because I want all of the park and rides. So, I'm not going to pass in any type of query string here.
With those two layers created, I can now do a Select Layer by Location so that I can get just the park and rides that fall within the selected city. So in this use of the tool, the inputs I’m supplying are strings, that are the names that I assigned to the Feature Layer objects. That’s instead of a feature layer held in a variable and the name of a feature class, as I did it in the first solution.
So coming into line 20, “ParkAndRideLayer” refers to all 365 ParkAndRide features. After line 20, it refers to just the 8 features that fall within the boundaries of Federal Way.
I then insert the name of that layer into a call to the CopyFeatures tool and then finish up my cleaning up the two feature layers.