Unit 24 - Spatio-temporal scripting

There is another GRASS Python library to be introduced - GRASS GIS Temporal Framework.

In a script below similar computation like in Unit 11 - PyGRASS scripting and Unit 12 - Script User Interface will be performed. But instead of processing single Sentinel scene, a spatio-temporal dataset of multiple Sentinel scenes will be processed.

Let’s define input and output parameters:

  • b4 - Name of input 4th band space time raster dataset (line 19)

  • b8 - Name of input 8th band space time raster dataset (line 23)

  • clouds - Name of the input mask (region+clouds) space time raster dataset (line 27)

  • clouds - Name for output stats file (line 30)

  • basename - Basename for output raster maps (line 33)

  • threshold - Threshold for removing small areas (line 38)

Focus on functions used from GRASS GIS Temporal Framework:

  • initialization must be done by init function, see line 138

  • space time datasets are open on lines 140-142 by open_old_stds

  • raster maps registered in reference dataset (b4) are listed on line 146 by get_registered_maps

  • related raster maps in two other datasets (b8, cl) are searched on lines 149-152 by get_registered_maps with where parameter

  1#!/usr/bin/env python
  2#
  3##############################################################################
  4#
  5# MODULE:       ndvi-tgrass-v1
  6#
  7# AUTHOR(S):    martin
  8#
  9# PURPOSE:      NDVI TGRASS version 1
 10#
 11# DATE:         Sat Feb  3 15:45:35 2018
 12#
 13##############################################################################
 14
 15#%module
 16#% description: NDVI TGRASS script version 1
 17#%end                
 18#%option G_OPT_STRDS_INPUT
 19#% key: b4
 20#% description: Name of input 4th band space time raster dataset
 21#%end
 22#%option G_OPT_STRDS_INPUT
 23#% key: b8
 24#% description: Name of input 8th band space time raster dataset
 25#%end
 26#%option G_OPT_STRDS_INPUT
 27#% key: mask
 28#% description: Name of input mask space time raster dataset
 29#%end
 30#%option G_OPT_F_OUTPUT
 31#%end
 32#%option
 33#% key: basename
 34#% description: Basename for output raster maps
 35#% required: yes
 36#%end
 37#%option
 38#% key: threshold
 39#% description: Threshold for removing small areas
 40#% answer: 1600
 41#%end
 42
 43import sys
 44import os
 45import atexit
 46
 47from grass.pygrass.modules import Module
 48from grass.script import parser
 49from grass.script.vector import vector_db_select
 50    
 51def cleanup():
 52    Module('g.remove', flags='f', name='ndvi', type='raster')
 53    Module('g.remove', flags='f', name='ndvi_class', type='raster')
 54    Module('g.remove', flags='f', name='ndvi_class', type='vector')
 55
 56def compute(b4, b8, msk, output):
 57
 58    Module("g.region",
 59           overwrite = True,
 60           vector = msk,
 61           align = b4)
 62
 63    Module("r.mask",
 64           overwrite = True,
 65           raster = msk)
 66
 67    Module("i.vi",
 68           overwrite = True,
 69           red = b4,
 70           output = "ndvi",
 71           nir = b8)
 72                
 73    recode_str="""-1:0.1:1
 740.1:0.5:2
 750.5:1:3"""
 76
 77    Module("r.recode",
 78           overwrite = True,
 79           input = "ndvi",
 80           output = "ndvi_class",
 81           rules = "-",
 82           stdin_ = recode_str)
 83
 84    colors_str="""1 grey
 852 255 255 0
 863 green"""
 87    Module("r.colors",
 88           map = "ndvi_class",
 89           rules = "-",
 90           stdin_ = colors_str)
 91    
 92    Module("r.to.vect",
 93           flags = 'sv',
 94           overwrite = True,
 95           input = "ndvi_class",
 96           output = "ndvi_class",
 97           type = "area")
 98
 99    Module("v.clean",
100           overwrite = True,
101           input = "ndvi_class",
102           output = output,
103           tool = "rmarea",
104           threshold = options['threshold'])
105
106def stats(output, date, fd):
107    fd.write('-' * 80)
108    fd.write(os.linesep)
109    fd.write('NDVI class statistics ({0}: {1})'.format(output, date))
110    fd.write(os.linesep)
111    fd.write('-' * 80)
112    fd.write(os.linesep)
113    from subprocess import PIPE
114    ret = Module('v.report', map=output, option='area',
115                 stdout_=PIPE)
116    for line in ret.outputs.stdout.splitlines()[1:]: # skip first line (cat|label|area)
117        # parse line (eg. 1||2712850)
118        data = line.split('|')
119        cat = data[0]
120        area = float(data[-1])
121        fd.write('NDVI class {0}: {1:.1f} ha'.format(cat, area/1e4))
122        fd.write(os.linesep)
123
124    # v.to.rast: use -c flag for updating statistics if exists
125    Module('v.rast.stats', flags='c', map=output, raster='ndvi',
126           column_prefix='ndvi', method=['minimum','maximum','average'])
127    
128    data = vector_db_select(output)
129    for vals in data['values'].itervalues():
130        # unfortunately we need to cast values by float
131        fd.write('NDVI class {0}: {1:.4f} (min) {2:.4f} (max) {3:.4f} (mean)'.format(
132            vals[0], float(vals[2]), float(vals[3]), float(vals[4])))
133        fd.write(os.linesep)
134        
135def main():
136    import grass.temporal as tgis
137
138    tgis.init()
139
140    sp4 = tgis.open_old_stds(options['b4'], 'raster')
141    sp8 = tgis.open_old_stds(options['b8'], 'raster')
142    msk = tgis.open_old_stds(options['mask'], 'raster')
143
144    idx = 1
145    fd = open(options['output'], 'w')
146    for item in sp4.get_registered_maps(columns='name,start_time'):
147        b4 = item[0]
148        date=item[1]
149        b8 = sp8.get_registered_maps(columns='name',
150                                     where="start_time = '{}'".format(date))[0][0]
151        ms = msk.get_registered_maps(columns='name',
152                                     where="start_time = '{}'".format(date))[0][0]
153        output = '{}_{}'.format(options['basename'], idx)
154        compute(b4, b8, ms, output)
155        stats(output, date, fd)
156        cleanup()
157        idx += 1
158
159    fd.close()
160    
161    return 0
162
163if __name__ == "__main__":
164    options, flags = parser()
165    sys.exit(main())

Example of usage:

ndvi-tgrass.py b4=b4 b8=b8 clouds=clouds basename=ndvi_t output=stats.txt
--------------------------------------------------------------------------------
NDVI class statistics (ndvi_t_1: 2017-05-06 10:50:31)
--------------------------------------------------------------------------------
NDVI class 1: 6714.4 ha
NDVI class 2: 11480.8 ha
NDVI class 3: 29852.3 ha
NDVI class 1: -0.9929 (min) 0.9928 (max) -0.3230 (mean)
NDVI class 2: -0.9512 (min) 0.9993 (max) 0.3327 (mean)
NDVI class 3: -0.9512 (min) 0.9988 (max) 0.7312 (mean)
...
--------------------------------------------------------------------------------
NDVI class statistics (ndvi_t_4: 2017-07-05 10:50:31)
--------------------------------------------------------------------------------
NDVI class 1: 24122.4 ha
NDVI class 2: 7243.5 ha
NDVI class 3: 16682.1 ha
NDVI class 1: -0.9914 (min) 0.9710 (max) -0.0292 (mean)
NDVI class 2: -0.9286 (min) 0.9487 (max) 0.2506 (mean)
NDVI class 3: -0.9545 (min) 0.9982 (max) 0.8190 (mean)

Note

Script will run for a while in the case of Oslo region.

Sample script to download: ndvi-tgrass-v1.py