v.krige.py 16 KB

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