Working with GRASS without starting it explicitly: Difference between revisions

From GRASS-Wiki
Jump to navigation Jump to search
(Attempt to cleanup the page (remove old GRASS 7 cruft))
 
(26 intermediate revisions by 5 users not shown)
Line 1: Line 1:
GRASS modules and imports of GRASS Python packages works only in a specific environmental settings (GRASS session). This settings is ensured by starting GRASS GIS application which prepares this GRASS session for you. It is possible to set up you system in the way GRASS session will be always active (GRASS is not running, just the environmental settings is ready). People often prefer to set up the GRASS environment in their script or program and this is what this article discuss. However, it must be noted that the the generally preferred (and easy) way is to create scripts and programs as GRASS modules which means that your don't have to bother with setting up the environment since the GRASS module should be always invoked only in GRASS session.
GRASS GIS modules and the import of GRASS Python packages works only in a specific environmental settings (GRASS session). This settings is ensured by starting GRASS GIS application which prepares this GRASS session for you. It is possible to set up you system in the way that the GRASS session will be always active (i.e. the GRASS startup script is not running, just the environmental settings are done). People often prefer to set up the GRASS environment in their script or program and this is what this article discuss. However, it must be noted that the the generally preferred (and easy) way is to create scripts and programs as GRASS modules which means that your don't have to bother with setting up the environment since the GRASS module should be always invoked only in GRASS session.


== GRASS sessions ==
== GRASS sessions ==


It is possible to access GRASS modules without explicitly starting a "GRASS session". GRASS libraries require certain [http://grass.osgeo.org/grass70/manuals/variables.html environment variables] to be set. In fact a "GRASS session" is just a set of processes (e.g. a shell and/or GUI) which have the necessary environment settings, specifically:
It is possible to access GRASS modules without explicitly starting a "GRASS session". GRASS libraries require certain [https://grass.osgeo.org/grass-stable/manuals/variables.html environment variables] to be set. In fact a "GRASS session" is just a set of processes (e.g. a shell and/or GUI) which have the necessary environment settings, specifically:


* '''GISBASE''' needs to be set to the top-level directory of the GRASS installation.
* '''GISBASE''' needs to be set to the top-level directory of the GRASS installation.
Line 9: Line 9:
* '''PATH''' needs to include '''$GISBASE/bin''' and '''$GISBASE/scripts'''.
* '''PATH''' needs to include '''$GISBASE/bin''' and '''$GISBASE/scripts'''.


If the GRASS libraries are shared libraries, the loader needs to be able to find them. This normally means that '''LD_LIBRARY_PATH''' (Linux, Solaris), '''DYLD_LIBRARY_PATH''' (MacOSX) or '''PATH''' (Windows) need to contain '''$GISBASE/lib''', although there are other means to the same end (e.g. on Linux, putting $GISBASE/lib (with $GISBASE replaced by its actual value) into /etc/ld.so.conf then running ldconfig).
If the GRASS libraries are shared libraries, the loader needs to be able to find them. This normally means that '''LD_LIBRARY_PATH''' (Linux, ...), '''DYLD_LIBRARY_PATH''' (MacOSX) or '''PATH''' (Windows) need to contain '''$GISBASE/lib''', although there are other means to the same end (e.g. on Linux, putting $GISBASE/lib (with $GISBASE replaced by its actual value) into /etc/ld.so.conf then running ldconfig).


Some libraries and modules use other variables. More information for most of them is available in the file ''$GISBASE/docs/html/variables.html''. The display libraries used by ''d.*'' commands use additional variables, which are documented along with the individual drivers.
Some libraries and modules use other variables. More information for most of them is available in the file ''$GISBASE/docs/html/variables.html''. The display libraries used by ''d.*'' commands use additional variables, which are documented along with the individual drivers.
Line 19: Line 19:
See the [[GRASS_and_Shell#GRASS_Batch_jobs|Batch jobs]] section of the GRASS shell-help wiki page for details.
See the [[GRASS_and_Shell#GRASS_Batch_jobs|Batch jobs]] section of the GRASS shell-help wiki page for details.


=== Accessing GRASS modules without starting a GRASS session ===
=== Starting a GRASS session and terminating it automatically ===


==== Python examples ====
A GRASS session can be started and terminated automatically with the -e flag.


See also [[GRASS Python Scripting Library]]
Since GRASS GIS 7 you can execute commands from outside by passing the command to be executed (or a script with a collection of GRASS GIS commands) to the --exec flag:


===== Python: GRASS GIS 7 with existing location =====
Creating a new Location based on a geodata file's projection (-c) and exit (-e) immediately:
<source lang="bash">
grass -c elevation.tiff -e /path/to/grassdata/test1/
</source>


The script initializes the session and lists available raster and vector maps:
Linking external raster data to PERMANENT Mapset:
<source lang="bash">
grass /path/to/grassdata/test1/PERMANENT/ -e --exec r.external input=basins.tiff output=basins
grass /path/to/grassdata/test1/PERMANENT/ -e --exec r.external input=elevation.tiff output=elevation
</source>


<source lang=python>
Get statistics for one raster map:
#!/usr/bin/env python
<source lang="bash">
grass /path/to/grassdata/test1/PERMANENT/ -e --exec r.univar map=elevation
</source>


import os
==== Start a GRASS session but do not terminate it automatically ====
import sys
import subprocess


# path to the GRASS GIS launch script
Compare the rasters visually:
# MS Windows
<source lang="bash">
grass7bin_win = r'C:\OSGeo4W\bin\grass70svn.bat'
grass /path/to/grassdata/test1/PERMANENT/ --exec g.gui.mapswipe first=elevation second=basins
# uncomment when using standalone WinGRASS installer
</source>
# grass7bin_win = r'C:\Program Files (x86)\GRASS GIS 7.0.0beta3\grass70.bat'
# Linux
grass7bin_lin = 'grass70'
# Mac OS X
# this is TODO
grass7bin_mac = '/Applications/GRASS/GRASS-7.0.app/'


# DATA
Read more in the [https://grass.osgeo.org/grass-stable/manuals/grass.html#batch-jobs-with-the-exec-interface manual].
# define GRASS DATABASE
# add your path to grassdata (GRASS GIS database) directory
gisdb = os.path.join(os.path.expanduser("~"), "grassdata")
# the following path is the default path on MS Windows
# gisdb = os.path.join(os.path.expanduser("~"), "Documents/grassdata")


# specify (existing) location and mapset
==== Python examples ====
location = "nc_spm_08"
mapset  = "user1"


For details on GRASS GIS Python programming, see also [[GRASS Python Scripting Library]].


########### SOFTWARE
===== Python: GRASS GIS 7 with an external library: grass-session =====
if sys.platform.startswith('linux'):
    # we assume that the GRASS GIS start script is available and in the PATH
    # query GRASS 7 itself for its GISBASE
    grass7bin = grass7bin_lin
elif sys.platform.startswith('win'):
    grass7bin = grass7bin_win
else:
    raise OSError('Platform not configured.')


# query GRASS 7 itself for its GISBASE
The [https://github.com/zarch/grass-session "grass-session" Python library] is an easy way to control GRASS GIS from "outside"-Python. To install the current version, run:
startcmd = [grass7bin, '--config', 'path']


p = subprocess.Popen(startcmd, shell=False,
<source lang="bash">
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# see also: https://pypi.org/project/grass-session/
out, err = p.communicate()
if p.returncode != 0:
    print >>sys.stderr, "ERROR: Cannot find GRASS GIS 7 start script (%s)" % startcmd
    sys.exit(-1)
gisbase = out.strip('\n\r')


# Set GISBASE environment variable
pip install grass-session
os.environ['GISBASE'] = gisbase
# for the latest development version use:
# the following not needed with trunk
# pip install git+https://github.com/zarch/grass-session.git
os.environ['PATH'] += os.pathsep + os.path.join(gisbase, 'extrabin')
</source>
# add path to GRASS addons
home = os.path.expanduser("~")
os.environ['PATH'] += os.pathsep + os.path.join(home, '.grass7', 'addons', 'scripts')


# define GRASS-Python environment
Then write
gpydir = os.path.join(gisbase, "etc", "python")
<source lang="python">
sys.path.append(gpydir)
#!/usr/bin/env python
# filename: test_session.py


########### DATA
from grass_session import Session
# Set GISDBASE environment variable
from grass.script import core as gcore
os.environ['GISDBASE'] = gisdb
# import GRASS Python bindings (see also pygrass)
import grass.script as gscript
import grass.script.setup as gsetup
###########
# launch session
gsetup.init(gisbase,
            gisdb, location, mapset)
gscript.message('Current GRASS GIS 7 environment:')
print gscript.gisenv()
gscript.message('Available raster maps:')
for rast in gscript.list_strings(type = 'rast'):
    print rast
gscript.message('Available vector maps:')
for vect in gscript.list_strings(type = 'vect'):
    print vect
</source>


===== Python: GRASS GIS 7 without existing location using metadata only =====
# create a new location from EPSG code (can also be a GeoTIFF or SHP or ... file)
with Session(gisdb="/tmp", location="mylocation",
            create_opts="EPSG:4326"):
  # run something in PERMANENT mapset:
  print(gcore.parse_command("g.gisenv", flags="s"))
# expected output:
# {u'GISDBASE': u"'/tmp/';",
#  u'LOCATION_NAME': u"'mylocation';",
#  u'MAPSET': u"'PERMANENT';",}


The script initializes the session, creates a temporary GRASS location and lists available raster and vector maps:
# create a new mapset in an existing location
with Session(gisdb="/tmp", location="mylocation", mapset="test",
            create_opts=""):
    # do something in the test mapset.
    print(gcore.parse_command("g.gisenv", flags="s"))
# expected output:
# {u'GISDBASE': u"'/tmp/';",
#  u'LOCATION_NAME': u"'mylocation';",
#  u'MAPSET': u"'test';",}


<source lang=python>
</source>
#!/usr/bin/env python


# Python script to generate a new GRASS GIS 7 location simply from metadata
The grass-session library looks at the <tt>GRASSBIN</tt> environmental variable. If the variable is not set, the library tries to use the current stable release of GRASS GIS (e.g. <tt>grass84</tt> on Linux for the 8.4 release).
# Markus Neteler, 2014
For instance, to execute the previous example using a custom installation of 8.5 version, write:


# ?? LINUX USAGE: First set LIBRARY SEARCH PATH
<source lang="bash">
#export LD_LIBRARY_PATH=$(grass70 --config path)/lib
# WAY 1
#python start_grass7_create_new_location_ADVANCED.py
# set variable "on the fly" for python call
GRASSBIN="$HOME/bin/grass85 python test_session.py"


# some predefined variables
# WAY 2
export GRASSBIN="$HOME/bin/grass85"
python3 test_session.py
{'GISDBASE': "'/tmp';", 'LOCATION_NAME': "'mylocation';", 'MAPSET': "'PERMANENT';"}
{'GISDBASE': "'/tmp';", 'LOCATION_NAME': "'mylocation';", 'MAPSET': "'test';"}
</source>


# Windows
See below for a solution with docker.
grass7path = r'C:\OSGeo4W\apps\grass\grass-7.0.svn'
grass7bin_win = r'C:\OSGeo4W\bin\grass70svn.bat'
# Linux
grass7bin_lin = 'grass70'
# MacOSX
grass7bin_mac = '/Applications/GRASS/GRASS-7.1.app/'
#myepsg = '4326' # latlong
myepsg = '3044' # ETRS-TM32, http://spatialreference.org/ref/epsg/3044/
#myfile = '/home/neteler/markus_repo/books/kluwerbook/data3rd/lidar/lidar_raleigh_nc_spm.shp'
myfile = '/data/maps/world_natural_earth_250m/europe_north_east.tif'
#myfile = r'C:\Dati\Padergnone\square_p95.tif'


###########
====== Using 'grass-session' with PyGRASS ======
import os
import sys
import subprocess
import shutil
import binascii
import tempfile


########### SOFTWARE
If you need to use python libraries that are using ctypes, run it like this for example:
if sys.platform.startswith('linux'):
    # we assume that the GRASS GIS start script is available and in the PATH
    # query GRASS 7 itself for its GISBASE
    grass7bin = grass7bin_lin
elif sys.platform.startswith('win'):
    grass7bin = grass7bin_win
else:
    OSError('Platform not configured.')


startcmd = grass7bin + ' --config path'
<source lang="python">
# file: test_session_ctypes.py
import grass_session
from grass.pygrass.vector import VectorTopo


p = subprocess.Popen(startcmd, shell=True,
print('DONE!')
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
</source>
out, err = p.communicate()
if p.returncode != 0:
print >>sys.stderr, 'ERROR: %s' % err
print >>sys.stderr, "ERROR: Cannot find GRASS GIS 7 start script (%s)" % startcmd
sys.exit(-1)
if sys.platform.startswith('linux'):
gisbase = out.strip('\n')
elif sys.platform.startswith('win'):
    if out.find("OSGEO4W home is") != -1:
gisbase = out.strip().split('\n')[1]
    else:
gisbase = out.strip('\n')
    os.environ['GRASS_SH'] = os.path.join(gisbase, 'msys', 'bin', 'sh.exe')


# Set GISBASE environment variable
The above script will raise an exception because is not possible to dynamically import the linked library in the same process.
os.environ['GISBASE'] = gisbase
# define GRASS-Python environment
gpydir = os.path.join(gisbase, "etc", "python")
sys.path.append(gpydir)
########
# define GRASS DATABASE
if sys.platform.startswith('win'):
    gisdb = os.path.join(os.getenv('APPDATA', 'grassdata')
else:
    gisdb = os.path.join(os.getenv('HOME', 'grassdata')


# override for now with TEMP dir
<source lang="bash">
gisdb = os.path.join(tempfile.gettempdir(), 'grassdata')
$ GRASSBIN=/home/pietro/.local/bin/grass python test.py
try:
GRASSBIN: /home/pietro/.local/bin/grass
     os.stat(gisdb)
GISBASE: /home/pietro/.local/grass
except:
Traceback (most recent call last):
     os.mkdir(gisdb)
  File "test.py", line 3, in <module>
    from grass.pygrass.vector import VectorTopo
  File "/home/pietro/.local/grass/etc/python/grass/pygrass/vector/__init__.py", line 5, in <module>
    import grass.lib.gis as libgis
  File "/home/pietro/.local/grass/etc/python/grass/lib/gis.py", line 23, in <module>
    _libs["grass_gis."] = load_library("grass_gis.")
  File "/home/pietro/.local/grass/etc/python/grass/lib/ctypes_loader.py", line 62, in load_library
     return self.load(path)
  File "/home/pietro/.local/grass/etc/python/grass/lib/ctypes_loader.py", line 78, in load
     raise ImportError(e)
ImportError: libgrass_datetime..so: cannot open shared object file: No such file or directory
</source>


# location/mapset: use random names for batch jobs
But you can set the <tt>LD_LIBRARY_PATH</tt> variable before launching the program. For instance from the command line you can define:
string_length = 16
location = binascii.hexlify(os.urandom(string_length))
mapset  = 'PERMANENT'
location_path = os.path.join(gisdb, location)


# Create new location (we assume that grass7bin is in the PATH)
<source lang="bash">
#  from EPSG code:
$ LD_LIBRARY_PATH=/home/pietro/.local/grass/lib \
startcmd = grass7bin + ' -c epsg:' + myepsg + ' -e ' + location_path
  GRASSBIN=/home/pietro/.local/bin/grass \
#  from SHAPE or GeoTIFF file
  python test_session_ctypes.py
#startcmd = grass7bin + ' -c ' + myfile + ' -e ' + location_path
GRASSBIN: /home/pietro/.local/bin/grass
GISBASE: /home/pietro/.local/grass
DONE!
</source>


print startcmd
p = subprocess.Popen(startcmd, shell=True,
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if p.returncode != 0:
    print >>sys.stderr, 'ERROR: %s' % err
    print >>sys.stderr, 'ERROR: Cannot generate location (%s)' % startcmd
    sys.exit(-1)
else:
    print 'Created location %s' % location_path


# Now the location with PERMANENT mapset exists.
The use of the <tt>with-statement</tt> close automatically the session.
If you have to manage more complex workflow you can manually define the opening and closing actions.
For example:


########
<source lang="bash">
# Now we can use PyGRASS or GRASS Scripting library etc. after
import os
# having started the session with gsetup.init() etc


# Set GISDBASE environment variable
# import grass_session
os.environ['GISDBASE'] = gisdb
from grass_session import Session


# Linux: Set path to GRASS libs (TODO: NEEDED?)
# import grass python libraries
path = os.getenv('LD_LIBRARY_PATH')
from grass.pygrass.modules.shortcuts import general as g
dir  = os.path.join(gisbase, 'lib')
if path:
    path = dir + os.pathsep + path
else:
    path = dir
os.environ['LD_LIBRARY_PATH'] = path


# language
os.environ['LANG'] = 'en_US'
os.environ['LOCALE'] = 'C'


# Windows: NEEDED?
# set some common environmental variables, like:
#path = os.getenv('PYTHONPATH')
os.environ.update(dict(GRASS_COMPRESS_NULLS='1',
#dirr = os.path.join(gisbase, 'etc', 'python')
                      GRASS_COMPRESSOR='ZSTD'))
#if path:
#    path = dirr + os.pathsep + path
#else:
#    path = dirr
#os.environ['PYTHONPATH'] = path


#print os.environ
# create a PERMANENT mapset
# create a Session instance
PERMANENT = Session()
PERMANENT.open(gisdb='/tmp', location='mytest',
              create_opts='EPSG:4326')


## Import GRASS Python bindings
import grass.script as grass
import grass.script.setup as gsetup


###########
# execute some command inside PERMANENT
# Launch session and do something
g.mapsets(flags="l")
gsetup.init(gisbase, gisdb, location, mapset)
g.list(type="raster", flags="m")


# say hello
# exit from PERMANENT
grass.message('--- GRASS GIS 7: Current GRASS GIS 7 environment:')
PERMANENT.close()
print grass.gisenv()


# do something in GRASS now...
# create a new mapset in the same location
user = Session()
user.open(gisdb='/tmp', location='mytest', mapset='user',
              create_opts='')


grass.message('--- GRASS GIS 7: Checking projection info:')
# execute some command inside user
in_proj = grass.read_command('g.proj', flags = 'jf')
g.mapsets(flags="l")
g.list(type="raster", flags="m")


# selective proj parameter printing
# exit from user
kv = grass.parse_key_val(in_proj)
user.close()
print kv
</source>
print kv['+proj']


# print full proj parameter printing
===== Hint: find the path to the GRASS GIS package start script =====
in_proj = in_proj.strip()
grass.message("--- Found projection parameters: '%s'" % in_proj)


# show current region:
Hint: in order to find the '''path to the GRASS GIS package''', launch it with <tt> --config path</tt>:
grass.message('--- GRASS GIS 7: Checking computational region info:')
in_region = grass.region()
grass.message("--- Computational region: '%s'" % in_region)


# do something else: r.mapcalc, v.rectify, ...
<source lang="bash">
# Linux
grass --config path
/usr/bin/grass84


# Finally remove the temporary batch location from disk
# Windows
print 'Removing location %s' % location_path
C:\>grass.bat --config path
shutil.rmtree(location_path)
C:\OSGeo4W\apps\grass\grass84
</source>


sys.exit(0)
===== Python: GRASS GIS 8 with existing location =====
</source>


===== Python: GRASS GIS 6 =====
See the example in the [https://grass.osgeo.org/grass-stable/manuals/libpython/script.html#module-script.setup documentation] or the script which initializes the session and lists available raster and vector maps:


The script initializes the session and lists available raster maps:
<source lang="python">
#!/usr/bin/env python


<source lang=python>
import os
import os
import subprocess
import sys
import sys


gisbase = os.environ['GISBASE'] = "/usr/local/src/grass_trunk/dist.x86_64-unknown-linux-gnu"
# define GRASS data settings (adapt to your needs)
grassdata = "/home/mneteler/grassdata/"
location = "nc_spm_08_grass7"
mapset = "user1"
 
# Python path: we ask GRASS GIS where its Python packages are
sys.path.append(
    subprocess.check_output(["grass", "--config", "python_path"], text=True).strip()
)
 
# Import GRASS Python bindings
import grass.script as gs
 
# full path to new project (formerly: location)
project = "/tmp/grassproject_epsg_25832"
gs.create_project(project, epsg="25832")
 
# initialize project
gs.setup.init(project)
</source>
 
At this point we are in a running GRASS GIS session, let's verify.
 
<source lang="python">
# show current GRASS GIS settings
print("GRASS GIS session: tests for PROJ, GDAL, PDAL, GRASS GIS")
print(gs.parse_command("g.gisenv", flags="s"))
</source>
 
Now we may also import further functionality (e.g. [https://grass.osgeo.org/grass-stable/manuals/libpython/grass.pygrass.vector.html?highlight=vectortopo VectorTopo of pygrass]):


gisdbase = os.path.join(os.environ['HOME'], "grassdata")
<source lang="python">
location = "nc_spm_08"
from grass.pygrass.vector import VectorTopo
mapset  = "user1"
</source>


sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "python"))
and can for example retrieve the topology of a vector map (using the [https://grass.osgeo.org/download/data/#NorthCarolinaDataset North Carolina dataset]):
import grass.script as grass
import grass.script.setup as gsetup


gsetup.init(gisbase,
<source lang="python">
            gisdbase, location, mapset)
test_vect = VectorTopo("zipcodes_wake")
test_vect.open(mode="r")
test_vect.num_primitive_of("point")
test_vect.num_primitive_of("line")
test_vect.num_primitive_of("centroid")
test_vect.num_primitive_of("boundary")
test_vect.close()
</source>


print grass.gisenv()
This should report:


grass.message('Raster maps:')
<source lang="python">
for rast in grass.list_strings(type = 'rast'):
>>> test_vect.num_primitive_of('point')
    print rast
0
>>> test_vect.num_primitive_of('line')
0
>>> test_vect.num_primitive_of('centroid')
48
>>> test_vect.num_primitive_of('boundary')
158
</source>
</source>


==== Bash examples (GNU/Linux) ====
==== Bash examples (GNU/Linux) ====


'''Note: see [[GRASS Batch jobs]] for a really easy approach.'''
'''''Note: see [[GRASS_and_Shell#GRASS_Batch_jobs|GRASS Batch jobs]] for a really easy approach.'''''


Below an example of a '''~/.bash_profile''' script to set the required settings in order to access GRASS commands outside of a GRASS-session. Specifically it is for GRASS 7 (commented parts for GRASS 6.4.2):
Below an example of a '''~/.bash_profile''' script to set the required settings in order to access GRASS commands outside of a GRASS GIS session


<source lang=bash>
<source lang=bash>
# example for GRASS 7
# GRASS environment
export GISBASE=/usr/local/grass-7.0.svn
export GISBASE=/usr/local/grass
# example for GRASS 6.4.2
export GRASS_VERSION=""
### export GISBASE=/usr/local/grass-6.4.2svn
export GRASS_VERSION="8.4"
 
export GISBASE="/home/mundialis/src/grass-8.4.2/dist.x86_64-pc-linux-gnu/"
# GRASS 7
export GRASS_GNUPLOT="gnuplot -persist"
export GRASS_VERSION="7.0.svn"
export GRASS_HTML_BROWSER="htmlview"
# GRASS 6.4.2
export GRASS_LD_LIBRARY_PATH="${GISBASE}/lib"
### export GRASS_VERSION="6.4.2svn"
export GRASS_PAGER="more"
export GRASS_PERL="/usr/bin/perl"
export GRASS_PROJSHARE="/usr/local/share/proj"
export GRASS_PYTHON="python"
export GRASS_SH="/bin/sh"
export GRASS_SHELL_PID="$$"


#generate GISRCRC
#generate GISRCRC
MYGISDBASE=$HOME/grassdata
MYGISDBASE=$HOME/grassdata
MYLOC=nc_spm_08
MYLOC=nc_spm_08_grass7
MYMAPSET=user1
MYMAPSET=user1


Line 367: Line 334:
export GRASS_HTML_BROWSER=firefox
export GRASS_HTML_BROWSER=firefox
export GRASS_PAGER=cat
export GRASS_PAGER=cat
export GRASS_WISH=wish
 
       
#For the temporal modules
export TGISDB_DRIVER=sqlite
export TGISDB_DATABASE=$MYGISDBASE/$MYLOC/PERMANENT/tgis/sqlite.db
 
# for fun, we can even set the shell prompt to contain a hint on GRASS GIS env being active
export PS1="[\u@\h \W G-$GRASS_VERSION]$ "
 
# system vars
export PATH="$GISBASE/bin:$GISBASE/scripts:$PATH"
export PATH="$GISBASE/bin:$GISBASE/scripts:$PATH"
export LD_LIBRARY_PATH="$GISBASE/lib"
export LD_LIBRARY_PATH="$GISBASE/lib"
Line 374: Line 348:
export PYTHONPATH="$GISBASE/etc/python:$PYTHONPATH"
export PYTHONPATH="$GISBASE/etc/python:$PYTHONPATH"
export MANPATH=$MANPATH:$GISBASE/man
export MANPATH=$MANPATH:$GISBASE/man
#For the temporal modules
export TGISDB_DRIVER=sqlite
export TGISDB_DATABASE=$MYGISDBASE/$MYLOC/PERMANENT/tgis/sqlite.db


# test a command
# test a command
g.list rast
g.list rast
 
v.info zipcodes_wake
######### below not needed ########
# GRASS 7
tmp=/tmp/grass7-"`whoami`"-$GIS_LOCK
# GRASS 6.4.2
### tmp=/tmp/grass6-"`whoami`"-$GIS_LOCK
 
export GISRC="$tmp/rc"
mkdir "$tmp"
 
# GRASS 7
cp ~/.grass7/rc "$GISRC"
# GRASS 6.4.2
### cp ~/.grassrc6 "$GISRC"
######### END below not needed ########
</source>
</source>


Line 411: Line 367:
=== Important notes ===
=== Important notes ===


Launching a grassXY session could still permit access on GRASS modules of the version that is set with the above script. The reason being is that grassXY scripts (e.g.: grass64, grass65, grass70) prepend the GRASS directories to '''PATH''', '''LD_LIBRARY_PATH''', etc. They won't remove any entries which are already there. A solution to this is to ''switch'' between GRASS versions by using a script like the following:
Launching a grassXY session could still permit access on GRASS modules of the version that is set with the above script. The reason being is that grassXY scripts (e.g.: grass84) prepend the GRASS directories to '''PATH''', '''LD_LIBRARY_PATH''', etc. They won't remove any entries which are already there. A solution to this is to ''switch'' between GRASS versions by using a script like the following:


<source lang="bash">
<source lang="bash">
Line 444: Line 400:


Using normal grass sessions commands are recorded in '''$GISDBASE/$LOCATION_NAME/$MAPSET/.bash_history'''. While working with "pure" bash, shell entries go in '''~/.bash_history'''. Merging existing "grass-bash_history(-ies)" with the bash history log, would probably be not a good idea. Perhaps it is wise(r) to use normal GRASS sessions when working on real projects. Nevertheless, it is upon the users preferences and expertise level to decide what is best.
Using normal grass sessions commands are recorded in '''$GISDBASE/$LOCATION_NAME/$MAPSET/.bash_history'''. While working with "pure" bash, shell entries go in '''~/.bash_history'''. Merging existing "grass-bash_history(-ies)" with the bash history log, would probably be not a good idea. Perhaps it is wise(r) to use normal GRASS sessions when working on real projects. Nevertheless, it is upon the users preferences and expertise level to decide what is best.
=== Hints and troubleshooting ===
Some hints for MS Windows users:
# The Python interpreter (python.exe) needs to be in the PATH
# Python needs to be associated with the .py extension
# PATHEXT needs to include .py if you want to be able to omit the extension
When everything works well, the installer should take care of all of these.
To manage parallel sessions on Linux, use process ID (PID) as lock file number and set the GIS_LOCK variable, for example
  export GIS_LOCK=$$
in Bash.


=== References ===
=== References ===
* Instructions and discussion on ''how to access GRASS mapsets outside of a GRASS session'' in the grass-user mailing list [http://n2.nabble.com/can-I-access-mapset-outside-of-grass-by-using-python-td3405902.html#a3405902].
* Instructions and discussion on [http://n2.nabble.com/can-I-access-mapset-outside-of-grass-by-using-python-td3405902.html#a3405902 how to access GRASS mapsets outside of a GRASS session] in the grass-user mailing list.
* removing duplicate entries in $PATH taken from [http://chunchung.blogspot.com/2007/11/remove-duplicate-paths-from-path-in.html]
* removing duplicate entries in $PATH taken from [http://chunchung.blogspot.com/2007/11/remove-duplicate-paths-from-path-in.html]


Line 458: Line 430:


* You can create a new mapset when starting GRASS with the -c flag. e.g.
* You can create a new mapset when starting GRASS with the -c flag. e.g.
  grass64 -c /path/to/location/new_mapset_name
  grass -c /path/to/location/new_mapset_name


=== Minimal locations ===
=== Minimal locations ===

Latest revision as of 14:15, 9 January 2026

GRASS GIS modules and the import of GRASS Python packages works only in a specific environmental settings (GRASS session). This settings is ensured by starting GRASS GIS application which prepares this GRASS session for you. It is possible to set up you system in the way that the GRASS session will be always active (i.e. the GRASS startup script is not running, just the environmental settings are done). People often prefer to set up the GRASS environment in their script or program and this is what this article discuss. However, it must be noted that the the generally preferred (and easy) way is to create scripts and programs as GRASS modules which means that your don't have to bother with setting up the environment since the GRASS module should be always invoked only in GRASS session.

GRASS sessions

It is possible to access GRASS modules without explicitly starting a "GRASS session". GRASS libraries require certain environment variables to be set. In fact a "GRASS session" is just a set of processes (e.g. a shell and/or GUI) which have the necessary environment settings, specifically:

  • GISBASE needs to be set to the top-level directory of the GRASS installation.
  • GISRC needs to contain the absolute path to a file containing settings for GISDBASE, LOCATION_NAME and MAPSET.
  • PATH needs to include $GISBASE/bin and $GISBASE/scripts.

If the GRASS libraries are shared libraries, the loader needs to be able to find them. This normally means that LD_LIBRARY_PATH (Linux, ...), DYLD_LIBRARY_PATH (MacOSX) or PATH (Windows) need to contain $GISBASE/lib, although there are other means to the same end (e.g. on Linux, putting $GISBASE/lib (with $GISBASE replaced by its actual value) into /etc/ld.so.conf then running ldconfig).

Some libraries and modules use other variables. More information for most of them is available in the file $GISBASE/docs/html/variables.html. The display libraries used by d.* commands use additional variables, which are documented along with the individual drivers.

Batch jobs

You can run GRASS scripts non-interactively from outside of a GRASS session with the GRASS_BATCH_JOB environment variable. When GRASS is started with this environment variable set it will automatically run the contents of the script given in the variable, then close the GRASS session when complete. In this way full lock-checking, GRASS variables setup, and temporary file cleaning tasks are still performed.

See the Batch jobs section of the GRASS shell-help wiki page for details.

Starting a GRASS session and terminating it automatically

A GRASS session can be started and terminated automatically with the -e flag.

Since GRASS GIS 7 you can execute commands from outside by passing the command to be executed (or a script with a collection of GRASS GIS commands) to the --exec flag:

Creating a new Location based on a geodata file's projection (-c) and exit (-e) immediately:

grass -c elevation.tiff -e /path/to/grassdata/test1/

Linking external raster data to PERMANENT Mapset:

grass /path/to/grassdata/test1/PERMANENT/ -e --exec r.external input=basins.tiff output=basins
grass /path/to/grassdata/test1/PERMANENT/ -e --exec r.external input=elevation.tiff output=elevation

Get statistics for one raster map:

grass /path/to/grassdata/test1/PERMANENT/ -e --exec r.univar map=elevation

Start a GRASS session but do not terminate it automatically

Compare the rasters visually:

grass /path/to/grassdata/test1/PERMANENT/ --exec g.gui.mapswipe first=elevation second=basins

Read more in the manual.

Python examples

For details on GRASS GIS Python programming, see also GRASS Python Scripting Library.

Python: GRASS GIS 7 with an external library: grass-session

The "grass-session" Python library is an easy way to control GRASS GIS from "outside"-Python. To install the current version, run:

# see also: https://pypi.org/project/grass-session/

pip install grass-session
# for the latest development version use:
# pip install git+https://github.com/zarch/grass-session.git

Then write

#!/usr/bin/env python
# filename: test_session.py

from grass_session import Session
from grass.script import core as gcore

# create a new location from EPSG code (can also be a GeoTIFF or SHP or ... file)
with Session(gisdb="/tmp", location="mylocation",
             create_opts="EPSG:4326"):
   # run something in PERMANENT mapset:
   print(gcore.parse_command("g.gisenv", flags="s"))
# expected output:
# {u'GISDBASE': u"'/tmp/';",
#  u'LOCATION_NAME': u"'mylocation';",
#  u'MAPSET': u"'PERMANENT';",}

# create a new mapset in an existing location
with Session(gisdb="/tmp", location="mylocation", mapset="test",
             create_opts=""):
    # do something in the test mapset.
    print(gcore.parse_command("g.gisenv", flags="s"))
# expected output:
# {u'GISDBASE': u"'/tmp/';",
#  u'LOCATION_NAME': u"'mylocation';",
#  u'MAPSET': u"'test';",}

The grass-session library looks at the GRASSBIN environmental variable. If the variable is not set, the library tries to use the current stable release of GRASS GIS (e.g. grass84 on Linux for the 8.4 release). For instance, to execute the previous example using a custom installation of 8.5 version, write:

# WAY 1
# set variable "on the fly" for python call
GRASSBIN="$HOME/bin/grass85 python test_session.py"

# WAY 2
export GRASSBIN="$HOME/bin/grass85"
python3 test_session.py
{'GISDBASE': "'/tmp';", 'LOCATION_NAME': "'mylocation';", 'MAPSET': "'PERMANENT';"}
{'GISDBASE': "'/tmp';", 'LOCATION_NAME': "'mylocation';", 'MAPSET': "'test';"}

See below for a solution with docker.

Using 'grass-session' with PyGRASS

If you need to use python libraries that are using ctypes, run it like this for example:

# file: test_session_ctypes.py
import grass_session
from grass.pygrass.vector import VectorTopo

print('DONE!')

The above script will raise an exception because is not possible to dynamically import the linked library in the same process.

$ GRASSBIN=/home/pietro/.local/bin/grass python test.py 
GRASSBIN: /home/pietro/.local/bin/grass
GISBASE: /home/pietro/.local/grass
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    from grass.pygrass.vector import VectorTopo
  File "/home/pietro/.local/grass/etc/python/grass/pygrass/vector/__init__.py", line 5, in <module>
    import grass.lib.gis as libgis
  File "/home/pietro/.local/grass/etc/python/grass/lib/gis.py", line 23, in <module>
    _libs["grass_gis."] = load_library("grass_gis.")
  File "/home/pietro/.local/grass/etc/python/grass/lib/ctypes_loader.py", line 62, in load_library
    return self.load(path)
  File "/home/pietro/.local/grass/etc/python/grass/lib/ctypes_loader.py", line 78, in load
    raise ImportError(e)
ImportError: libgrass_datetime..so: cannot open shared object file: No such file or directory

But you can set the LD_LIBRARY_PATH variable before launching the program. For instance from the command line you can define:

$ LD_LIBRARY_PATH=/home/pietro/.local/grass/lib \
   GRASSBIN=/home/pietro/.local/bin/grass \
   python test_session_ctypes.py
GRASSBIN: /home/pietro/.local/bin/grass
GISBASE: /home/pietro/.local/grass
DONE!


The use of the with-statement close automatically the session. If you have to manage more complex workflow you can manually define the opening and closing actions. For example:

import os

# import grass_session
from grass_session import Session

# import grass python libraries
from grass.pygrass.modules.shortcuts import general as g


# set some common environmental variables, like:
os.environ.update(dict(GRASS_COMPRESS_NULLS='1',
                       GRASS_COMPRESSOR='ZSTD'))

# create a PERMANENT mapset
# create a Session instance
PERMANENT = Session()
PERMANENT.open(gisdb='/tmp', location='mytest',
               create_opts='EPSG:4326')


# execute some command inside PERMANENT
g.mapsets(flags="l")
g.list(type="raster", flags="m")

# exit from PERMANENT
PERMANENT.close()

# create a new mapset in the same location
user = Session()
user.open(gisdb='/tmp', location='mytest', mapset='user',
               create_opts='')

# execute some command inside user
g.mapsets(flags="l")
g.list(type="raster", flags="m")

# exit from user
user.close()
Hint: find the path to the GRASS GIS package start script

Hint: in order to find the path to the GRASS GIS package, launch it with --config path:

# Linux
grass --config path
/usr/bin/grass84

# Windows
C:\>grass.bat --config path
C:\OSGeo4W\apps\grass\grass84
Python: GRASS GIS 8 with existing location

See the example in the documentation or the script which initializes the session and lists available raster and vector maps:

#!/usr/bin/env python

import os
import subprocess
import sys

# define GRASS data settings (adapt to your needs)
grassdata = "/home/mneteler/grassdata/"
location = "nc_spm_08_grass7"
mapset = "user1"

# Python path: we ask GRASS GIS where its Python packages are
sys.path.append(
    subprocess.check_output(["grass", "--config", "python_path"], text=True).strip()
)

# Import GRASS Python bindings
import grass.script as gs

# full path to new project (formerly: location)
project = "/tmp/grassproject_epsg_25832"
gs.create_project(project, epsg="25832")

# initialize project
gs.setup.init(project)

At this point we are in a running GRASS GIS session, let's verify.

# show current GRASS GIS settings
print("GRASS GIS session: tests for PROJ, GDAL, PDAL, GRASS GIS")
print(gs.parse_command("g.gisenv", flags="s"))

Now we may also import further functionality (e.g. VectorTopo of pygrass):

from grass.pygrass.vector import VectorTopo

and can for example retrieve the topology of a vector map (using the North Carolina dataset):

test_vect = VectorTopo("zipcodes_wake")
test_vect.open(mode="r")
test_vect.num_primitive_of("point")
test_vect.num_primitive_of("line")
test_vect.num_primitive_of("centroid")
test_vect.num_primitive_of("boundary")
test_vect.close()

This should report:

>>> test_vect.num_primitive_of('point')
0
>>> test_vect.num_primitive_of('line')
0
>>> test_vect.num_primitive_of('centroid')
48
>>> test_vect.num_primitive_of('boundary')
158

Bash examples (GNU/Linux)

Note: see GRASS Batch jobs for a really easy approach.

Below an example of a ~/.bash_profile script to set the required settings in order to access GRASS commands outside of a GRASS GIS session

# GRASS environment
export GISBASE=/usr/local/grass
export GRASS_VERSION=""
export GRASS_VERSION="8.4"
export GISBASE="/home/mundialis/src/grass-8.4.2/dist.x86_64-pc-linux-gnu/"
export GRASS_GNUPLOT="gnuplot -persist"
export GRASS_HTML_BROWSER="htmlview"
export GRASS_LD_LIBRARY_PATH="${GISBASE}/lib"
export GRASS_PAGER="more"
export GRASS_PERL="/usr/bin/perl"
export GRASS_PROJSHARE="/usr/local/share/proj"
export GRASS_PYTHON="python"
export GRASS_SH="/bin/sh"
export GRASS_SHELL_PID="$$"

#generate GISRCRC
MYGISDBASE=$HOME/grassdata
MYLOC=nc_spm_08_grass7
MYMAPSET=user1

# Set the global grassrc file to individual file name
MYGISRC="$HOME/.grassrc.$GRASS_VERSION.$$"

echo "GISDBASE: $MYGISDBASE" > "$MYGISRC"
echo "LOCATION_NAME: $MYLOC" >> "$MYGISRC"
echo "MAPSET: $MYMAPSET" >> "$MYGISRC"
echo "GRASS_GUI: text" >> "$MYGISRC"
 
# path to GRASS settings file
export GISRC=$MYGISRC
export GRASS_PYTHON=python
export GRASS_MESSAGE_FORMAT=plain
export GRASS_TRUECOLOR=TRUE
export GRASS_TRANSPARENT=TRUE
export GRASS_PNG_AUTO_WRITE=TRUE
export GRASS_GNUPLOT='gnuplot -persist'
export GRASS_WIDTH=640
export GRASS_HEIGHT=480
export GRASS_HTML_BROWSER=firefox
export GRASS_PAGER=cat

#For the temporal modules
export TGISDB_DRIVER=sqlite
export TGISDB_DATABASE=$MYGISDBASE/$MYLOC/PERMANENT/tgis/sqlite.db

# for fun, we can even set the shell prompt to contain a hint on GRASS GIS env being active
export PS1="[\u@\h \W G-$GRASS_VERSION]$ "

# system vars
export PATH="$GISBASE/bin:$GISBASE/scripts:$PATH"
export LD_LIBRARY_PATH="$GISBASE/lib"
export GRASS_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
export PYTHONPATH="$GISBASE/etc/python:$PYTHONPATH"
export MANPATH=$MANPATH:$GISBASE/man

# test a command
g.list rast
v.info zipcodes_wake

The above script will allow GRASS commands to be used anywhere. Furthermore, if the ~/.Xsession sources the bash startup scripts, the settings aren't limited to interactive shells, but also work for e.g. M-! in XEmacs). Each interactive shell gets a separate "session" (i.e. a separate $GISRC file), while GUI programs share a common session.

Note, however, that ~/.bash_profile is a personal initialization startup script and, thus, read during the login process. Any modifications to it will be read after (re-)login or explicitly sourcing it.

At the end of the ~/.bash_profile file, the following lines can be added to ensure no duplication of entries in the PATH variable.

 PATH=`awk -F: '{for(i=1;i<=NF;i++){if(!($i in a)){a[$i];printf s$i;s=":"}}}'<<<$PATH`
 PYTHONPATH=`awk -F: '{for(i=1;i<=NF;i++){if(!($i in a)){a[$i];printf s$i;s=":"}}}'<<<$PYTHONPATH`

Important notes

Launching a grassXY session could still permit access on GRASS modules of the version that is set with the above script. The reason being is that grassXY scripts (e.g.: grass84) prepend the GRASS directories to PATH, LD_LIBRARY_PATH, etc. They won't remove any entries which are already there. A solution to this is to switch between GRASS versions by using a script like the following:

        strippath()
        {
            (
            oldpath="$1"
            newpath=
            IFS=:
            for dir in $oldpath ; do
                case "${dir}" in
                *grass*)
                        ;;
                *)
                        newpath="$newpath:$dir"
                        ;;
                esac
            done
            echo "${newpath#:}"
            )
        }
        
        PATH=`strippath $PATH`
        
        export GISBASE=$PWD/dist.i686-pc-linux-gnu
        export PATH="$GISBASE/bin:$GISBASE/scripts:$PATH"
        export LD_LIBRARY_PATH="$GISBASE/lib"
        export PYTHONPATH="$GISBASE/etc/python"

Note: the above script needs to be "source"d; executing it won't work. It might also require some adjustments (e.g. the GISBASE variable).

Using normal grass sessions commands are recorded in $GISDBASE/$LOCATION_NAME/$MAPSET/.bash_history. While working with "pure" bash, shell entries go in ~/.bash_history. Merging existing "grass-bash_history(-ies)" with the bash history log, would probably be not a good idea. Perhaps it is wise(r) to use normal GRASS sessions when working on real projects. Nevertheless, it is upon the users preferences and expertise level to decide what is best.

Hints and troubleshooting

Some hints for MS Windows users:

  1. The Python interpreter (python.exe) needs to be in the PATH
  2. Python needs to be associated with the .py extension
  3. PATHEXT needs to include .py if you want to be able to omit the extension

When everything works well, the installer should take care of all of these.

To manage parallel sessions on Linux, use process ID (PID) as lock file number and set the GIS_LOCK variable, for example

 export GIS_LOCK=$$

in Bash.

References

GRASS databases

(project file structure)

Minimal mapsets

within a functional LOCATION (see below) the minimal mapset is a subdirectory of the MAPSET's name, containing a WIND file. The WIND file can simply be copied from PERMANENT/DEFAULT_WIND. Optionally you can put a VAR file in there too to define the default database driver to use.

  • You can create a new mapset when starting GRASS with the -c flag. e.g.
grass -c /path/to/location/new_mapset_name

Minimal locations

Within the GISDATABASE (which is simply a subdirectory some where), the minimum LOCATION consists of a directory giving the LOCATION its name, which in turn contains a PERMANENT subdirectory for the PERMANENT mapset. The PERMANENT mapset contains a few special files that regular mapsets don't. These are:

PROJ_INFO
contains the location's projection information
PROJ_UNITS
contains the location's map units definition
DEFAULT_WIND
the default region (WINDow file). The format is identical to the WIND files created by g.region
WIND
(optional?)

See also