123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- #!/usr/bin/env python
- """
- MODULE: v.krige
- AUTHOR(S): Anne Ghisla <a.ghisla AT gmail.com>
- PURPOSE: Performs ordinary or block kriging
- DEPENDS: R 2.x, packages gstat, maptools and spgrass6, optional: automap
- COPYRIGHT: (C) 2009-2014 the GRASS Development Team
- This program is free software under the GNU General Public
- License (>=v2). Read the file COPYING that comes with GRASS
- for details.
- """
- ## g.parser information
- #%module
- #% description: Performs ordinary or block kriging for vector maps.
- #% keywords: vector
- #% keywords: interpolation
- #% keywords: raster
- #% keywords: kriging
- #%end
- #%option G_OPT_V_INPUT
- #% description: Name of point vector map containing sample data
- #%end
- #%option G_OPT_DB_COLUMN
- #% description: Name of attribute column with numerical value to be interpolated
- #% required: yes
- #%end
- #%option G_OPT_R_OUTPUT
- #% label: Name for output raster map
- #% description: If omitted, will be <input name>_kriging
- #% required : no
- #%end
- #%option
- #% key: package
- #% type: string
- #% options: gstat
- #% answer: gstat
- #% description: R package to use
- #% required: no
- #%end
- #%option
- #% key: model
- #% type: string
- #% options: Nug,Exp,Sph,Gau,Exc,Mat,Ste,Cir,Lin,Bes,Pen,Per,Hol,Log,Pow,Spl,Leg,Err,Int
- #% multiple: yes
- #% label: Variogram model(s)
- #% description: Leave empty to test all models (requires automap)
- #% required: no
- #%end
- #%option
- #% key: block
- #% type: integer
- #% multiple: no
- #% label: Block size (square block)
- #% description: Block size. Used by block kriging.
- #% required: no
- #%end
- #%option
- #% key: range
- #% type: integer
- #% label: Range value
- #% description: Automatically fixed if not set
- #% required : no
- #%end
- #%option
- #% key: nugget
- #% type: integer
- #% label: Nugget value
- #% description: Automatically fixed if not set
- #% required : no
- #%end
- #%option
- #% key: sill
- #% type: integer
- #% label: Sill value
- #% description: Automatically fixed if not set
- #% required : no
- #%end
- #%option G_OPT_R_OUTPUT
- #% key: output_var
- #% label: Name for output variance raster map
- #% description: If omitted, will be <input name>_kriging.var
- #% required : no
- #%end
- import os, sys
- from tempfile import gettempdir
- import time
- import thread
- if not os.environ.has_key("GISBASE"):
- print "You must be in GRASS GIS to run this program."
- sys.exit(1)
- ### i18N
- import gettext
- gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
- ### dependencies to be checked once, as they are quite time-consuming. cfr. grass.parser.
- # GRASS binding
- try:
- import grass.script as grass
- except ImportError:
- sys.exit(_("No GRASS-python library found"))
-
- # move other checks in functions, as R?
- # globals
- #~ Command = None
- #~ InputData = None
- #~ Variogram = None
- #~ VariogramFunction = None
- #~ robjects = None
- #~ rinterface = None
- #classes in alphabetical order. methods in logical order :)
- # <2.5 class definition, without () - please test
- class Controller:
- """ Executes analysis. For the moment, only with gstat functions."""
- # moved here the global variables
- def __init__(self):
- #~ self.Command = None
- self.InputData = None
- self.Variogram = None
- #~ VariogramFunction = None
- #~ robjects = None
- #~ rinterface = None
-
- def ImportMap(self, map, column):
- """ Imports GRASS map as SpatialPointsDataFrame and adds x/y columns to attribute table.
- Checks for NULL values in the provided column and exits if they are present."""
- #@NOTE: new way with R - as it doesn't alter original data
- Rpointmap = robjects.r.readVECT6(map, type = 'point')
- # checks if x,y columns are present in dataframe. If they do are present, but with different names,
- # they'll be duplicated.
- if "x" not in robjects.r.names(Rpointmap):
- # extract coordinates with S4 method
- coordinatesPreDF = robjects.r['as.data.frame'](robjects.r.coordinates(Rpointmap))
- coordinatesDF = robjects.r['data.frame'](x = coordinatesPreDF.rx('coords.x1')[0],
- y = coordinatesPreDF.rx('coords.x2')[0])
- # match coordinates with data slot of SpatialPointsDataFrame - maptools function
- # match is done on row.names
- Rpointmap = robjects.r.spCbind(Rpointmap, coordinatesDF)
-
- # GRASS checks for null values in the chosen column. R can hardly handle column as a variable,
- # looks for a hardcoded string.
- cols = grass.vector_columns(map=map, layer=1)
- nulls = int(grass.parse_command('v.univar',
- map=map,
- column=column,
- type='point',
- parse = (grass.parse_key_val,
- {'sep':': '}
- )
- )['number of NULL attributes'])
- if nulls > 0:
- grass.fatal(_("%d NULL value(s) in the selected column - unable to perform kriging.") % nulls)
- return Rpointmap
-
- def CreateGrid(self, inputdata):
- Region = grass.region()
- Grid = robjects.r.gmeta2grd()
- # addition of coordinates columns into dataframe.
- coordinatesDF = robjects.r['as.data.frame'](robjects.r.coordinates(Grid))
- data = robjects.r['data.frame'](x = coordinatesDF.rx('s1')[0],
- y = coordinatesDF.rx('s2')[0],
- k = robjects.r.rep(1, Region['cols']*Region['rows']))
- GridPredicted = robjects.r.SpatialGridDataFrame(Grid,
- data,
- proj4string = robjects.r.CRS(robjects.r.proj4string(inputdata)))
- return GridPredicted
-
- def ComposeFormula(self, column, isblock):
- if isblock is True:
- predictor = 'x+y'
- else:
- predictor = '1'
- print column + "~" + predictor
- Formula = robjects.Formula(column + "~" + predictor)
- return Formula
-
- def FitVariogram(self, formula, inputdata, sill, nugget, range, model = ''):
- """ Fits variogram either automagically either specifying all parameters.
- Returns a list containing data and model variograms. """
-
- Variograms = {}
-
- if model is '':
- robjects.r.require('automap')
- DottedParams = {}
- #print (nugget.r_repr(), sill, range)
- DottedParams['fix.values'] = robjects.r.c(nugget, range, sill)
-
- VariogramModel = robjects.r.autofitVariogram(formula, inputdata, **DottedParams)
- #print robjects.r.warnings()
- Variograms['datavariogram'] = VariogramModel.rx('exp_var')[0]
- Variograms['variogrammodel'] = VariogramModel.rx('var_model')[0]
- # obtain the model name. *Too* complicated to get the string instead of level, unlike R does.
- VariogramAsDF = robjects.r['as.data.frame'](VariogramModel.rx('var_model')[0]) # force conversion
- ModelDF = VariogramAsDF.rx('model')[0]
- Variograms['model'] = ModelDF.levels[ModelDF[1] - 1]
- else:
- DataVariogram = robjects.r['variogram'](formula, inputdata)
- VariogramModel = robjects.r['fit.variogram'](DataVariogram,
- model = robjects.r.vgm(psill = sill,
- model = model,
- nugget = nugget,
- range = range))
- Variograms['datavariogram'] = DataVariogram
- Variograms['variogrammodel'] = VariogramModel
- Variograms['model'] = model
- return Variograms
-
- def DoKriging(self, formula, inputdata, grid, model, block):
- DottedParams = {'debug.level': -1} # let krige() print percentage status
- if block is not '': #@FIXME(anne): but it's a string!! and krige accepts it!!
- DottedParams['block'] = block
- #print DottedParams
- KrigingResult = robjects.r.krige(formula, inputdata, grid, model, **DottedParams)
- return KrigingResult
-
- def ExportMap(self, map, column, name, overwrite, command, variograms):
- # add kriging parameters to raster map history
- robjects.r.writeRAST6(map, vname = name, zcol = column, overwrite = overwrite)
- grass.run_command('r.support',
- map = name,
- title = 'Kriging output',
- history = 'Issued from command v.krige ' + command)
- if command.find('model') is -1: # if the command has no model option, add automap chosen model
- grass.run_command('r.support',
- map = name,
- history = 'Model chosen by automatic fitting: ' + variograms['model'])
-
- def Run(self, input, column, output, package, sill, nugget, range, logger, \
- overwrite, model, block, output_var, command, **kwargs):
- """ Wrapper for all functions above. """
- logger.message(_("Processing %d cells. Computing time raises "
- "exponentially with resolution." % grass.region()['cells']))
- logger.message(_("Importing data..."))
- if self.InputData is None:
- self.InputData = self.ImportMap(input, column)
- # and from here over, InputData refers to the global variable
- #print(robjects.r.slot(InputData, 'data').names)
- logger.message(_("Data successfully imported."))
-
- GridPredicted = self.CreateGrid(self.InputData)
-
- logger.message(_("Fitting variogram..."))
- if block is not '':
- self.predictor = 'x+y'
- else:
- self.predictor = '1'
- if self.Variogram is None:
- self.Variogram = self.FitVariogram(robjects.Formula(column + "~" + self.predictor),
- self.InputData,
- model = model,
- sill = sill,
- nugget = nugget,
- range = range)
- logger.message(_("Variogram fitting complete."))
-
- logger.message(_("Kriging..."))
- KrigingResult = self.DoKriging(robjects.Formula(column + "~" + self.predictor), self.InputData,
- GridPredicted, self.Variogram['variogrammodel'], block) # using global ones
- logger.message(_("Kriging complete."))
-
- self.ExportMap(map = KrigingResult,
- column='var1.pred',
- name = output,
- overwrite = overwrite,
- command = command,
- variograms = self.Variogram)
- if output_var is not '':
- self.ExportMap(map = KrigingResult,
- column='var1.var',
- name = output_var,
- overwrite = overwrite,
- command = command,
- variograms = self.Variogram)
-
- def main(argv = None):
- """ Main. Calls either GUI or CLI, depending on arguments provided. """
- #@FIXME: solve this double ifelse. the control should not be done twice.
-
- controller = Controller()
-
- if argv is None:
- importR()
- argv = sys.argv[1:] # stripping first item, the full name of this script
- # wxGUI call
- if not os.getenv("GRASS_WXBUNDLED"):
- from core import globalvar
- globalvar.CheckForWx()
- from modules import vkrige as GUI
-
- import wx
-
- app = wx.App()
- KrigingFrame = GUI.KrigingModule(parent = None,
- Rinstance = robjects,
- controller = controller)
- KrigingFrame.Centre()
- KrigingFrame.Show()
- app.MainLoop()
-
- else:
- #CLI
- options, flags = argv
-
- #@TODO: Work on verbosity. Sometimes it's too verbose (R), sometimes not enough.
- if grass.find_file(options['input'], element = 'vector')['fullname'] is '':
- grass.fatal(_("Vector map <%s> not found") % options['input'] )
-
- #@TODO: elaborate input string, if contains mapset or not.. thanks again to Bob for testing on 64bit.
-
- # create output map name, if not specified
- if options['output'] is '':
- try: # to strip mapset name from fullname. Ugh.
- options['input'] = options['input'].split("@")[0]
- except:
- pass
- options['output'] = options['input'] + '_kriging'
- # check for output map with same name. g.parser can't handle this, afaik.
- if grass.find_file(options['output'], element = 'cell')['fullname'] \
- and os.getenv("GRASS_OVERWRITE") == None:
- grass.fatal(_("option: <output>: Raster map already exists."))
- if options['output_var'] is not '' \
- and (grass.find_file(options['output_var'], element = 'cell')['fullname'] \
- and os.getenv("GRASS_OVERWRITE") == None):
- grass.fatal(_("option: <output>: Variance raster map already exists."))
- importR()
- if options['model'] is '':
- try:
- robjects.r.require("automap")
- except ImportError as e:
- grass.fatal(_("R package automap is missing, no variogram autofit available."))
- else:
- if options['sill'] is '' or options['nugget'] is '' or options['range'] is '':
- grass.fatal(_("You have specified model, but forgot at least one of sill, nugget and range."))
-
- #@TODO: let GRASS remount its commandstring. Until then, keep that 4 lines below.
- #print grass.write_command(argv)
- command = ""
- notnulloptions = {}
- for k, v in options.items():
- if v is not '':
- notnulloptions[k] = v
- command = command.join("%s=%s " % (k, v) for k, v in notnulloptions.items())
-
- # re-cast integers from strings, as parser() cast everything to string.
- for each in ("sill","nugget","range"):
- if options[each] is not '':
- options[each] = int(options[each])
- else:
- options[each] = robjects.r('''NA''')
-
- #controller = Controller()
- controller.Run(input = options['input'],
- column = options['column'],
- output = options['output'],
- overwrite = os.getenv("GRASS_OVERWRITE") == 1,
- package = options['package'],
- model = options['model'],
- block = options['block'],
- sill = options['sill'],
- nugget = options['nugget'],
- range = options['range'],
- output_var = options['output_var'],
- command = command,
- logger = grass)
-
- def importR():
- # R
- # unuseful since rpy2 will complain adequately.
- # try:
- # #@FIXME: in Windows, it launches R terminal
- # grass.find_program('R')
- # except:
- # sys.exit(_("R is not installed. Install it and re-run, or modify environment variables."))
-
- # rpy2
- global robjects
- global rinterface
- grass.message(_('Loading dependencies, please wait...'))
- try:
- import rpy2.robjects as robjects
- import rpy2.rinterface as rinterface #to speed up kriging? for plots.
- except ImportError:
- # ok for other OSes?
- grass.fatal(_("Python module 'Rpy2' not found. Please install it and re-run v.krige."))
-
- # R packages check. Will create one error message after check of all packages.
- missingPackagesList = []
- for each in ["rgeos", "gstat", "spgrass6", "maptools"]:
- if not robjects.r.require(each, quietly = True)[0]:
- missingPackagesList.append(each)
- if missingPackagesList:
- errorString = _("R package(s) ") + \
- ", ".join(map(str, missingPackagesList)) + \
- _(" missing. Install it/them and re-run v.krige.")
- grass.fatal(errorString)
-
- if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(main(argv = grass.parser()))
- else:
- main()
|