The links below provide an outline of the material for this lesson. Be sure to carefully read through the entire lesson before returning to Canvas to submit your assignments.
In Lesson 1, you received an introduction to Python. Lesson 2 builds on that experience, diving into Python fundamentals. Many of the things you'll learn are common to programming in other languages. If you already have coding experience, this lesson may contain some review.
This lesson has a relatively large amount of reading from the course materials, the Zandbergen text, and the ArcGIS help. I believe you will get a better understanding of the Python concepts as they are explained and demonstrated from several different perspectives. Whenever the examples use the IPython console, I strongly suggest that you type in the code yourself as you follow the examples. This can take some time, but you'll be amazed at how much more information you retain if you try the examples yourself instead of just reading them.
At the end of the lesson, you'll be required to write a Python script that puts together many of the things you've learned. This will go much faster if you've taken the time to read all the required text and work through the examples.
Lesson 2 covers Python fundamentals (many of which are common to other programming languages) and gives you a chance to practice these in a project. To complete this lesson, you are required to do the following:
Do items 1 - 3 (including any of the practice exercises you want to attempt) during the first week of the lesson. You will need the second week to concentrate on the project and quiz.
By the end of this lesson, you should:
At this point, you've learned most of what you need to know about ModelBuilder, and this may be enough to address many of the GIS tasks that you face in your work. However, as useful as ModelBuilder is, you'll find that sometimes you need Python to build extra intelligence into your geoprocessing. For example, you may need to construct complex query strings, or employ conditional logic. You may need to read, or parse, varying types of user input before you can send it to a tool as a parameter. Or you might need to do complex looping that, at some threshold, probably becomes easier to write in Python than to figure out with ModelBuilder.
In Lesson 1, you saw your first Python scripts and were introduced to the basics, such as importing modules, using arcpy, working with properties and methods, and indenting your code in try/catch blocks. In the following sections, you'll learn about more Python programming fundamentals such as working with lists, looping, if/then decision structures, manipulating strings, and casting variables.
Although this might not be the most thrilling section of the course, it's probably the most important section for you to spend time understanding and experimenting with on your own, especially if you are new to programming.
Programming is similar to playing sports: if you take time to practice the fundamentals, you'll have an easier time when you need to put all your skills together. For example, think about the things you need to learn in order to play basketball. A disciplined basketball player practices dribbling, passing, long-range shooting, layup shots, free throws, defense, and other skills. If you practice each of these fundamentals well individually, you'll be able to put them together when it's time to play a full game.
Learning a programming language is the same way. When faced with a problem, you'll be forced to draw on your fundamental skills to come up with a workable plan. You may need to include a loop in your program, store items in a list, or make the program do one of four different things based on certain user input. If you know how to do each of these things individually, you'll be able to fit the pieces together, even if the required task seems daunting.
Take time to make sure you understand what's happening in each line of the code examples, and if you run into a question, please jot it down and post to the forums.
In Lesson 1, you learned about some common data types in Python, such as strings and integers. Sometimes you need a type that can store multiple related values together. Python offers several ways of doing this, and the first one we'll learn about is the list.
Here's a simple example of a list. You can type this in the PyScripter Python Interpreter to follow along:
>>> suits = ['Spades', 'Clubs', 'Diamonds', 'Hearts']
This list named 'suits' stores four related string values representing the suits in a deck of cards. In many programming languages, storing a group of objects in sequence like this is done with arrays. While the Python list could be thought of as an array, it's a little more flexible than the typical array in other programming languages. This is because you're allowed to put multiple data types into one list.
For example, suppose we wanted to make a list for the card values you could draw. The list might look like this:
>>> values = ['Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King']
Notice that you just mixed string and integer values in the list. Python doesn't care. However, each item in the list still has an index, meaning an integer that denotes each item's place in the list. The list starts with index 0 and for each item in the list, the index increments by one. Try this:
>>> print (suits[0]) Spades >>> print (values[12]) King
In the above lines, you just requested the item with index 0 in the suits list and got 'Spades'. Similarly, you requested the item with index 12 in the values list and got 'King'.
It may take some practice initially to remember that your lists start with a 0 index. Testing your scripts can help you avoid off-by-one errors that might result from forgetting that lists are zero-indexed. For example, you might set up a script to draw 100 random cards and print the values. If none of them is an Ace, you've probably stacked the deck against yourself by making the indices begin at 1.
Remember you learned that everything is an object in Python? That applies to lists too. In fact, lists have a lot of useful methods that you can use to change the order of the items, insert items, sort the list, and so on. Try this:
>>> suits = ['Spades', 'Clubs', 'Diamonds', 'Hearts'] >>> suits.sort() >>> print (suits) ['Clubs', 'Diamonds', 'Hearts', 'Spades']
Notice that the items in the list are now in alphabetical order. The sort() method allowed you to do something in one line of code that would have otherwise taken many lines. Another helpful method like this is reverse(), which allows you to sort a list in reverse alphabetical order:
>>> suits.reverse() >>> print (suits) ['Spades', 'Hearts', 'Diamonds', 'Clubs']
Before you attempt to write list-manipulation code, check your textbook or the Python list reference documentation [2] to see if there's an existing method that might simplify your work.
What happens when you want to combine two lists? Type this in the PyScripter Interpreter:
>>> listOne = [101,102,103] >>> listTwo = [104,105,106] >>> listThree = listOne + listTwo >>> print (listThree) [101, 102, 103, 104, 105, 106]
Notice that you did not get [205,207,209]; rather, Python treats the addition as appending listTwo to listOne. Next, try these other ways of adding items to the list:
>>> listThree += [107] >>> print (listThree) [101, 102, 103, 104, 105, 106, 107] >>> listThree.append(108) >>> print (listThree) [101, 102, 103, 104, 105, 106, 107, 108]
To put an item at the end of the list, you can either add a one-item list (how we added 107 to the list) or use the append() method on the list (how we added 108 to the list). Notice that listThree += [107] is a shortened form of saying listThree = listThree + [107].
If you need to insert some items in the middle of the list, you can use the insert() method:
>>> listThree.insert(4, 999) >>> print (listThree) [101, 102, 103, 104, 999, 105, 106, 107, 108]
Notice that the insert() method above took two parameters. You might have even noticed a tooltip that shows you what the parameters mean.
The first parameter is the index position that the new item will take. This method call inserts 999 between 104 and 105. Now 999 is at index 4.
Sometimes you'll need to find out how many items are in a list, particularly when looping. Here's how you can get the length of a list:
>>> myList = [4,9,12,3,56,133,27,3] >>> print (len(myList)) 8
Notice that len() gives you the exact number of items in the list. To get the index of the final item, you would need to use len(myList) - 1. Again, this distinction can lead to off-by-one errors if you're not careful.
Lists are not the only way to store ordered collections of items in Python; you can also use tuples and dictionaries. Tuples are like lists, but you can't change the objects inside a tuple over time. In some cases, a tuple might actually be a better structure for storing values like the suits in a deck of cards because this is a fixed list that you wouldn't want your program to change by accident.
Dictionaries differ from lists in that items are not indexed; instead, each item is stored with a key value which can be used to retrieve the item. We'll use dictionaries later in the course, and your reading assignment for this lesson covers dictionary basics. The best way to understand how dictionaries work is to play with some of the textbook examples in the PyScripter Python Interpreter (see Zandbergen 4.17 (new editiion) or 6.8 (old edition)).
A loop is a section of code that repeats an action. Remember, the power of scripting (and computing in general) is the ability to quickly repeat a task that might be time-consuming or error-prone for a human. Looping is how you repeat tasks with code; whether its reading a file, searching for a value, or performing the same action on each item in a list.
A for loop does something with each item in a list. Type this in the PyScripter Python Interpreter to see how a simple for loop works:
>>> for name in ["Carter", "Reagan", "Bush"]: print (name + " was a U.S. president.")
After typing this, you'll have to hit Enter twice in a row to tell PyScripter that you are done working on the loop and that the loop should be executed. You should see:
Carter was a U.S. president Reagan was a U.S. president Bush was a U.S. president
Notice a couple of important things about the loop above. First, you declared a new variable, "name," to represent each item in the list as you iterated through. This is okay to do; in fact, it's expected that you'll do this at the beginning of the for loop.
The second thing to notice is that after the condition, or the first line of the loop, you typed a colon (:), then started indenting subsequent lines. Some programming languages require you to type some kind of special line or character at the end of the loop (for example, "Next" in Visual Basic, or "}" in JavaScript), but Python just looks for the place where you stop indenting. By pressing Enter twice, you told Python to stop indenting and that you were ready to run the loop.
for loops can also work with lists of numbers. Try this one in the PyScripter Python Interpreter:
>>> x = 2 >>> multipliers = [1,2,3,4] >>> for num in multipliers: print (x * num) 2 4 6 8
In the loop above, you multiplied each item in the list by 2. Notice that you can set up your list before you start coding the loop.
You could have also done the following with the same result:
>>> multipliers = [1,2,3,4] >>> for num in multipliers: x = 2 print (x * num)
The above code, however, is less efficient than what we did initially. Can you see why? This time, you are declaring and setting the variable x=2 inside the loop. The Python interpreter will now have to read and execute that line of code four times instead of one. You might think this is a trivial amount of work, but if your list contained thousands or millions of items, the difference in execution time would become noticeable. Declaring and setting variables outside a loop, whenever possible, is a best practice in programming.
While we're on the subject, what would you do if you wanted to multiply 2 by every number from 1 to 1000? It would definitely be too much typing to manually set up a multipliers list, as in the previous example. In this case, you can use Python's built-in range function. Try this:
>>> x = 2 >>> for num in range(1,1001): print (x * num)
The range function is your way of telling Python, "Start here and stop there." We used 1001 because the loop stops one item before the function's second argument (the arguments are the values you put in parentheses to tell the function how to run). If you need the function to multiply by 0 at the beginning as well, you could even get away with using one argument:
>>> x = 2 >>> for num in range(1001): print (x * num)
The range function has many interesting uses, which are detailed in this section's reading assignment in Zandbergen.
A while loop executes until some condition is met. Here's how to code our example above using a while loop:
>>> x = 0 >>> while x < 1001: print (x * 2) x += 1
while loops often involve the use of some counter that keeps track of how many times the loop has run. Sometimes you'll perform operations with the counter. For example, in the above loop, x was the counter, and we also multiplied the counter by 2 each time during the loop. To increment the counter, we used x += 1 which is shorthand for x = x + 1, or "add one to x".
Some situations call for putting one loop inside another, a practice called nesting. Nested loops could help you print every card in a deck (minus the Jokers):
>>> suits = ['Spades', 'Clubs', 'Diamonds', 'Hearts'] >>> values = ['Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King'] >>> for suit in suits: for value in values: print (str(value) + " of " + str(suit))
In the above example, you start with a suit, then loop through each value in the suit, printing out the card name. When you've reached the end of the list of values, you jump out of the nested loop and go back to the first loop to get the next suit. Then you loop through all values in the second suit and print the card names. This process continues until all the suits and values have been looped through.
You will use looping repeatedly (makes sense!) as you write GIS scripts in Python. Often, you'll need to iterate through every row in a table, every field in a table, or every feature class in a folder or a geodatabase. You might even need to loop through the vertices of a geographic feature.
You saw above that loops work particularly well with lists. arcpy has some methods that can help you create lists. Here's an example you can try that uses arcpy.ListFeatureClasses(). First, manually create a new folder C:\PSU\Geog485\Lesson2\PracticeData. Then copy the code below into a new script in PyScripter and run the script. The script copies all the data in your Lesson1 folder into the new Lesson2\PracticeData folder you just created.
# Copies all feature classes from one folder to another import arcpy try: arcpy.env.workspace = "C:/PSU/Geog485/Lesson1" # List the feature classes in the Lesson 1 folder fcList = arcpy.ListFeatureClasses() # Loop through the list and copy the feature classes to the Lesson 2 PracticeData folder for featureClass in fcList: arcpy.CopyFeatures_management(featureClass, "C:/PSU/Geog485/Lesson2/PracticeData/" + featureClass) except: print ("Script failed to complete") print (arcpy.GetMessages(2))
Notice above that once you have a Python list of feature classes (fcList), it's very easy to set up the loop condition (for featureClass in fcList:).
Another common operation in GIS scripts is looping through tables. In fact, the arcpy module contains some special objects called cursors that help you do this. Here's a short script showing how a cursor can loop through each row in a feature class and print the name. We'll cover cursors in detail in the next lesson, so don't worry if some of this code looks confusing right now. The important thing is to notice how a loop is used to iterate through each record:
import arcpy inTable = "C:/PSU/Geog485/Lesson2/CityBoundaries.shp" inField = "NAME" rows = arcpy.SearchCursor(inTable) #This loop goes through each row in the table # and gets a requested field value for row in rows: currentCity = row.getValue(inField) print (currentCity)
In the above example, a search cursor named rows retrieves records from the table. The for loop makes it possible to perform an action on each individual record.
Read the following in the ArcGIS Pro Help:
Many scripts that you write will need to have conditional logic that executes a block of code given a condition and perhaps executes a different block of code given a different condition. The "if," "elif," and "else" statements in Python provide this conditional logic. Try typing this example in the Python Interpreter:
>>> x = 3 >>> if x > 2: print ("Greater than two") Greater than two
In the above example, the keyword "if" denotes that some conditional test is about to follow. In this case, the condition of x being greater than two was met, so the script printed "Greater than two." Notice that you are required to put a colon (:) after the condition and indent any code executing because of the condition. For consistency in this class, all indentation is done using four spaces.
Using "else" is a way to run code if the condition isn't met. Try this:
>>> x = 1 >>> if x > 2: print ("Greater than two") else: print ("Less than or equal to two") Less than or equal to two
Notice that you don't have to put any condition after "else." It's a way of catching all other cases. Again, the conditional code is indented four spaces, which makes the code very easy for a human to scan. The indentation is required because Python doesn't require any type of "end if" statement (like many other languages) to denote the end of the code you want to execute.
If you want to run through multiple conditions, you can use "elif", which is Python's abbreviation for "else if":
>>> x = 2 >>> if x > 2: print ("Greater than two") elif x == 2: print ("Equal to two") else: print ("Less than two") Equal to two
In the code above, elif x == 2: tests whether x is equal to two. The == is a way to test whether two values are equal. Using a single = in this case would result in an error because = is used to assign values to variables. In the code above, you're not trying to assign x the value of 2, you want to check if x is already equal to 2, hence you use ==.
Caution: Using = instead of == to check for equivalency is a very common Python mistake, especially if you've used other languages where = is allowed for equivalency checks.
You can also use if, elif, and else to handle multiple possibilities in a set. The code below picks a random school from a list (notice we had to import the random module to do this and call a special method random.randrange()). After the school is selected and its name is printed, a series of if/elif/else statements appears that handles each possibility. Notice that the else statement is left in as an error handler; you should not run into that line if your code works properly, but you can leave the line in there to fail gracefully if something goes wrong.
import random # Choose a random school from a list and print it schools = ["Penn State", "Michigan", "Ohio State", "Indiana"] randomSchoolIndex = random.randrange(0,4) chosenSchool = schools[randomSchoolIndex] print (chosenSchool) # Depending on the school, print the mascot if chosenSchool == "Penn State": print ("You're a Nittany Lion") elif chosenSchool == "Michigan": print ("You're a Wolverine") elif chosenSchool == "Ohio State": print ("You're a Buckeye") elif chosenSchool == "Indiana": print ("You're a Hoosier") else: print ("This program has an error")
Some other programming languages have special keywords for doing the above, such as switch or select case. In Python, however, it's usually just done with a long list of "if"s and "elif"s.
You've previously learned how the string variable can contain numbers and letters and represent almost anything. When using Python with ArcGIS, strings can be useful for storing paths to data and printing messages to the user. There are also some geoprocessing tool parameters that you'll need to supply with strings.
Python has some very useful string manipulation abilities. We won't get into all of them in this course, but the following are a few techniques that you need to know.
To concatenate two strings means to append or add one string on to the end of another. For example, you could concatenate the strings "Python is " and "a scripting language" to make the complete sentence "Python is a scripting language." Since you are adding one string to another, it's intuitive that in Python you can use the + sign to concatenate strings.
You may need to concatenate strings when working with path names. Sometimes it's helpful or required to store one string representing the folder or geodatabase from which you're pulling datasets and a second string representing the dataset itself. You put both together to make a full path.
The following example, modified from one in the ArcGIS Help, demonstrates this concept. Suppose you already have a list of strings representing feature classes that you want to clip. The list is represented by "featureClassList" in this script:
# This script clips all datasets in a folder import arcpy inFolder = "c:\\data\\inputShapefiles\\" resultsFolder = "c:\\data\\results\\" clipFeature = "c:\\data\\states\\Nebraska.shp" # List feature classes arcpy.env.workspace = inFolder featureClassList = arcpy.ListFeatureClasses() # Loop through each feature class and clip for featureClass in featureClassList: # Make the output path by concatenating strings outputPath = resultsFolder + featureClass # Clip the feature class arcpy.Clip_analysis(featureClass, clipFeature, outputPath)
String concatenation is occurring in this line: outputPath = resultsFolder + featureClass. In longhand, the output folder "c:\\data\\results\\" is getting the feature class name added on the end. If the feature class name were "Roads.shp" the resulting output string would be "c:\\data\\results\\Roads.shp".
The above example shows that string concatenation can be useful in looping. Constructing the output path by using a set workspace or folder name followed by a feature class name from a list gives much more flexibility than trying to create output path strings for each dataset individually. You may not know how many feature classes are in the list or what their names are. You can get around that if you construct the output paths on the fly through string concatenation.
Sometimes in programming, you have a variable of one type that needs to be treated as another type. For example, 5 can be represented as a number or as a string. Python can only perform math on 5 if it is treated as a number, and it can only concatenate 5 onto an existing string if it is treated as a string.
Casting is a way of forcing your program to think of a variable as a different type. Create a new script in PyScripter, and type or paste the following code:
x = 0 while x < 10: print (x) x += 1 print ("You ran the loop " + x + " times.")
Now, try to run it. The script attempts to concatenate strings with the variable x to print how many times you ran a loop, but it results in an error: "TypeError: must be str not int." Python doesn't have a problem when you want to print the variable x on its own, but Python cannot mix strings and integer variables in a printed statement. To get the code to work, you have to cast the variable x to a string when you try to print it.
x = 0 while x < 10: print (x) x += 1 print ("You ran the loop " + str(x) + " times.")
You can force Python to think of x as a string by using str(x). Python has other casting functions such as int() and float() that you can use if you need to go from a string to a number. Use int() for integers and float() for decimals.
It's time to take a break and do some readings from another source. If you are new to Python scripting, this will help you see the concepts from a second angle.
Finish reading Zandbergen chapters 4 - 6 as detailed below. This can take a few hours, but it will save you hours of time if you make sure you understand this material now.
ArcGIS Pro edition:
ArcMap edition:
If you still don't feel like you understand the material after reading the above chapters, don't re-read it just yet. Try some coding from the Lesson 2 practice exercises and assignments, then come back and re-read if necessary. If you are really struggling with a particular concept, type the examples in the console. Programming is like a sport in the sense that you cannot learn all about it by reading; at some point, you have to get up and do it.
In this section of the lesson, you've learned the basic programming concepts of lists, loops, decision structures, and string manipulation. You might be surprised at what you can do with just these skills. In this section, we'll practice putting them all together to address a scenario. This will give us an opportunity to talk about strategies for approaching programming problems in general.
The scenario we'll tackle is to simulate a one-player game of Hasbro's children's game "Hi Ho! Cherry-O." In this simple game of chance, you begin with 10 cherries on a tree. You take a turn by spinning a random spinner, which tells you whether you get to add or remove cherries on the turn. The possible spinner results are:
You continue taking turns until you have 0 cherries left on your tree, at which point you have won the game. Your objective here is to write a script that simulates the game, printing the following:
Although this example may seem juvenile, it's an excellent way to practice everything you just learned. As a beginner, you may seem overwhelmed by the above problem. A common question is, "Where do I start?" The best approach is to break down the problem into smaller chunks of things you know how to do.
One of the most important programming skills you can acquire is the ability to verbalize a problem and translate it into a series of small programming steps. Here's a list of things you would need to do in this script. Programmers call this pseudocode because it's not written in code, but it follows the sequence their code will need to take.
It also helps to list the variables you'll need to keep track of:
Let's try to address each of the pseudocode steps. Don't worry about the full flow of the script yet. Rather, try to understand how each step of the problem should be solved with code. Assembling the blocks of code at the end is relatively trivial.
How do you simulate a random spin? In one of our previous examples, we used the random module to generate a random number within a range of integers; however, the choices on this spinner are not linear. A good approach here is to store all spin possibilities in a list and use the random number generator to pick the index for one of the possibilities. On its own, the code would look like this:
import random spinnerChoices = [-1, -2, -3, -4, 2, 2, 10] spinIndex = random.randrange(0, 7) spinResult = spinnerChoices[spinIndex]
The list spinnerChoices holds all possible mathematical results of a spin (remove 1 cherry, remove 2 cherries, etc.). The final value 10 represents the spilled bucket (putting all cherries back on the tree).
You need to pick one random value out of this list to simulate a spin. The variable spinIndex represents a random integer from 0 to 6 that is the index of the item you'll pull out of the list. For example, if spinIndex turns out to be 2, your spin is -3 (remove 3 cherries from the tree). The spin is held in the variable spinResult.
The random.randrange() method is used to pick the random numbers. At the beginning of your script, you have to import the random module in order to use this method.
Once you have a spin result, it only takes one line of code to print it. You'll have to use the str() method to cast it to a string, though.
print ("You spun " + str(spinResult) + ".")
As mentioned above, you need to have some variable to keep track of the number of cherries on your tree. This is one of those variables that it helps to name intuitively:
cherriesOnTree = 10
After you complete a spin, you need to modify this variable based on the result. Remember that the result is held in the variable spinResult, and that a negative spinResult removes cherries from your tree. So your code to modify the number of cherries on the tree would look like:
cherriesOnTree += spinResult
Remember, the above is shorthand for saying cherriesOnTree = cherriesOnTree + spinResult.
If you win the game, you have 0 cherries. You don't have to reach 0 exactly, but it doesn't make sense to say that you have negative cherries. Similarly, you might spin the spilled bucket, which for simplicity we represented with positive 10 in the spinnerChoices. You are not allowed to have more than 10 cherries on the tree.
A simple if/elif decision structure can help you keep the cherriesOnTree within 0 and 10:
if cherriesOnTree > 10: cherriesOnTree = 10 elif cherriesOnTree < 0: cherriesOnTree = 0
This means, if you wound up with more than 10 cherries on the tree, set cherriesOnTree back to 10. If you wound up with fewer than 0 cherries, set cherriesOnTree to 0.
All you have to do for this step is to print your cherriesOnTree variable, casting it to a string, so it can legally be inserted into a sentence.
print ("You have " + str(cherriesOnTree) + "cherries on your tree.")
You probably anticipated that you would have to figure out a way to take multiple turns. This is the perfect scenario for a loop.
What is the loop condition? There have to be some cherries left on the tree in order to start another turn, so you could begin the loop this way:
while cherriesOnTree > 0:
Much of the code we wrote above would go inside the loop to simulate a turn. Since we need to keep track of the number of turns taken, at the end of the loop we need to increment a counter:
turns += 1
This turns variable would have to be initialized at the beginning of the script, before the loop.
This code could print the number of turns at the end of the game:
print ("It took you " + str(turns) + "turns to win the game.")
Your only remaining task is to assemble the above pieces of code into a script. Below is an example of how the final script would look. Copy this into a new PyScripter script and try to run it:
# Simulates one game of Hi Ho! Cherry-O import random spinnerChoices = [-1, -2, -3, -4, 2, 2, 10] turns = 0 cherriesOnTree = 10 # Take a turn as long as you have more than 0 cherries while cherriesOnTree > 0: # Spin the spinner spinIndex = random.randrange(0, 7) spinResult = spinnerChoices[spinIndex] # Print the spin result print ("You spun " + str(spinResult) + ".") # Add or remove cherries based on the result cherriesOnTree += spinResult # Make sure the number of cherries is between 0 and 10 if cherriesOnTree > 10: cherriesOnTree = 10 elif cherriesOnTree < 0: cherriesOnTree = 0 # Print the number of cherries on the tree print ("You have " + str(cherriesOnTree) + " cherries on your tree.") turns += 1 # Print the number of turns it took to win the game print ("It took you " + str(turns) + " turns to win the game.") lastline = input(">")
Review the final code closely and consider the following things.
The first thing you do is import whatever supporting modules you need; in this case, it's the random module.
Next, you declare the variables that you'll use throughout the script. Each variable has a scope, which determines how broadly it is used throughout the script. The variables spinnerChoices, turns, and cherriesOnTree are needed through the entire script, so they are declared at the beginning, outside the loop. Variables used throughout your entire program like this have global scope. On the other hand, the variables spinIndex and spinResult have local scope because they are used only inside the loop. Each time the loop runs, these variables are re-initialized and their values change.
You could potentially declare the variable spinnerChoices inside the loop and get the same end result, but performance would be slower because the variable would have to be re-initialized every time you ran the loop. When possible, you should declare variables outside loops for this reason.
If you had declared the variables turns or cherriesOnTree inside the loop, your code would have logical errors. You would essentially be starting the game anew on every turn with 10 cherries on your tree, having taken 0 turns. In fact, you would create an infinite loop because there is no way to remove 10 cherries during one turn, and the loop condition would always evaluate to true. Again, be very careful about where you declare your variables when the script contains loops.
Notice that the total number of turns is printed outside the loop once the game has ended. The final line lastline = input(">") gives you an empty cursor prompting for input and is just a trick to make sure the application doesn't disappear when it's finished (if you run the script from a command console).
In the above example, you saw how lists, loops, decision structures, and variable casting can work together to help you solve a programming challenge. You also learned how to approach a problem one piece at a time and assemble those pieces into a working script. You'll have a chance to practice these concepts on your own during this week's assignment. The next and final section of this lesson will provide you with some sources of help if you get stuck.
If the above activity made you enthusiastic about writing some code yourself, take the above script and try to find the average number of turns it takes to win a game of Hi-Ho! Cherry-O. To do this, add another loop that runs the game a large number of times, say 10000. You'll need to record the total number of turns required to win all the games, then divide by the number of games (use "/" for the division). Send me your final result, and I'll let you know if you've found the correct average.
If you find writing code to be a slow, mystifying, and painstaking process, fraught with all kinds of opportunities to make mistakes, welcome to the world of a programmer! Perhaps to their chagrin, programmers spend the majority of their time hunting down and fixing bugs. Programmers also have to continually expand and adapt their skills to work with new languages and technologies, which requires research, practice, and lots of trial and error.
The best candidates for software engineering jobs are not the ones who list the most languages or acronyms on their resumes. Instead, the most desirable candidates are self-sufficient, meaning they know how to learn new things and find answers to problems on their own. This doesn't mean that they never ask for help; on the contrary, a good programmer knows when to stop banging his or her head against the wall and consult peers or a supervisor for advice. However, most everyday problems can be solved using the help documentation, online code examples, online forums, existing code that works, programming books, and debugging tools in the software.
Suppose you're in a job interview and your prospective employer asks, "What do you do when you run into a 'brick wall' when programming? What sources do you first go to for help?" If you answer, "My supervisor" or "My co-workers," this is a red flag, signifying that you could be a potential time sink to the development team. Although the more difficult problems require group collaboration, a competitive software development team cannot afford to hold an employee's hand through every issue that he or she encounters. From the author's experience, many of the most compelling candidates answer this question, "Google." They know that most programming problems, although vexing, are common and the answer may be at their fingertips in less than 30 seconds through a well-phrased Internet search. With popular online forums such as Stack Exchange providing answers to many common syntax and structuring questions, searching for information online can actually be faster than walking down the hall and asking a co-worker, and it saves everybody time.
In this section of the lesson, you'll learn about places where you can go for help when working with Python and when programming in general. You will have a much easier experience in this course if you remember these resources and use them as you complete your assignments.
The secret to successful programming is to run early, run often, and don't be afraid of things going wrong when you run your code the first time. Debugging, or finding mistakes in code, is a part of life for programmers. Here are some things that can happen:
Errors happen. There are very few programmers who can sit down and, off the top of their heads, write dozens of lines of bug free code. This means a couple of things for you:
Syntax errors occur when you typed something incorrectly and your code refuses to run. Common syntax errors include forgetting a colon when setting a loop or an if condition, using single backslashes in a file name, providing the wrong number of arguments to a function, or trying to mix variable types incorrectly, such as dividing a number by a string.
When you try to run code with a syntax error in PyScripter, an error message will appear in the Python Interpreter, referring to the script file that was run, along with the line number that contained the error. For example, a developer transitioning from ArcMap to ArcGIS Pro might forget that the syntax of the print function is different, resulting in the error message, "SyntaxError: Missing parentheses in call to 'print'. Did you mean print(x)?"
If your code crashes, you may see an error message in the Python Interpreter. Instead of allowing your eyes to glaze over or banging your head against the desk, you should rejoice at the fact that the software possibly reported to you exactly what went wrong! Scour the message for clues as to what line of code caused the error and what the problem was. Do this even if the message looks intimidating. For example, see if you can understand what caused this error message (as reported by the Spyder IDE):
runfile('C:/Users/detwiler/Documents/geog485/Lesson2/syntax_error_practice.py', wdir='C:/Users/detwiler/Documents/geog485/Lesson2') Traceback (most recent call last): File ~\AppData\Local\ESRI\conda\envs\arcgispro-py3-spyd\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec exec(code, globals, locals) File c:\users\detwiler\documents\geog485\lesson2\syntax_error_practice.py:5 x = x / 0 ZeroDivisionError: division by zero
The message begins with a call to Python's runfile() function, which Spyder shows when the script executes successfully as well. Because there's an error in this example script, the runfile() bit is then followed by a "traceback," a report on the origin of the error. The first few lines refer to the internal Python modules that encountered the problem, which in most cases you can safely ignore. The part of the traceback you should focus on, the part that refers to your script file, is at the end; in this case, we're told that the error was caused in Line 5: x = x / 0. Dividing by 0 is not possible, and the computer won't try to do it. (PyScripter's traceback report for this same script lists only your script file, leaving out the internal Python modules, so you may find it an easier environment for locating errors.)
Error messages are not always as easy to decipher as in this case, unfortunately. There are many online forums where you might go looking for help with a broken script (Esri's GeoNet [4] for ArcGIS-specific questions; StackOverflow [5], StackExchange [6], Quora [7] for more generic questions). You can make it a lot easier for someone to help you if, rather than just saying something like, "I'm getting an error when I run my script. Can anyone see what I did wrong," you include the line number flagged by PyScripter along with the exact text of the error message. The exact text is important because the people trying to help you are likely to plug it into a search engine and will get better results that way. Or better yet, you could search the error message yourself! The ability to solve coding problems through the reading of documentation and searching online forums is one of the distinguishing characteristics of a good developer.
Sometimes it's easy to sprinkle a few 'print' statements throughout your code to figure out how far it got before it crashed, or what's happening to certain values in your script as it runs. This can also be helpful to verify that your loops are doing what you expect and that you are avoiding off-by-one errors.
Suppose you are trying to find the mean (average) value of the items in a list with the code below.
#Find average of items in a list list = [22,343,73,464,90] for item in list: total = 0 total += item average = total / len(list) print ("Average is " + str(average))
The script reports "Average is 18," which doesn't look right. From a quick visual check of this list you could guess that the average would be over 100. The script isn't erroneously getting the number 18 from the list; it's not one of the values. So where is it coming from? You can place a few strategic print statements in the script to get a better report of what's going on:
#Find average of items in a list list = [22,343,73,464,90] for item in list: print ("Processing loop...") total = 0 total += item print (total) print (len(list)) average = total / len(list) print ("Performing division...") print ("Average is " + str(average))
Now when you run the script you see.
Processing loop... 22 Processing loop... 343 Processing loop... 73 Processing loop... 464 Processing loop... 90 5 Performing division... Average is 18
The error now becomes more clear. The running total isn't being kept successfully; instead, it's resetting each time the loop runs. This causes the last value, 90, to be divided by 5, yielding an answer of 18. You need to initialize the variable for the total outside the loop to prevent this from happening. After fixing the code and removing the print statements, you get:
#Find average of items in a list list = [22,343,73,464,90] total = 0 for item in list: total += item average = total / len(list) print ("Average is " + str(average))
The resulting "Average is 198" looks a lot better. You've fixed a logical error in your code: an error that doesn't make your script crash, but produces the wrong result.
Although debugging with print statements is quick and easy, you need to be careful with it. Once you've fixed your code, you need to remember to remove the statements in order to make your code faster and less cluttered. Also, adding print statements becomes impractical for long or complex scripts. You can pinpoint problems more quickly and keep track of many variables at a time using the PyScripter debugger, which is covered in the next section of this lesson.
Sometimes when other quick attempts at debugging fail, you need a way to take a deeper look into your script. Most integrated development environments (IDEs) like Pyscripter include some debugging tools that allow you to step through your script line-by-line to attempt to find an error. These tools allow you to keep an eye on the value of all variables in your script to see how they react to each line of code. The Debug toolbar can be a good way to catch logical errors where an offending line of code is preventing your script from returning the correct outcome. The Debug toolbar can also help you find which line of code is causing a crash.
The best way to explain the aspects of debugging is to work through an example. This time, we'll look at some code that tries to calculate the factorial of an integer (the integer is hard-coded to 5 in this case). In mathematics, a factorial is the product of an integer and all positive integers below it. Thus, 5! (or "5 factorial") should be 5 * 4 * 3 * 2 * 1 = 120.
The code below attempts to calculate a factorial through a loop that increments the multiplier by 1 until it reaches the original integer. This is a valid approach since 1 * 2 * 3 * 4 * 5 would also yield 120.
# This script calculates the factorial of a given # integer, which is the product of the integer and # all positive integers below it. number = 5 multiplier = 1 while multiplier < number: number *= multiplier multiplier += 1 print (number)
Even if you can spot the error, follow along with the steps below to get a feel for the debugging process and the PyScripter Debug toolbar.
Open PyScripter and copy the above code into a new script.
Step through the loop until "multiplier" reaches a value of 10. It should be obvious at this point that the loop has not exited at the desired point. Our intent was for it to quit when "number" reached 120.
Can you spot the error now? The fact that the loop has failed to exit should draw your attention to the loop condition. The loop will only exit when "multiplier" is greater than or equal to "number." That is obviously never going to happen as "number" keeps getting bigger and bigger as it is multiplied each time through the loop.
In this example, the code contained a logical error. It re-used the variable for which we wanted to find the factorial (5) as a variable in the loop condition, without considering that the number would be repeatedly increased within the loop. Changing the loop condition to the following would cause the script to work:
while multiplier < 5:
Even better than hard-coding the value 5 in this line would be to initialize a variable early and set it equal to the number whose factorial we want to find. The number could then get multiplied independent of the loop condition variable.
Click the Stop button in the Debug toolbar to end the debugging session. We're now going to step through a corrected version of the factorial script, but you may notice that the Variable window still displays a list of the variables and their values from the point at which you stopped executing. That's not necessarily a problem, but it is good to keep in mind.
Open a new script, paste in the code below, and save the script as debugger_walkthrough2.py
# This script calculates the factorial of a given # integer, which is the product of the integer and # all positive integers below it. number = 5 loopStop = number multiplier = 1 while multiplier < loopStop: number *= multiplier multiplier += 1 print (number)
In the above example, you used the Debug toolbar to find a logical error that had caused an endless loop in your code. Debugging tools are often your best resource for hunting down subtle errors in your code.
You can and should practice using the Debug toolbar in the script-writing assignments that you receive in this course. You may save a lot of time this way. As a teaching assistant in a university programming lab years ago, the author of this course saw many students wait a long time to get one-on-one help, when a simple walk through their code using the debugger would have revealed the problem.
Read Zandbergen 7.1 - 7.5 (11.1 - 11.5 if you have the old ArcMap version of the book) to get his tips for debugging. Then read 7.11 (11.1 in the old version) and dog-ear the section on debugging as a checklist for you to review any time you hit a problem in your code during the next few weeks. The text doesn't focus solely on PyScripter's debugging tools, but you should be able to follow along and compare the tools you're reading about to what you encountered in PyScripter during the short exercise above. It will also be good for you to see how this important aspect of script development is handled in other IDEs.
When you work with geoprocessing tools in Python, sometimes a script will fail because something went wrong with the tool. It could be that you wrote flawless Python syntax, but your script doesn't work as expected because Esri's geoprocessing tools cannot find a dataset or otherwise digest a tool parameter. You won't be able to catch these errors with the debugger, but you can get a view into them by printing the messages returned from the Esri geoprocessing framework.
Esri has configured its geoprocessing tools to frequently report what they're doing. When you run a geoprocessing tool from ArcGIS Pro, you see a box with these messages, sometimes accompanied by a progress bar. You learned in Lesson 1 that you can use arcpy.GetMessages() to access these messages from your script. If you only want to view the messages when something goes wrong, you can include them in an except block of code, like this.
try: . . . except: print (arcpy.GetMessages())
Remember that when using try/except, in the normal case, Python will execute everything in the try-block (= everything following the "try:" that is indented relative to it) and then continue after the except-block (= everything following the "except:" that is indented relative to it). However, if some command in the try-block fails, the program execution directly jumps to the beginning of the except-block and, in this case, prints out the messages we get from arcpy.GetMessages(). After the except-block has been executed, Python will continue with the next statement after the except-block.
Geoprocessing messages have three levels of severity: Message, Warning, and Error. You can pass an index to the arcpy.GetMessages() method to filter through only the messages that reach a certain level of severity. For example, arcpy.GetMessages(2) would return only the messages with a severity of "Error". Error and warning messages sometimes include a unique code that you can use to look up more information about the message. The ArcGIS Pro Help contains topics that list the message codes and provide details on each. Some of the entries have tips for fixing the problem.
Try/except can be used in many ways and at different indentation levels in a script. For instance, you can have a single try/except construct as in the example above, where the try-block contains the entire functionality of your program. If something goes wrong, the script will output some error message and then terminate. In other situations, you may want to split the main code into different parts, each contained by its own try/except construct to deal with the particular problems that may occur in this part of the code. For instance, the following structure may be used in a batch script that performs two different geoprocessing operations on a set of shapefiles when an attempt should be made to perform the second operation, even if the first one fails.
for featureClass in fcList: try: . . . # perform geoprocessing operation 1 except: . . . # deal with failure of geoprocessing operation 1 try: . . . # perform geoprocessing operation 2 except: . . . # deal with failure of geoprocessing operation 2
Let us assume, the first geoprocessing operation fails for the first shapefile in fcList. As a result, the program execution will jump to the first except-block which contains the code for dealing with this error situation. In the simplest case, it will just produce some output messages. After the except-block has been executed, the program execution will continue as normal by moving on to the second try/except statement and attempt to perform the second geoprocessing operation. Either this one succeeds or it fails too, in which case the second except-block will be executed. The last thing to note is that since both try/except statements are contained in the body of the for-loop going through the different shapefiles, even if both of the operations fail for one of the files, the script will always jump back to the beginning of the loop body and continue with the next shapefile in the list which is often the desired behavior of a batch script.
Please take a look at the official ArcGIS Pro documentation for more detail about geoprocessing messages. Be sure to read these topics:
Besides the above approaches, there are many other places you can get help. A few of them are described below. If you're new to programming, just knowing that these resources exist and how to use them can help you feel more confident. Find the ones that you prefer and return to them often. This habit will help you become a self-sufficient programmer and will improve your potential to learn any new programming language or technology.
Drawing on the resources below takes time and effort. Many people don't like combing through computer documentation, and this is understandable. However, you may ultimately save time if you look up the answer for yourself instead of waiting for someone to help you. Even better, you will have learned something new from your own experience, and things you learn this way are much easier to remember in the future.
Search engines are useful for both quick answers and obscure problems. Did you forget the syntax for a loop? The quickest remedy may be to Google "for loop python" or "while loop python" and examine one of the many code examples returned. Search engines are extremely useful for diagnosing error messages. Google the error message in quotes, and you can read experiences from others who have had the same issue. If you don't get enough hits, remove the quotes to broaden the search.
One risk you run from online searches is finding irrelevant information. Even more dangerous is using irrelevant information. Research any sample code to make sure it is applicable to the version of Python you're using. Some syntax in Python 3.x, used for scripting in ArcGIS Pro, is different from the Python 2.x used for scripting in ArcMap, for example.
Esri maintains their entire help system online, and you'll find most of their scripting topics in the arcpy section [10].
Another section, which you should visit repeatedly, is the Tool Reference [11], which describes every tool in the toolbox and contains Python scripting examples for each. If you're having trouble understanding what parameters go in or out of a tool, or if you're getting an error back from the geoprocessing framework itself, try the Tool Reference before you do a random Internet search. You will have to visit the Tool Reference in order to be successful in some of the course projects and quizzes.
The official Python documentation [12] is available online. Some of it gets very detailed and takes the tone of being written by programmers for programmers. The part you'll probably find most helpful is the Python Standard Library reference [13], which is a good place to learn about Python's modules such as "os", "csv", "math," or "random."
Programming books can be very hit or miss. Many books are written for people who have already programmed in other languages. Others proclaim they're aimed at beginners, but the writing or design of the book may be unintuitive or difficult to digest. Before you drop $40 on a book, try to skim through it yourself to see if the writing generally makes sense to you (don't worry about not understanding the code--that will come along as you work through the book).
The course text Python Scripting for ArcGIS is a generally well-written introduction to just what the title says: working with ArcGIS using Python. There are a few other Python+ArcGIS books as well. If you've struggled with the material, or if you want to do a lot of scripting in the future, I may recommend picking up one of these. Your textbook can come in handy if you need to look at a very basic code example, or if you're going to use a certain type of code construct for the first time, and you want to review the basics before you write anything.
A good general Python reference is Learning Python by Mark Lutz. We previously used this text in Geog 485 before there was a book about scripting with ArcGIS. It covers beginning to advanced topics, so don't worry if some parts of it look intimidating.
The Esri forums are a place where you can pose your question to other Esri software users, or read about issues other users have encountered that may be similar to yours. There is a Python Esri forum [4] that relates to scripting with ArcGIS, and also a more general Geoprocessing Esri forum [14] you might find useful.
Before you post a question on the Esri forums, do a little research to make sure the question hasn't been answered already, at least recently. I also suggest that you post the question to our class forums first, since your peers are working on the same problems, and you are more likely to find someone who's familiar with your situation and has found a solution.
There are many other online forums that address GIS or programming questions. You'll see them all over the Internet if you perform a Google search on how to do something in Python. Some of these sites are laden with annoying banner ads or require logins, while others are more immediately helpful. Stack Exchange is an example of a well-traveled technical forum, light on ads, that allows readers to promote or demote answers depending on their helpfulness. One of its child sites, GIS Stack Exchange [15], specifically addresses GIS and cartography issues.
If you do post to online forums, be sure to provide detailed information on the problem and list what you've tried already. Avoid posts such as "Here's some code that's broken, and I don't know why" followed by dozens of lines of pasted-in code. State the problem in a general sense and focus on the problem code. Include exact error messages when possible.
People on online forums are generally helpful, but expect a hostile reception if you make them feel like they are doing your academic homework for you. Also, be aware that posting or copying extensive sections of Geog 485 assignment code on the internet is a violation of academic integrity and may result in a penalty applied to your grade (see section on Academic Integrity in the course syllbus).
Our course has discussion boards that we recommend you use to consult your peers and instructor about any Python problem that you encounter. I encourage you to check them often and to participate by both asking and answering questions. I request that you make your questions focused and avoid pasting large blocks of code that would rob someone of the benefit of completing the assignment on their own. Short, focused blocks of code that solve a specific question are definitely okay. Code blocks that are not copied directly from your assignment are also okay.
I monitor all discussion boards closely; however, sometimes I may not respond immediately because I want to give you a chance to help each other and work through problems together. If you post a question and wind up solving your own problem, please post again to let us know and include how you managed to solve the problem in case other students run into the same issue.
I am available to help you at any point in the course, and my goal is to respond to any personal message or e-mail within 24 hours on weekdays (notice the obvious problem if you have waited to begin your assignment until 24 hours before it's due!). I am happy to consult with you through e-mail, video conference, or whatever technology is necessary to help you be successful.
I ask that you try some of the many troubleshooting and help resources above before you contact me. If the issue is with your code and I cannot immediately see the problem, the resources we will use to find the answer will be the same that I listed above: the debugger, printing geoprocessing messages, looking for online code examples, etc. If you feel unsure about what you're doing, I'm available to talk through these approaches with you. Also, in cases where you feel that you cannot post a description of the problem without including a lot of code that may give away part of the solution to an assignment, feel free to send your code and problem description directly to me via Canvas mail.
Before trying to tackle Project 2, you may want to try some simple practice exercises, particularly if the concepts in this lesson are new to you. Remember to choose File > New in PyScripter to create a new script (or click the empty page icon). You can name the scripts something like Practice1, Practice2, etc.
Python String objects have an index method that enables you to find a substring within the larger string. For example, if I had a variable defined as name = "James Franklin" and followed that up with the expression name.index("Fr"), it would return the value 6 because the substring "Fr" begins at character 6 in the string held in name. (The first character in a string is at position 0.)
For this practice exercise, start by creating a list of names like the following:
beatles = ["John Lennon", "Paul McCartney", "Ringo Starr", "George Harrison"]
Then write code that will loop through all the items in the list, printing a message like the following:
"There is a space in ________'s name at character ____." where the first blank is filled in with the name currently being processed by the loop and the second blank is filled in with the position of the first space in the name as returned by the index method. (You should obtain values of 4, 4, 5 and 6, respectively, for the items in the list above.)
This is a good example in which it is smart to write and test versions of the script that incrementally build toward the desired result rather than trying to write the final version in one fell swoop. For example, you might start by setting up a loop and simply printing each name. If you get that to work, give yourself a little pat on the back and then see if you can simply print the positions of the space. Once you get that working, then try plugging the name and space positions into the larger message.
Practice 1 Solution [16]
Build on Exercise 1 by printing each name in the list in the following format:
Last, First
To do this, you'll need to find the position of the space just as before. To extract part of a string, you can specify the start character and the end character in brackets after the string's name, as in the following:
name = "James Franklin" print (name[6:14]) # prints Franklin
One quirky thing about this syntax is that you need to specify the end character as 1 beyond the one you really want. The final "n" in "Franklin" is really at position 13, but I needed to specify a value of 14.
One handy feature of the syntax is that you may omit the end character index if you want everything after the start character. Thus, name[6:] will return the same string as name[6:14] in this example. Likewise, the start character may be omitted to obtain everything from the beginning of the string to the specified end character.
Practice 2 Solution [17]
Write a script that accepts a score from 1-100 as an input parameter, then reports the letter grade for that score. Assign letter grades as follows:
A: 90-100
B: 80-89
C: 70-79
D: 60-69
F: <60
Practice 3 Solution [18]
Imagine that you're again working with the Nebraska precipitation data from Lesson 1 and that you want to create copies of the Precip2008Readings shapefile for the next 4 years after 2008 (e.g., Precip2009Readings, Precip2010Readings, etc.). Essentially, you want to copy the attribute schema of the 2008 shapefile, but not the data points themselves. Those will be added later. The tool for automating this kind of operation is the Create Feature Class tool in the Data Management toolbox. Look up this tool in the Help system and examine its syntax and the example script. Note the optional template parameter, which allows you to specify a feature class whose attribute schema you want to copy. Also note that Esri uses some inconsistent casing with this tool, and you will have to call arcpy.CreateFeatureclass_management() using a lower-case "c" on "class." If you follow the examples in the Geoprocessing Tool Reference help, you will be fine.
To complete this exercise, you should invoke the Create Feature Class tool inside a loop that will cause the tool to be run once for each desired year. The range(...) function can be used to produce the list of years for your loop.
Practice 4 Solution [19]
The data for this practice exercise consists of two file geodatabases: one for the USA and one for just the state of Iowa. The USA dataset contains miscellaneous feature classes. The Iowa file geodatabase is empty except for an Iowa state boundary feature class.
Download the data [20]
Your task is to write a script that programmatically clips all the feature classes in the USA geodatabase to the Iowa state boundary. The clipped feature classes should be written to the Iowa geodatabase. Append "Iowa" to the beginning of all the clipped feature class names.
Your script should be flexible enough that it could handle any number of feature classes in the USA geodatabase. For example, if there were 15 feature classes in the USA geodatabase instead of three, your final code should not need to change in any way.
Practice 5 Solution [21]
Some GIS departments have determined a single, standard projection in which to maintain their source data. The raw datasets, however, can be obtained from third parties in other projections. These datasets then need to be reprojected into the department's standard projection. Batch reprojection, or the reprojection of many datasets at once, is a task well suited to scripting.
In this project, you'll practice Python fundamentals by writing a script that re-projects the vector datasets in a folder. From this script, you will then create a script tool that can easily be shared with others.
The tool you will write should look like the image below. It has two input parameters and no output parameters. The two input parameters are:
Running the tool causes re-projected datasets to be placed on disk in the target folder.
To receive full credit, your script:
Successful completion of the above requirements is sufficient to earn 90% of the credit on this project. The remaining 10% is reserved for "over and above" efforts which could include, but are not limited to, the following:
You are not required to handle datum transformations in this script. It is assumed that each dataset in the folder uses the same datum, although the datasets may be in different projections. Handling transformations would cause you to have to add an additional parameter in the Project tool and would make your script more complicated than you would probably like for this assignment.
The Lesson 2 data [1] folder contains a set of vector shapefiles for you to work with when completing this project (delete any subfolders in your Lesson 2 data folder—you may have one called PracticeData—before beginning this project). These shapefiles were obtained from the Washington State Department of Transportation GeoData Distribution Catalog [22], and they represent various geographic features around Washington state. For the purpose of this project, I have put these datasets in various projections. These projections share the same datum (NAD 83) so that you do not have to deal with datum transformations.
The datasets and their original projections are:
Deliverables for this project are as follows:
The following tips can help improve your possibility of success with this project:
There are a lot of ways to insert "_projected" in the name of a dataset, but you might find it useful to start by temporarily removing ".shp" and adding it back on later. To make your code work for both a shapefile (which has the extension .shp) and a feature class in a geodatabase (which does not have the extension .shp), you can use the following:
rootName = fc if rootName.endswith(".shp"): rootName = rootName.replace(".shp","")
In the above code, fc is your feature class name. If it is the name of a shapefile it will include the .shp . The replace function searches for any string ".shp" (the first parameter) in the file name and replaces it with nothing (symbolized in the second parameter by empty quotes ""). So after running this code, variable rootName will contain the name of the feature class name without the ".shp" . Since replace(...) does not change anything if the string given as the first parameter does not occur in fc, the code above can be replaced by just a single line:
rootName = fc.replace(".shp","")You could also potentially chop off the last four characters using something like
rootName = fc[:-4]
but hard-coding numbers other than 0 or 1 in your script can make the code less readable for someone else. Seeing a function like replace is a lot easier for someone to interpret than seeing -4 and trying to figure out why that number was chosen. You should therefore use replace(...) in your solution instead.
if fcSR.Name != targetSR.Name:where fcSR is the spatial reference of the feature class to be projected and targetSR is the target spatial reference obtained from the target projection shapefile.
Links
[1] https://www.e-education.psu.edu/geog485/sites/www.e-education.psu.edu.geog485/files/data/Lesson2.zip
[2] http://docs.python.org/tutorial/datastructures.html
[3] https://pro.arcgis.com/en/pro-app/arcpy/get-started/listing-data.htm
[4] https://community.esri.com/community/developers/gis-developers/python
[5] http://www.stackoverflow.com/
[6] http://www.stackexchange.com/
[7] https://www.quora.com/
[8] https://pro.arcgis.com/en/pro-app/arcpy/geoprocessing_and_python/message-types-and-severity.htm
[9] https://pro.arcgis.com/en/pro-app/tool-reference/appendices/understanding-geoprocessing-tool-errors-and-warnings.htm
[10] https://pro.arcgis.com/en/pro-app/arcpy/get-started/what-is-arcpy-.htm
[11] https://pro.arcgis.com/en/pro-app/tool-reference/
[12] http://www.python.org/doc/
[13] http://docs.python.org/library
[14] https://community.esri.com/community/gis/analysis/geoprocessing
[15] http://gis.stackexchange.com/
[16] https://www.e-education.psu.edu/geog485/node/173
[17] https://www.e-education.psu.edu/geog485/node/174
[18] https://www.e-education.psu.edu/geog485/node/250
[19] https://www.e-education.psu.edu/geog485/176
[20] https://www.e-education.psu.edu/geog485/sites/www.e-education.psu.edu.geog485/files/data/Lesson2PracticeExercise.zip
[21] https://www.e-education.psu.edu/geog485/node/251
[22] https://gisdata-wsdot.opendata.arcgis.com/