PyGRASS

PyGRASS is a library originally developed during the Google Summer of Code 2012. The PyGRASS library adds two main functionalities:

  • a Python interface through the ctypes binding of the C API of GRASS, to read and write natively GRASS GIS 7 data structures.
  • a GRASS GIS module interface using objects to check the parameters and execute the respective modules.

For further discussion about the implementation ideas and performance are presented in the article:

Zambelli, P.; Gebbert, S.; Ciolli, M. Pygrass: An Object Oriented Python Application Programming Interface (API) for Geographic Resources Analysis Support System (GRASS) Geographic Information System (GIS). ISPRS Int. J. Geo-Inf. 2013, 2, 201-219.

Playing with the C API of GRASS GIS through Python

Standard scripting with GRASS modules in Python may sometime seem discouraging especially when you have to do conceptually simple things like: iterate over vector features or raster rows/columns. With the standard scripting approach you would have to use several workaround such as:

  • create temporary files,
  • read and parse these files,
  • work with the data,
  • and remove all unnecessary files that were created by the script.

Most of GRASS GIS is written in C programming language. Using the C API, all this is much more simple since you can directly work on GRASS GIS data and do just what you need to do. However, you perhaps want to stick to Python. Here, the PyGRASS library introduced several objects that allow to interact directly with the data using the underlying C API of GRASS GIS. Three main areas are covered so far:

  • vector maps,
  • raster maps,
  • mapsets, and region.

Working with vector data

Creating a new vector map

Import the necessary classes:


In [ ]:
from grass.pygrass.vector import VectorTopo
from grass.pygrass.vector.geometry import Point

Create an instance of the vector map, with:


In [ ]:
view_points = VectorTopo('view_points')

Open the map in write mode, with:


In [ ]:
view_points.open(mode='w')

Create some vector geometry features, like two points:


In [ ]:
point1 = Point(635818.8, 221342.4)
point2 = Point(633627.7, 227050.2)

Add the above two points to the new vector map with:


In [ ]:
view_points.write(point1)
view_points.write(point2)

Finally close the vector map, with:


In [ ]:
view_points.close()

Now do the same thing using the context manager syntax provided by with, and creating and setting also the attribute table, with:


In [ ]:
# Define the columns of the new vector map
cols = [(u'cat',       'INTEGER PRIMARY KEY'),
        (u'name',      'TEXT')]

with VectorTopo('view_points', mode='w', tab_cols=cols, overwrite=True) as view_points:
    # save the point and the attribute
    view_points.write(point1, ('pub', ))
    view_points.write(point2, ('restaurant', ))
    # save the changes to the database
    view_points.table.conn.commit()

Note: we don't need to close the vector map because it is already closed by the context manager.

Reading an existing vector map


In [ ]:
with VectorTopo('view_points', mode='r') as points:
    for point in points:
        print(point, point.attrs['name'])

Working with raster data

A class with similar behaviour has been created for raster maps, import this class with:


In [ ]:
from grass.pygrass.raster import RasterRow

Reading an existing raster map

Instanziate a raster class with:


In [ ]:
elev = RasterRow('elevation', mapset='PERMANENT')

Similar to the vector approach shown above, we can open a raster map with:


In [ ]:
elev.open(mode='r')

Now we have row by row access to the whole raster map. Below we show an example to access the first row:


In [ ]:
row = elev[0]

In [ ]:
row

Since row instances inherit from the numpy arrays, you can treat them as numpy arrays:


In [ ]:
row * 2

In [ ]:
row.shape

To convert the whole raster map to a numpy array, and then back to a GRASS GIS raster map, we can define two functions like:


In [ ]:
import numpy as np

from grass.pygrass.raster.buffer import Buffer
from grass.pygrass.gis.region import Region

def raster2numpy(rastname, mapset=''):
    """Return a numpy array from a raster map"""
    with RasterRow(rastname, mapset=mapset, mode='r') as rast:
        return np.array(rast)


def numpy2raster(array, mtype, rastname, overwrite=False):
    """Save a numpy array to a raster map"""
    reg = Region()
    if (reg.rows, reg.cols) != array.shape:
        msg = "Region and array are different: %r != %r"
        raise TypeError(msg % ((reg.rows, reg.cols), array.shape))
    with RasterRow(rastname, mode='w', mtype=mtype, overwrite=overwrite) as new:
        newrow = Buffer((array.shape[1],), mtype=mtype)
        for row in array:
            newrow[:] = row[:]
            new.put_row(newrow)

In [ ]:
elev = raster2numpy('elevation', mapset='PERMANENT')

In [ ]:
elev

In [ ]:
numpy2raster(elev * 2, mtype='FCELL', rastname='doubled_elev', overwrite=True)

In [ ]:
# import a function to render the results
from render import view

from grass.pygrass.modules.shortcuts import raster as r

# set the colors of the input and output maps to a predefined elevation color table
r.colors(map='elevation@PERMANENT,doubled_elev', color='elevation')

In [ ]:
view(['elevation@PERMANENT'])

In [ ]:
view(['doubled_elev'])

Calling GRASS GIS modules

Import the Module class from the pygrass library, with:


In [ ]:
from grass.pygrass.modules import Module

Execute a GRASS GIS module with (here: viewshed calculation):


In [ ]:
viewshed = Module('r.viewshed', input='elevation', output='viewshed2', coordinates=(635818.8, 221342.4), flags='c', overwrite=True)

We can play with the python instance asking for inputs and outputs parameters and flags


In [ ]:
viewshed.inputs.input

In [ ]:
viewshed.inputs.coordinates

In [ ]:
viewshed.outputs.output

In [ ]:
viewshed.flags.c

We can change the parameters values, parameterwise with:


In [ ]:
viewshed.inputs.coordinates = [(635825, 221350)]

Or re-execute the module passing only the parameter(s) that need(s) to be different:


In [ ]:
viewshed(output='viewshed_newpoint')

We can retrieve the command string that will be execute in both bash and Python style:


In [ ]:
viewshed.get_bash()

In [ ]:
viewshed.get_python()

Extra parameters of the Module class

run_

The run_ parameter defines if the module must be execute or not, the default value is True.

finish_

The finish_ parameter defines if the module has to wait the end of the process, default is True

env_

The env_ parameter defines the environmental variable that will be set and used by the process.

stdin_, stdout_, stderr_

The parameters stdin_, stdout_ and stderr_ are used to pass or capture the input or the output of the GRASS GIS modules.

Example


In [ ]:
import subprocess as sub

# Another way to use the Module class objects is through the shortcuts
# import the shortcuts for the general modules
from grass.pygrass.modules.shortcuts import general as g

Now we can call and capture the stdout of a GRASS GIS modules, with:


In [ ]:
greg = g.region(flags='p', stdout_=sub.PIPE)
print(greg.outputs.stdout)