Now that we have a better understanding of geospatial analysis, the next step is to build a simple GIS using Python called SimpleGIS
. This small program will be a technically complete GIS with a geographic data model and the ability to render the data as a visual thematic map showing the population of different cities.
The data model will also be structured so that you can perform basic queries. Our SimpleGIS
will contain the state of Colorado, three cities, and population counts for each city.
Most importantly, we will demonstrate the power and simplicity of Python programming by building this tiny system in pure Python. We will only use modules available in the standard Python distribution without downloading any third-party libraries.
As stated earlier, this book assumes that you have some basic knowledge of Python. The examples in this book are based on Python 3.4.3, which you can download here:
https://www.python.org/downloads/release/python-343/
The only module used in the following example is the turtle
module that provides a very simple graphics engine based on the Tkinter
library included with Python. If you used the installers for Windows or Mac OS X, the Tkinter
library should be included already. If you compiled Python yourself or are using a distribution from somewhere besides Python.org
, then make sure that you can import the turtle
module by typing the following at a command prompt to run the turtle
demo script:
python –m turtle
If your Python distribution does not have Tkinter, you can find information on installing it from the following page. The information is for Python 2.3 but the process is still the same:
http://tkinter.unpythonic.net/wiki/How_to_install_Tkinter
The official Python wiki page for Tkinter can be found here:
https://wiki.python.org/moin/TkInter
The documentation for Tkinter is in the Python Standard Library documentation that can be found at https://docs.python.org/2/library/tkinter.html.
If you are new to Python, Dive into Python is a free online book, which covers all the basics of Python and will bring you up to speed. For more information, refer to http://www.diveintopython.net.
The code is divided into two different sections. The first is the data model section and the second is the map renderer that draws this data. For the data model, we will use simple Python lists. A Python list is a native data type, which serves as a container for other Python objects in a specified order. Python lists can contain other lists and are great for simple data structures. They also map well to more complex structures or even databases if you decide you want to develop your script further.
The second portion of the code will render the map using the Python turtle graphics engine. We will have only one function in the GIS that converts the world coordinates, in this case, longitude and latitude, into pixel coordinates. All graphics engines have an origin point of (0,0) that is usually in the top-left or lower-left corner of the canvas. Turtle graphics are designed to teach programming visually. The turtle graphics canvas uses an origin of (0,0) in the center, similar to a graphing calculator. The following image illustrates the type of Cartesian graph that the turtle module uses. In the following graph, some points are plotted in both positive and negative space:
This also means that the turtle graphics engine can have negative pixel coordinates, which is uncommon for graphics canvases. However, for this example, the turtle
module is the quickest and simplest way to render our map.
You can run this program interactively in the Python interpreter or you can save the complete program as a script and run it. The Python interpreter is an incredibly powerful way to learn new concepts because it gives you real-time feedback on errors or unexpected program behavior. You can easily recover from these issues and try something else until you get the results that you want:
turtle
module first. We'll use Python's import as feature to assign the module the name t
to save space and time when typing turtle commands:import turtle as t
0
. So, if we want to access the first item in a list called myList
, we would reference it as follows:myList[0]
firstItem = 0 myList[firstItem]
Downloading the example code
You can download the example code files for all the Packt books that you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register in order to have the files e-mailed to you directly.
In computer science, assigning commonly used numbers to an easy-to-remember variable is a common practice. These variables are called constants.
So, for our example, we'll assign constants for some common elements used for all the cities. All cities will have a name, one or more points, and a population count:
NAME = 0 POINTS = 1 POP = 2
state = ["COLORADO", [[-109, 37],[-109, 41],[-102, 41],[-102, 37]], 5187582]
cities = [] cities.append(["DENVER",[-104.98, 39.74], 634265]) cities.append(["BOULDER",[-105.27, 40.02], 98889]) cities.append(["DURANGO",[-107.88,37.28], 17069])
map_width = 400 map_height = 300
minx = 180 maxx = -180 miny = 90 maxy = -90 forx,y in state[POINTS]: if x < minx: minx = x elif x > maxx: maxx = x if y < miny: miny = y elif y > maxy: maxy = y
dist_x = maxx - minx dist_y = maxy - miny x_ratio = map_width / dist_x y_ratio = map_height / dist_y
convert()
, is our only function in SimpleGIS
. It transforms a point in the map coordinates from one of our data layers to pixel coordinates using the previous calculations. You'll notice that, at the end, we divide the map width and height in half and subtract it from the final conversion to account for the unusual center origin of the turtle graphics canvas. Every geospatial program has some form of this function:def convert(point): lon = point[0] lat = point[1] x = map_width - ((maxx - lon) * x_ratio) y = map_height - ((maxy - lat) * y_ratio) # Python turtle graphics start in the # middle of the screen # so we must offset the points so they are centered x = x - (map_width/2) y = y - (map_height/2) return [x,y]
turtle
module uses the concept of a cursor called a pen. Moving the cursor around the canvas is exactly the same as moving a pen around a piece of paper. The cursor will draw a line when you move it. So, you'll notice that throughout the code, we use the t.up()
and t.down()
commands to pick the pen up when we want to move to a new location and put it down when we're ready to draw. We have some important steps in this section. As the border of Colorado is a polygon, we must draw a line between the last point and first point to close the polygon. We can also leave out the closing step and just add a duplicate point to the Colorado dataset. Once we draw the state, we'll use the write()
method to label the polygon:t.up() first_pixel = None for point in state[POINTS]: pixel = convert(point) if not first_pixel: first_pixel = pixel t.goto(pixel) t.down() t.goto(first_pixel) t.up() t.goto([0,0]) t.write(state[NAME], align="center", font=("Arial",16,"bold"))
dot()
method to plot a small circle at the pixel coordinate returned by our SimpleGISconvert()
function. We'll then label the dot with the city's name and add the population. You'll notice that we must convert the population number to a string in order to use it in the turtle write()
method. To do so, we use Python's built-in function, str()
:#for city in cities: pixel = convert(city[POINTS]) t.up() t.goto(pixel) # Place a point for the city t.dot(10) # Label the city t.write(city[NAME] + ", Pop.: " + str(city[POP]), align="left") t.up()
min()
and max()
functions. These functions take a list as an argument and return the minimum and maximum values of this list. These functions have a special feature called a key argument that allows you to sort complex objects. As we are dealing with nested lists in our data model, we'll take advantage of the key argument in these functions. The key argument accepts a function that temporarily alters the list for evaluation before a final value is returned. In this case, we want to isolate the population values for comparison and then the points. We could write a whole new function to return the specified value, but we can use Python's lambda keyword instead. The lambda keyword defines an anonymous function that is used inline. Other Python functions can be used inline, for example, the string function, str()
, but they are not anonymous. This temporary function will isolate our value of interest.biggest_city = max(cities, key=lambda city:city[POP]) t.goto(0,-200) t.write("The biggest city is: " + biggest_city[NAME])
western_city = min(cities, key=lambda city:city[POINTS]) t.goto(0,-220) t.write("The western-most city is: " + western_city[NAME])
min()
function to select the smallest longitude value and this works because we represented our city locations as longitude and latitude pairs. It is possible to use different representations for points including possible representations where this code would need modification to work correctly. However, for our SimpleGIS
, we are using a common point representation to make it as intuitive as possible.These last two commands are just for clean up purposes. First, we hide the cursor. Then, we call the turtle done()
method, which will keep the turtle graphics window with our map open until we choose to close it using the close handle at the top of the window.
t.pen(shown=False) t.done()
Whether you followed along using the Python interpreter or you ran the complete program as a script, you should see the following map rendered in real time:
Congratulations! You have followed in the footsteps of Paleolithic hunters, the father of GIS Dr. Roger Tomlinson, geospatial pioneer Howard Fisher, and game-changing humanitarian programmers to create a functional, extensible, and technically complete geographic information system. It took less than 60 lines of pure Python code! You will be hard-pressed to find a programming language that can create a complete GIS using only its core libraries in such a finite amount of readable code as Python. Even if you did, it is highly unlikely that the language would survive the geospatial Python journey that you'll take through the rest of this book.
As you can see, there is lots of room for expansion of SimpleGIS
. Here are some other ways that you might expand this simple tool using the reference material for Tkinter and Python linked at the beginning of this section:
The possibilities are endless. SimpleGIS
can also be used as a way to quickly test and visualize geospatial algorithms that you come across. If you want to add more data layers, you can create more lists, but these lists would become difficult to manage. In this case, you can use another Python module included in the standard distribution. The SQLite
module provides a SQL-like database in Python that can be saved to disk or run in memory.