v.krige.py 16 KB

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