v.krige.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. #!/usr/bin/env python
  2. """
  3. MODULE: v.krige
  4. AUTHOR(S): Anne Ghisla <a.ghisla AT gmail.com>
  5. PURPOSE: Performs ordinary or block kriging
  6. DEPENDS: R 2.x, packages gstat, maptools and spgrass6, optional: automap
  7. COPYRIGHT: (C) 2009-2014 the GRASS Development Team
  8. This program is free software under the GNU General Public
  9. License (>=v2). Read the file COPYING that comes with GRASS
  10. for details.
  11. """
  12. ## g.parser information
  13. #%module
  14. #% description: Performs ordinary or block kriging for vector maps.
  15. #% keywords: vector
  16. #% keywords: interpolation
  17. #% keywords: raster
  18. #% keywords: kriging
  19. #%end
  20. #%option G_OPT_V_INPUT
  21. #% description: Name of point vector map containing sample data
  22. #%end
  23. #%option G_OPT_DB_COLUMN
  24. #% description: Name of attribute column with numerical value to be interpolated
  25. #% required: yes
  26. #%end
  27. #%option G_OPT_R_OUTPUT
  28. #% label: Name for output raster map
  29. #% description: If omitted, will be <input name>_kriging
  30. #% required : no
  31. #%end
  32. #%option
  33. #% key: package
  34. #% type: string
  35. #% options: gstat
  36. #% answer: gstat
  37. #% description: R package to use
  38. #% required: no
  39. #%end
  40. #%option
  41. #% key: model
  42. #% type: string
  43. #% options: Nug,Exp,Sph,Gau,Exc,Mat,Ste,Cir,Lin,Bes,Pen,Per,Hol,Log,Pow,Spl,Leg,Err,Int
  44. #% multiple: yes
  45. #% label: Variogram model(s)
  46. #% description: Leave empty to test all models (requires automap)
  47. #% required: no
  48. #%end
  49. #%option
  50. #% key: block
  51. #% type: integer
  52. #% multiple: no
  53. #% label: Block size (square block)
  54. #% description: Block size. Used by block kriging.
  55. #% required: no
  56. #%end
  57. #%option
  58. #% key: range
  59. #% type: integer
  60. #% label: Range value
  61. #% description: Automatically fixed if not set
  62. #% required : no
  63. #%end
  64. #%option
  65. #% key: nugget
  66. #% type: integer
  67. #% label: Nugget value
  68. #% description: Automatically fixed if not set
  69. #% required : no
  70. #%end
  71. #%option
  72. #% key: sill
  73. #% type: integer
  74. #% label: Sill value
  75. #% description: Automatically fixed if not set
  76. #% required : no
  77. #%end
  78. #%option G_OPT_R_OUTPUT
  79. #% key: output_var
  80. #% label: Name for output variance raster map
  81. #% description: If omitted, will be <input name>_kriging.var
  82. #% required : no
  83. #%end
  84. import os, sys
  85. from tempfile import gettempdir
  86. import time
  87. import thread
  88. if not os.environ.has_key("GISBASE"):
  89. print "You must be in GRASS GIS to run this program."
  90. sys.exit(1)
  91. ### i18N
  92. import gettext
  93. gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
  94. ### dependencies to be checked once, as they are quite time-consuming. cfr. grass.parser.
  95. # GRASS binding
  96. try:
  97. import grass.script as grass
  98. except ImportError:
  99. sys.exit(_("No GRASS-python library found"))
  100. # move other checks in functions, as R?
  101. # globals
  102. #~ Command = None
  103. #~ InputData = None
  104. #~ Variogram = None
  105. #~ VariogramFunction = None
  106. #~ robjects = None
  107. #~ rinterface = None
  108. #classes in alphabetical order. methods in logical order :)
  109. # <2.5 class definition, without () - please test
  110. class Controller:
  111. """ Executes analysis. For the moment, only with gstat functions."""
  112. # moved here the global variables
  113. def __init__(self):
  114. #~ self.Command = None
  115. self.InputData = None
  116. self.Variogram = None
  117. #~ VariogramFunction = None
  118. #~ robjects = None
  119. #~ rinterface = None
  120. def ImportMap(self, map, column):
  121. """ Imports GRASS map as SpatialPointsDataFrame and adds x/y columns to attribute table.
  122. Checks for NULL values in the provided column and exits if they are present."""
  123. #@NOTE: new way with R - as it doesn't alter original data
  124. Rpointmap = robjects.r.readVECT6(map, type = 'point')
  125. # checks if x,y columns are present in dataframe. If they do are present, but with different names,
  126. # they'll be duplicated.
  127. if "x" not in robjects.r.names(Rpointmap):
  128. # extract coordinates with S4 method
  129. coordinatesPreDF = robjects.r['as.data.frame'](robjects.r.coordinates(Rpointmap))
  130. coordinatesDF = robjects.r['data.frame'](x = coordinatesPreDF.rx('coords.x1')[0],
  131. y = coordinatesPreDF.rx('coords.x2')[0])
  132. # match coordinates with data slot of SpatialPointsDataFrame - maptools function
  133. # match is done on row.names
  134. Rpointmap = robjects.r.spCbind(Rpointmap, coordinatesDF)
  135. # GRASS checks for null values in the chosen column. R can hardly handle column as a variable,
  136. # looks for a hardcoded string.
  137. cols = grass.vector_columns(map=map, layer=1)
  138. nulls = int(grass.parse_command('v.univar',
  139. map=map,
  140. column=column,
  141. type='point',
  142. parse = (grass.parse_key_val,
  143. {'sep':': '}
  144. )
  145. )['number of NULL attributes'])
  146. if nulls > 0:
  147. grass.fatal(_("%d NULL value(s) in the selected column - unable to perform kriging.") % nulls)
  148. return Rpointmap
  149. def CreateGrid(self, inputdata):
  150. Region = grass.region()
  151. Grid = robjects.r.gmeta2grd()
  152. # addition of coordinates columns into dataframe.
  153. coordinatesDF = robjects.r['as.data.frame'](robjects.r.coordinates(Grid))
  154. data = robjects.r['data.frame'](x = coordinatesDF.rx('s1')[0],
  155. y = coordinatesDF.rx('s2')[0],
  156. k = robjects.r.rep(1, Region['cols']*Region['rows']))
  157. GridPredicted = robjects.r.SpatialGridDataFrame(Grid,
  158. data,
  159. proj4string = robjects.r.CRS(robjects.r.proj4string(inputdata)))
  160. return GridPredicted
  161. def ComposeFormula(self, column, isblock):
  162. if isblock is True:
  163. predictor = 'x+y'
  164. else:
  165. predictor = '1'
  166. print column + "~" + predictor
  167. Formula = robjects.Formula(column + "~" + predictor)
  168. return Formula
  169. def FitVariogram(self, formula, inputdata, sill, nugget, range, model = ''):
  170. """ Fits variogram either automagically either specifying all parameters.
  171. Returns a list containing data and model variograms. """
  172. Variograms = {}
  173. if model is '':
  174. robjects.r.require('automap')
  175. DottedParams = {}
  176. #print (nugget.r_repr(), sill, range)
  177. DottedParams['fix.values'] = robjects.r.c(nugget, range, sill)
  178. VariogramModel = robjects.r.autofitVariogram(formula, inputdata, **DottedParams)
  179. #print robjects.r.warnings()
  180. Variograms['datavariogram'] = VariogramModel.rx('exp_var')[0]
  181. Variograms['variogrammodel'] = VariogramModel.rx('var_model')[0]
  182. # obtain the model name. *Too* complicated to get the string instead of level, unlike R does.
  183. VariogramAsDF = robjects.r['as.data.frame'](VariogramModel.rx('var_model')[0]) # force conversion
  184. ModelDF = VariogramAsDF.rx('model')[0]
  185. Variograms['model'] = ModelDF.levels[ModelDF[1] - 1]
  186. else:
  187. DataVariogram = robjects.r['variogram'](formula, inputdata)
  188. VariogramModel = robjects.r['fit.variogram'](DataVariogram,
  189. model = robjects.r.vgm(psill = sill,
  190. model = model,
  191. nugget = nugget,
  192. range = range))
  193. Variograms['datavariogram'] = DataVariogram
  194. Variograms['variogrammodel'] = VariogramModel
  195. Variograms['model'] = model
  196. return Variograms
  197. def DoKriging(self, formula, inputdata, grid, model, block):
  198. DottedParams = {'debug.level': -1} # let krige() print percentage status
  199. if block is not '': #@FIXME(anne): but it's a string!! and krige accepts it!!
  200. DottedParams['block'] = block
  201. #print DottedParams
  202. KrigingResult = robjects.r.krige(formula, inputdata, grid, model, **DottedParams)
  203. return KrigingResult
  204. def ExportMap(self, map, column, name, overwrite, command, variograms):
  205. # add kriging parameters to raster map history
  206. robjects.r.writeRAST6(map, vname = name, zcol = column, overwrite = overwrite)
  207. grass.run_command('r.support',
  208. map = name,
  209. title = 'Kriging output',
  210. history = 'Issued from command v.krige ' + command)
  211. if command.find('model') is -1: # if the command has no model option, add automap chosen model
  212. grass.run_command('r.support',
  213. map = name,
  214. history = 'Model chosen by automatic fitting: ' + variograms['model'])
  215. def Run(self, input, column, output, package, sill, nugget, range, logger, \
  216. overwrite, model, block, output_var, command, **kwargs):
  217. """ Wrapper for all functions above. """
  218. logger.message(_("Processing %d cells. Computing time raises "
  219. "exponentially with resolution." % grass.region()['cells']))
  220. logger.message(_("Importing data..."))
  221. if self.InputData is None:
  222. self.InputData = self.ImportMap(input, column)
  223. # and from here over, InputData refers to the global variable
  224. #print(robjects.r.slot(InputData, 'data').names)
  225. logger.message(_("Data successfully imported."))
  226. GridPredicted = self.CreateGrid(self.InputData)
  227. logger.message(_("Fitting variogram..."))
  228. if block is not '':
  229. self.predictor = 'x+y'
  230. else:
  231. self.predictor = '1'
  232. if self.Variogram is None:
  233. self.Variogram = self.FitVariogram(robjects.Formula(column + "~" + self.predictor),
  234. self.InputData,
  235. model = model,
  236. sill = sill,
  237. nugget = nugget,
  238. range = range)
  239. logger.message(_("Variogram fitting complete."))
  240. logger.message(_("Kriging..."))
  241. KrigingResult = self.DoKriging(robjects.Formula(column + "~" + self.predictor), self.InputData,
  242. GridPredicted, self.Variogram['variogrammodel'], block) # using global ones
  243. logger.message(_("Kriging complete."))
  244. self.ExportMap(map = KrigingResult,
  245. column='var1.pred',
  246. name = output,
  247. overwrite = overwrite,
  248. command = command,
  249. variograms = self.Variogram)
  250. if output_var is not '':
  251. self.ExportMap(map = KrigingResult,
  252. column='var1.var',
  253. name = output_var,
  254. overwrite = overwrite,
  255. command = command,
  256. variograms = self.Variogram)
  257. def main(argv = None):
  258. """ Main. Calls either GUI or CLI, depending on arguments provided. """
  259. #@FIXME: solve this double ifelse. the control should not be done twice.
  260. controller = Controller()
  261. if argv is None:
  262. importR()
  263. argv = sys.argv[1:] # stripping first item, the full name of this script
  264. # wxGUI call
  265. if not os.getenv("GRASS_WXBUNDLED"):
  266. from core import globalvar
  267. globalvar.CheckForWx()
  268. from modules import vkrige as GUI
  269. import wx
  270. app = wx.App()
  271. KrigingFrame = GUI.KrigingModule(parent = None,
  272. Rinstance = robjects,
  273. controller = controller)
  274. KrigingFrame.Centre()
  275. KrigingFrame.Show()
  276. app.MainLoop()
  277. else:
  278. #CLI
  279. options, flags = argv
  280. #@TODO: Work on verbosity. Sometimes it's too verbose (R), sometimes not enough.
  281. if grass.find_file(options['input'], element = 'vector')['fullname'] is '':
  282. grass.fatal(_("Vector map <%s> not found") % options['input'] )
  283. #@TODO: elaborate input string, if contains mapset or not.. thanks again to Bob for testing on 64bit.
  284. # create output map name, if not specified
  285. if options['output'] is '':
  286. try: # to strip mapset name from fullname. Ugh.
  287. options['input'] = options['input'].split("@")[0]
  288. except:
  289. pass
  290. options['output'] = options['input'] + '_kriging'
  291. # check for output map with same name. g.parser can't handle this, afaik.
  292. if grass.find_file(options['output'], element = 'cell')['fullname'] \
  293. and os.getenv("GRASS_OVERWRITE") == None:
  294. grass.fatal(_("option: <output>: Raster map already exists."))
  295. if options['output_var'] is not '' \
  296. and (grass.find_file(options['output_var'], element = 'cell')['fullname'] \
  297. and os.getenv("GRASS_OVERWRITE") == None):
  298. grass.fatal(_("option: <output>: Variance raster map already exists."))
  299. importR()
  300. if options['model'] is '':
  301. try:
  302. robjects.r.require("automap")
  303. except ImportError as e:
  304. grass.fatal(_("R package automap is missing, no variogram autofit available."))
  305. else:
  306. if options['sill'] is '' or options['nugget'] is '' or options['range'] is '':
  307. grass.fatal(_("You have specified model, but forgot at least one of sill, nugget and range."))
  308. #@TODO: let GRASS remount its commandstring. Until then, keep that 4 lines below.
  309. #print grass.write_command(argv)
  310. command = ""
  311. notnulloptions = {}
  312. for k, v in options.items():
  313. if v is not '':
  314. notnulloptions[k] = v
  315. command = command.join("%s=%s " % (k, v) for k, v in notnulloptions.items())
  316. # re-cast integers from strings, as parser() cast everything to string.
  317. for each in ("sill","nugget","range"):
  318. if options[each] is not '':
  319. options[each] = int(options[each])
  320. else:
  321. options[each] = robjects.r('''NA''')
  322. #controller = Controller()
  323. controller.Run(input = options['input'],
  324. column = options['column'],
  325. output = options['output'],
  326. overwrite = os.getenv("GRASS_OVERWRITE") == 1,
  327. package = options['package'],
  328. model = options['model'],
  329. block = options['block'],
  330. sill = options['sill'],
  331. nugget = options['nugget'],
  332. range = options['range'],
  333. output_var = options['output_var'],
  334. command = command,
  335. logger = grass)
  336. def importR():
  337. # R
  338. # unuseful since rpy2 will complain adequately.
  339. # try:
  340. # #@FIXME: in Windows, it launches R terminal
  341. # grass.find_program('R')
  342. # except:
  343. # sys.exit(_("R is not installed. Install it and re-run, or modify environment variables."))
  344. # rpy2
  345. global robjects
  346. global rinterface
  347. grass.message(_('Loading dependencies, please wait...'))
  348. try:
  349. import rpy2.robjects as robjects
  350. import rpy2.rinterface as rinterface #to speed up kriging? for plots.
  351. except ImportError:
  352. # ok for other OSes?
  353. grass.fatal(_("Python module 'Rpy2' not found. Please install it and re-run v.krige."))
  354. # R packages check. Will create one error message after check of all packages.
  355. missingPackagesList = []
  356. for each in ["rgeos", "gstat", "spgrass6", "maptools"]:
  357. if not robjects.r.require(each, quietly = True)[0]:
  358. missingPackagesList.append(each)
  359. if missingPackagesList:
  360. errorString = _("R package(s) ") + \
  361. ", ".join(map(str, missingPackagesList)) + \
  362. _(" missing. Install it/them and re-run v.krige.")
  363. grass.fatal(errorString)
  364. if __name__ == '__main__':
  365. if len(sys.argv) > 1:
  366. sys.exit(main(argv = grass.parser()))
  367. else:
  368. main()