grid.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Thu Mar 28 11:06:00 2013
  4. @author: pietro
  5. """
  6. import os
  7. import multiprocessing as mltp
  8. import subprocess as sub
  9. import shutil as sht
  10. from grass.script.setup import write_gisrc
  11. from grass.pygrass.gis import Mapset, Location, make_mapset
  12. from grass.pygrass.gis.region import Region
  13. from grass.pygrass.modules import Module
  14. from grass.pygrass.functions import get_mapset_raster
  15. from split import split_region_tiles
  16. from patch import patch_map
  17. def select(parms, ptype):
  18. """Select only a certain type of parameters. ::
  19. >>> slp = Module('r.slope.aspect',
  20. ... elevation='ele', slope='slp', aspect='asp',
  21. ... run_=False)
  22. >>> for rast in select(slp.outputs, 'raster'):
  23. ... print rast
  24. ...
  25. slp
  26. asp
  27. """
  28. for k in parms:
  29. par = parms[k]
  30. if par.type == ptype or par.typedesc == ptype and par.value:
  31. if par.multiple:
  32. for p in par.value:
  33. yield p
  34. else:
  35. yield par.value
  36. def copy_mapset(mapset, path):
  37. """Copy mapset to another place without copying raster and vector data.
  38. """
  39. per_old = os.path.join(mapset.gisdbase, mapset.location, 'PERMANENT')
  40. per_new = os.path.join(path, 'PERMANENT')
  41. map_old = mapset.path()
  42. map_new = os.path.join(path, mapset.name)
  43. if not os.path.isdir(per_new):
  44. os.makedirs(per_new)
  45. if not os.path.isdir(map_new):
  46. os.mkdir(map_new)
  47. for f in (fi for fi in os.listdir(per_old) if fi.isupper()):
  48. sht.copy(os.path.join(per_old, f), per_new)
  49. for f in (fi for fi in os.listdir(map_old) if fi.isupper()):
  50. sht.copy(os.path.join(map_old, f), map_new)
  51. gisdbase, location = os.path.split(path)
  52. return Mapset(mapset.name, location, gisdbase)
  53. def copy_raster(rasters, src, dst, region=None, gisrc_dst=None):
  54. """Copy raster from one mapset to another, crop the raster to the region.
  55. """
  56. # set region
  57. if region:
  58. region.set_current()
  59. nam = "copy%d__%s" % (id(dst), '%s')
  60. expr = "%s=%s"
  61. # instantiate modules
  62. mpclc = Module('r.mapcalc')
  63. rpck = Module('r.pack')
  64. rupck = Module('r.unpack')
  65. rm = Module('g.remove')
  66. # get and set GISRC
  67. gisrc_src = os.environ['GISRC']
  68. gisrc_dst = gisrc_dst if gisrc_dst else write_gisrc(dst.gisdbase,
  69. dst.location,
  70. dst.name)
  71. pdst = dst.path()
  72. for rast in rasters:
  73. # change gisdbase to src
  74. os.environ['GISRC'] = gisrc_src
  75. src.current()
  76. name = nam % rast
  77. mpclc(expression=expr % (name, rast), overwrite=True)
  78. file_dst = "%s.pack" % os.path.join(pdst, name)
  79. rpck(input=name, output=file_dst, overwrite=True)
  80. rm(rast=name)
  81. # change gisdbase to dst
  82. os.environ['GISRC'] = gisrc_dst
  83. dst.current()
  84. rupck(input=file_dst, output=rast, overwrite=True)
  85. os.remove(file_dst)
  86. return gisrc_src, gisrc_dst
  87. def get_cmd(cmdd):
  88. """Transforma a cmd dictionary to a list of parameters"""
  89. cmd = [cmdd['name'], ]
  90. cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['inputs']
  91. if not isinstance(v, list)))
  92. cmd.extend(("%s=%s" % (k, ','.join(vals if isinstance(vals[0], str)
  93. else map(repr, vals)))
  94. for k, vals in cmdd['inputs']
  95. if isinstance(vals, list)))
  96. cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['outputs']
  97. if not isinstance(v, list)))
  98. cmd.extend(("%s=%s" % (k, ','.join(map(repr, vals)))
  99. for k, vals in cmdd['outputs']
  100. if isinstance(vals, list)))
  101. cmd.extend(("%s" % (flg) for flg in cmdd['flags'] if len(flg) == 1))
  102. cmd.extend(("--%s" % (flg[0]) for flg in cmdd['flags'] if len(flg) > 1))
  103. return cmd
  104. def cmd_exe(args):
  105. """Create a mapset, and execute a cmd inside."""
  106. bbox, mapnames, msetname, cmd = args
  107. mset = Mapset()
  108. try:
  109. make_mapset(msetname)
  110. except:
  111. pass
  112. ms = Mapset(msetname)
  113. ms.visible.extend(mset.visible)
  114. env = os.environ.copy()
  115. env['GISRC'] = write_gisrc(mset.gisdbase, mset.location, msetname)
  116. if mapnames:
  117. inputs = dict(cmd['inputs'])
  118. # reset the inputs to
  119. for key in mapnames:
  120. inputs[key] = mapnames[key]
  121. cmd['inputs'] = inputs.items()
  122. # set the region to the tile
  123. sub.Popen(['g,region', 'rast=%s' % key], env=env).wait()
  124. else:
  125. #reg = Region() nsres=reg.nsres, ewres=reg.ewres,
  126. # set the computational region
  127. lcmd = ['g.region', ]
  128. lcmd.extend(["%s=%s" % (k, v) for k, v in bbox.iteritems()])
  129. sub.Popen(lcmd, env=env).wait()
  130. # run the grass command
  131. sub.Popen(get_cmd(cmd), env=env).wait()
  132. class GridModule(object):
  133. """Run GRASS raster commands in a multiproccessing mode.
  134. Parameters
  135. -----------
  136. cmd: raster GRASS command
  137. Only command staring with r.* are valid.
  138. width: integer
  139. Width of the tile, in pixel.
  140. height: integer
  141. Height of the tile, in pixel.
  142. overlap: integer
  143. Overlap between tiles, in pixel.
  144. nthreads: number of threads
  145. Default value is equal to the number of processor available.
  146. split: boolean
  147. If True use r.tile to split all the inputs.
  148. run_: boolean
  149. If False only instantiate the object.
  150. args and kargs: cmd parameters
  151. Give all the parameters to the command.
  152. Examples
  153. --------
  154. ::
  155. >>> grd = GridModule('r.slope.aspect',
  156. ... width=500, height=500, overlap=2,
  157. ... processes=None, split=True,
  158. ... elevation='elevation',
  159. ... slope='slope', aspect='aspect', overwrite=True)
  160. >>> grd.run()
  161. """
  162. def __init__(self, cmd, width=None, height=None, overlap=0, processes=None,
  163. split=False, debug=False, region=None, move=None, log=False,
  164. start_row=0, start_col=0, out_prefix='',
  165. *args, **kargs):
  166. kargs['run_'] = False
  167. self.mset = Mapset()
  168. self.module = Module(cmd, *args, **kargs)
  169. self.width = width
  170. self.height = height
  171. self.overlap = overlap
  172. self.processes = processes
  173. self.region = region if region else Region()
  174. self.start_row = start_row
  175. self.start_col = start_col
  176. self.out_prefix = out_prefix
  177. self.n_mset = None
  178. self.gisrc_src = self.gisrc_dst = None
  179. self.log = log
  180. if move:
  181. self.n_mset = copy_mapset(self.mset, move)
  182. rasters = select(self.module.inputs, 'raster')
  183. self.gisrc_src, self.gisrc_dst = copy_raster(rasters,
  184. self.mset,
  185. self.n_mset,
  186. region=self.region)
  187. self.bboxes = split_region_tiles(region=region,
  188. width=width, height=height,
  189. overlap=overlap)
  190. self.msetstr = cmd.replace('.', '') + "_%03d_%03d"
  191. self.inlist = None
  192. if split:
  193. self.split()
  194. self.debug = debug
  195. def clean_location(self):
  196. """Remove all created mapsets."""
  197. mapsets = Location().mapsets(self.msetstr.split('_')[0] + '_*')
  198. for mset in mapsets:
  199. Mapset(mset).delete()
  200. def split(self):
  201. """Split all the raster inputs using r.tile"""
  202. rtile = Module('r.tile')
  203. inlist = {}
  204. for inm in select(self.module.inputs, 'raster'):
  205. rtile(input=inm.value, output=inm.value,
  206. width=self.width, height=self.height,
  207. overlap=self.overlap)
  208. patt = '%s-*' % inm.value
  209. inlist[inm.value] = sorted(self.mset.glist(type='rast',
  210. pattern=patt))
  211. self.inlist = inlist
  212. def get_works(self):
  213. """Return a list of tuble with the parameters for cmd_exe function"""
  214. works = []
  215. reg = Region()
  216. cmd = self.module.get_dict()
  217. for row, box_row in enumerate(self.bboxes):
  218. for col, box in enumerate(box_row):
  219. inms = None
  220. if self.inlist:
  221. inms = {}
  222. cols = len(box_row)
  223. for key in self.inlist:
  224. indx = row * cols + col
  225. inms[key] = "%s@%s" % (self.inlist[key][indx],
  226. self.mset.name)
  227. # set the computational region, prepare the region parameters
  228. bbox = dict([(k[0], str(v)) for k, v in box.items()[:-2]])
  229. bbox['nsres'] = '%f' % reg.nsres
  230. bbox['ewres'] = '%f' % reg.ewres
  231. works.append((bbox, inms,
  232. self.msetstr % (self.start_row + row,
  233. self.start_col + col),
  234. cmd))
  235. return works
  236. def define_mapset_inputs(self):
  237. for inmap in self.module.inputs:
  238. inm = self.module.inputs[inmap]
  239. # (inm.type == 'raster' or inm.typedesc == 'group') and inm.value:
  240. if inm.type == 'raster' and inm.value:
  241. if '@' not in inm.value:
  242. mset = get_mapset_raster(inm.value)
  243. inm.value = inm.value + '@%s' % mset
  244. def run(self, patch=True, clean=True):
  245. """Run the GRASS command."""
  246. self.module.flags.overwrite = True
  247. self.define_mapset_inputs()
  248. if self.debug:
  249. for wrk in self.get_works():
  250. cmd_exe(wrk)
  251. else:
  252. pool = mltp.Pool(processes=self.processes)
  253. result = pool.map_async(cmd_exe, self.get_works())
  254. result.wait()
  255. if not result.successful():
  256. raise RuntimeError
  257. if patch:
  258. self.patch()
  259. if self.n_mset is not None:
  260. # move the outputs to the original mapset
  261. outputs = [self.out_prefix + o
  262. for o in select(self.module.outputs, 'raster')]
  263. copy_raster(outputs, self.n_mset, self.mset, self.region,
  264. self.gisrc_src)
  265. if self.log:
  266. # record in the temp directory
  267. from grass.lib.gis import G_tempfile
  268. tmp, dummy = os.path.split(G_tempfile())
  269. tmpdir = os.path.join(tmp, self.module.name)
  270. for k in self.module.outputs:
  271. par = self.module.outputs[k]
  272. if par.typedesc == 'raster' and par.value:
  273. dirpath = os.path.join(tmpdir, par.name)
  274. if not os.path.isdir(dirpath):
  275. os.makedirs(dirpath)
  276. fp = open(os.path.join(dirpath,
  277. self.out_prefix + par.value), 'w+')
  278. fp.close()
  279. if clean:
  280. self.clean_location()
  281. self.rm_tiles()
  282. def patch(self):
  283. """Patch the final results."""
  284. # patch all the outputs
  285. for otmap in self.module.outputs:
  286. otm = self.module.outputs[otmap]
  287. if otm.typedesc == 'raster' and otm.value:
  288. patch_map(otm.value,
  289. self.mset.name, self.msetstr,
  290. split_region_tiles(width=self.width,
  291. height=self.height),
  292. self.module.flags.overwrite,
  293. self.start_row, self.start_col, self.out_prefix)
  294. def rm_tiles(self):
  295. """Remove all the tiles."""
  296. # if split, remove tiles
  297. if self.inlist:
  298. grm = Module('g.remove')
  299. for key in self.inlist:
  300. grm(rast=self.inlist[key])