v.krige.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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 for vector maps.
  15. #% keywords: vector
  16. #% keywords: raster
  17. #% keywords: interpolation
  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: Exp,Sph,Gau,Mat,Lin
  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. GUIModulesPath = os.path.join(os.getenv("GISBASE"), "etc", "gui", "wxpython", "gui_modules")
  92. sys.path.append(GUIModulesPath)
  93. GUIPath = os.path.join(os.getenv("GISBASE"), "etc", "gui", "wxpython", "scripts")
  94. sys.path.append(GUIPath)
  95. ### i18N
  96. import gettext
  97. gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
  98. ### dependencies to be checked once, as they are quite time-consuming. cfr. grass.parser.
  99. # GRASS binding
  100. try:
  101. import grass.script as grass
  102. except ImportError:
  103. sys.exit(_("No GRASS-python library found"))
  104. # move other checks in functions, as R?
  105. # globals
  106. Command = None
  107. InputData = None
  108. Variogram = None
  109. VariogramFunction = None
  110. robjects = None
  111. rinterface = None
  112. #classes in alphabetical order. methods in logical order :)
  113. # <2.5 class definition, without () - please test
  114. class Controller:
  115. """ Executes analysis. For the moment, only with gstat functions."""
  116. def ImportMap(self, map, column):
  117. """ Imports GRASS map as SpatialPointsDataFrame and adds x/y columns to attribute table.
  118. Checks for NULL values in the provided column and exits if they are present."""
  119. #@NOTE: new way with R - as it doesn't alter original data
  120. Rpointmap = robjects.r.readVECT6(map, type = 'point')
  121. # checks if x,y columns are present in dataframe. If they do are present, but with different names,
  122. # they'll be duplicated.
  123. if "x" not in robjects.r.names(Rpointmap):
  124. # extract coordinates with S4 method
  125. coordinatesPreDF = robjects.r['as.data.frame'](robjects.r.coordinates(Rpointmap))
  126. coordinatesDF = robjects.r['data.frame'](x = coordinatesPreDF.rx('coords.x1')[0],
  127. y = coordinatesPreDF.rx('coords.x2')[0])
  128. # match coordinates with data slot of SpatialPointsDataFrame - maptools function
  129. # match is done on row.names
  130. Rpointmap = robjects.r.spCbind(Rpointmap, coordinatesDF)
  131. # GRASS checks for null values in the chosen column. R can hardly handle column as a variable,
  132. # looks for a hardcoded string.
  133. logger.message("Got here")
  134. cols = grass.vector_columns(map=map, layer=1)
  135. logger.message("Not here:")
  136. nulls = int(grass.parse_command('v.univar',
  137. map=map,
  138. column=column,
  139. type='point',
  140. parse = (grass.parse_key_val,
  141. {'sep':': '}
  142. )
  143. )['number of NULL attributes'])
  144. if nulls > 0:
  145. grass.fatal(_("%d NULL value(s) in the selected column - unable to perform kriging.") % nulls)
  146. return Rpointmap
  147. def CreateGrid(self, inputdata):
  148. Region = grass.region()
  149. Grid = robjects.r.gmeta2grd()
  150. # addition of coordinates columns into dataframe.
  151. coordinatesDF = robjects.r['as.data.frame'](robjects.r.coordinates(Grid))
  152. data = robjects.r['data.frame'](x = coordinatesDF.rx('s1')[0],
  153. y = coordinatesDF.rx('s2')[0],
  154. k = robjects.r.rep(1, Region['cols']*Region['rows']))
  155. GridPredicted = robjects.r.SpatialGridDataFrame(Grid,
  156. data,
  157. proj4string = robjects.r.CRS(robjects.r.proj4string(inputdata)))
  158. return GridPredicted
  159. def ComposeFormula(self, column, isblock, inputdata):
  160. if isblock is True:
  161. predictor = 'x+y'
  162. else:
  163. predictor = 1
  164. Formula = robjects.Formula(column + "~" + predictor)
  165. #print Formula
  166. return Formula
  167. def FitVariogram(self, formula, inputdata, sill, nugget, range, model = ''):
  168. """ Fits variogram either automagically either specifying all parameters.
  169. Returns a list containing data and model variograms. """
  170. Variograms = {}
  171. if model is '':
  172. robjects.r.require('automap')
  173. DottedParams = {}
  174. #print (nugget.r_repr(), sill, range)
  175. DottedParams['fix.values'] = robjects.r.c(nugget, range, sill)
  176. VariogramModel = robjects.r.autofitVariogram(formula, inputdata, **DottedParams)
  177. #print robjects.r.warnings()
  178. Variograms['datavariogram'] = VariogramModel.rx('exp_var')[0]
  179. Variograms['variogrammodel'] = VariogramModel.rx('var_model')[0]
  180. # obtain the model name. *Too* complicated to get the string instead of level, unlike R does.
  181. VariogramAsDF = robjects.r['as.data.frame'](VariogramModel.rx('var_model')[0]) # force conversion
  182. ModelDF = VariogramAsDF.rx('model')[0]
  183. Variograms['model'] = ModelDF.levels[ModelDF[1] - 1]
  184. else:
  185. DataVariogram = robjects.r['variogram'](formula, inputdata)
  186. VariogramModel = robjects.r['fit.variogram'](DataVariogram,
  187. model = robjects.r.vgm(psill = sill,
  188. model = model,
  189. nugget = nugget,
  190. range = range))
  191. Variograms['datavariogram'] = DataVariogram
  192. Variograms['variogrammodel'] = VariogramModel
  193. Variograms['model'] = model
  194. return Variograms
  195. def DoKriging(self, formula, inputdata, grid, model, block):
  196. DottedParams = {'debug.level': -1} # let krige() print percentage status
  197. if block is not '': #@FIXME(anne): but it's a string!! and krige accepts it!!
  198. DottedParams['block'] = block
  199. #print DottedParams
  200. KrigingResult = robjects.r.krige(formula, inputdata, grid, model, **DottedParams)
  201. return KrigingResult
  202. def ExportMap(self, map, column, name, overwrite, command, variograms):
  203. # add kriging parameters to raster map history
  204. robjects.r.writeRAST6(map, vname = name, zcol = column, overwrite = overwrite)
  205. grass.run_command('r.support',
  206. map = name,
  207. title = 'Kriging output',
  208. history = 'Issued from command v.krige ' + command)
  209. if command.find('model') is -1: # if the command has no model option, add automap chosen model
  210. grass.run_command('r.support',
  211. map = name,
  212. history = 'Model chosen by automatic fitting: ' + variograms['model'])
  213. def Run(self, input, column, output, package, sill, nugget, range, logger, \
  214. overwrite, model, block, output_var, command, **kwargs):
  215. """ Wrapper for all functions above. """
  216. logger.message(_("Processing %d cells. Computing time raises "
  217. "exponentially with resolution." % grass.region()['cells']))
  218. logger.message(_("Importing data..."))
  219. if globals()["InputData"] is None:
  220. globals()["InputData"] = self.ImportMap(input, column)
  221. # and from here over, InputData refers to the global variable
  222. #print(robjects.r.slot(InputData, 'data').names)
  223. logger.message(_("Data successfully imported."))
  224. GridPredicted = self.CreateGrid(InputData)
  225. logger.message(_("Fitting variogram..."))
  226. isblock = block is not ''
  227. Formula = self.ComposeFormula(column, isblock, InputData)
  228. if globals()["Variogram"] is None:
  229. globals()["Variogram"] = self.FitVariogram(Formula,
  230. InputData,
  231. model = model,
  232. sill = sill,
  233. nugget = nugget,
  234. range = range)
  235. logger.message(_("Variogram fitted."))
  236. logger.message(_("Kriging..."))
  237. KrigingResult = self.DoKriging(Formula, InputData, GridPredicted, Variogram['variogrammodel'], block) # using global ones
  238. logger.message(_("Kriging performed."))
  239. self.ExportMap(map = KrigingResult,
  240. column='var1.pred',
  241. name = output,
  242. overwrite = overwrite,
  243. command = command,
  244. variograms = Variogram)
  245. if output_var is not '':
  246. self.ExportMap(map = KrigingResult,
  247. column='var1.var',
  248. name = output_var,
  249. overwrite = overwrite,
  250. command = command,
  251. variograms = Variogram)
  252. def main(argv = None):
  253. """ Main. Calls either GUI or CLI, depending on arguments provided. """
  254. #@FIXME: solve this double ifelse. the control should not be done twice.
  255. controller = Controller()
  256. if argv is None:
  257. importR()
  258. argv = sys.argv[1:] # stripping first item, the full name of this script
  259. # wxGUI call
  260. import globalvar
  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()