Chapter 7. Python and Elevation Data

Elevation data is one of the most fascinating types of geospatial data. It represents many different types of data sources and formats. It can display properties of both vector and raster data resulting in unique data products. Elevation data can be used for terrain visualization, land cover classification, hydrology modeling, transportation routing, feature extraction, and many other purposes.

You can't perform all of these options with both raster and vector data but as elevation data is three-dimensional, containing x, y, and z coordinates, you can often get more out of this data than any other type.

In this chapter, you're going to learn to read and write elevation data in both raster and vector formats. We'll also create some derivative products. The topics that we'll cover are as follows:

  • Using ASCII Grid elevation data files
  • Creating shaded relief images
  • Creating elevation contours
  • Gridding LIDAR data
  • Creating a 3D mesh

ASCII Grid files

For most of this chapter, we'll use ASCII Grid files or ASCIIGRID. These files are a type of raster data usually associated with elevation data. This grid format stores data as text in equal-sized square rows and columns with a simple header. Each cell in a row/column stores a single numeric value, which can represent some feature of terrain, such as elevation, slope, or flow direction. The simplicity makes it an easy-to-use, platform-independent raster format. This format is described in the ASCII Grids section in Chapter 2, Geospatial Data.

Throughout the book, we've relied on GDAL and, to some extent, PIL to read and write geospatial raster data including the gdal_array module to load raster data in the NumPy arrays. ASCI Grid allows us to read and write rasters using only Python or even NumPy because it is simple, plain text.

Tip

As a reminder, some elevation datasets use image formats to store elevation data. Most image formats only support 8-bit values ranging between 0 to 255; however, some formats, including TIFF, can store larger values. Geospatial software can typically display these datasets; however, traditional image software and libraries usually do not. For simplicity in this chapter, we'll mostly stick to the ASCI Grid format for data, which is both human and machine readable as well as being widely supported.

Reading grids

NumPy has the ability to read the ASCI Grid format directly using its loadtxt() method designed to read arrays from text files. The first six lines consist of the header, which are not part of the array. The following lines are a sample of a grid header:

ncols        250
nrows        250
xllcorner    277750.0
yllcorner    6122250.0
cellsize     1.0
NODATA_value      -9999

Line 1 contains the number of columns in the grid, which is synonymous with the x axis. Line 2 represents the y axis described as a number of rows. Line 3 represents the x coordinate of the lower left corner, which is the minimum x value in meters. Line 4 is the corresponding minimum y value in the lower left corner of the grid. Line 5 is the cell size or resolution of the raster. As the cells are square, only one size value is needed, as opposed to the separate x and y resolution values in most geospatial rasters. The fifth line is the NODATA_value, which is a number assigned to any cell for which a value is not provided. Geospatial software ignores these cells for calculations and often allows special display settings for it, such as coloring them black or transparent. The -9999 value is a common no data placeholder value used in the industry, which is easy to detect in software but can be arbitrarily selected. Elevation with negative values (that is, bathymetry) may have valid data at -9999 meters, for instance, and may select 9999 or other values. As long as this value is defined in the header, most software will have no problems. In some examples, we'll use the number zero; however, zero can often also be a valid data value.

The numpy.loadtxt() method includes an argument called skiprows, which allows you to specify a number of lines in the file to be skipped before reading the array values. To try this technique, you can download a sample grid file called myGrid.asc from http://git.io/vYapU.

So, for myGrid.asc, we would use the following code:

myArray  = numpy.loadtxt("myGrid.asc", skiprows=6)

This line results in the myArray variable containing a numpy array derived from the ASCIIGRID myGrid.asc file. The ASC filename extension is used by the ASCIIGRID format. This code works great but there's one problem. NumPy allows us to skip the header but not keep it. We need to keep it in order to have a spatial reference for our data to process as well as save this grid or create a new one.

To solve this problem, we'll use Python's built-in linecache module to grab the header. We could open the file, loop through the lines, store each one in a variable, and then close the file. However, linecache reduces the solution to a single line. The following line reads the first line in the file to a variable called line1:

import linecache
line1 = linecache.getline("myGrid.asc", 1)

In the examples in this chapter, we'll use this technique to create a simple header processor that can parse these headers to Python variables in just a few lines.

Writing grids

Writing grids in NumPy is just as easy as reading them. We use the corresponding numpy.savetxt() function to save a grid to a text file. The only catch is that we must build and add the six lines of header information before we dump the array to the file. This process is slightly different if you are using NumPy versions before 1.7 or after. In either case, you build the header as a string first. If you are using NumPy 1.7 or later, the savetext() method has an optional argument called header, which lets you specify a string as an argument. You can quickly check your NumPy version from the command line using the following command:

python -c "import numpy;print(numpy.__version__)"
1.9.2

The backward-compatible method is to open a file, write the header, and then dump the array. Here is a sample of the version 1.7 approach to save an array called myArray to an ASCII Grid file called myGrid.asc:

header = "ncols        {}
".format(myArray.shape[1])
header += "nrows        {}
".format(myArray.shape[0])
header += "xllcorner    277750.0
"
header += "yllcorner    6122250.0
"
header += "cellsize     1.0
"
header += "NODATA_value      -9999"
numpy.savetxt("myGrid.asc", myArray, header=header, fmt="%1.2f")

We make use of Python format strings, which allow you to put placeholders in a string to format the Python objects to be inserted. The {} format variable turns whatever object you reference into a string. In this case, we are referencing the number of columns and rows in the array. In NumPy, an array has both a size and shape property. The size property returns an integer for the number of values in the array. The shape property returns a tuple with the number of rows and columns, respectively. So, in the preceding example, we use the shape property tuple to add the row and column counts to the header of our ASCII Grid. Notice that we also add a trailing newline character for each line ( ). There is no reason to change the x and y values, cell size, or no data value unless we altered them in the script. The savetxt() method also has a fmt argument, which allows you to use Python format strings to specify how the array values are written. In this case, the %1.2f value specifies floats with at least one number and no more than two decimal places.

The backward-compatible version for NumPy, before 1.6, builds the header string in the same way but creates the file handle first:

import numpy
f = open("myGrid.asc", "w")
f.write(header)
numpy.savetxt(f, myArray, fmt="%1.2f")
f.close()

In the examples in this chapter, we'll introduce Python's with statement as an approach to write files, which provides you with more graceful file management by ensuring that the files are closed properly. If any exceptions are thrown, the file is still closed cleanly:

with open("myGrid.asc", "w") as f:
  f.write(header)
  numpy.savetxt(f, str(myArray), fmt="%1.2f")

As you'll see in the upcoming examples, this ability to produce valid geospatial data files using only NumPy is quite powerful. In the next couple of examples, we'll be using an ASCIIGRID Digital Elevation Model (DEM) of a mountainous area near Vancouver, British Columbia in Canada. You can download this sample as a ZIP file at the following URL:

http://git.io/vYwUX

The following image is the raw DEM colorized using QGIS with a color ramp that makes the lower elevation values dark blue and higher elevation values bright red:

Writing grids

While we can conceptually understand the data in this way, it is not an intuitive way to visualize the data. Let's see if we can do better.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset