grid.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  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
  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 grass.pygrass.modules.grid.split import split_region_tiles
  16. from grass.pygrass.modules.grid.patch import rpatch_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 val in par.value:
  33. yield val
  34. else:
  35. yield par.value
  36. def copy_special_mapset_files(path_src, path_dst):
  37. """Copy all the special GRASS files that are contained in
  38. a mapset to another mapset."""
  39. for fil in (fi for fi in os.listdir(path_src) if fi.isupper()):
  40. sht.copy(os.path.join(path_src, fil), path_dst)
  41. def copy_mapset(mapset, path):
  42. """Copy mapset to another place without copying raster and vector data.
  43. """
  44. per_old = os.path.join(mapset.gisdbase, mapset.location, 'PERMANENT')
  45. per_new = os.path.join(path, 'PERMANENT')
  46. map_old = mapset.path()
  47. map_new = os.path.join(path, mapset.name)
  48. if not os.path.isdir(per_new):
  49. os.makedirs(per_new)
  50. if not os.path.isdir(map_new):
  51. os.mkdir(map_new)
  52. copy_special_mapset_files(per_old, per_new)
  53. copy_special_mapset_files(map_old, map_new)
  54. gisdbase, location = os.path.split(path)
  55. return Mapset(mapset.name, location, gisdbase)
  56. def read_gisrc(gisrc):
  57. """Read a GISRC file and return a tuple with the mapset, location
  58. and gisdbase.
  59. """
  60. with open(gisrc, 'r') as gfile:
  61. gis = dict([(k.strip(), v.strip())
  62. for k, v in [row.split(':') for row in gfile]])
  63. return gis['MAPSET'], gis['LOCATION_NAME'], gis['GISDBASE']
  64. def get_mapset(gisrc_src, gisrc_dst):
  65. """Get mapset from a GISRC source to a GISRC destination."""
  66. msrc, lsrc, gsrc = read_gisrc(gisrc_src)
  67. mdst, ldst, gdst = read_gisrc(gisrc_dst)
  68. path_src = os.path.join(gsrc, lsrc, msrc)
  69. path_dst = os.path.join(gdst, ldst, mdst)
  70. if not os.path.isdir(path_dst):
  71. os.makedirs(path_dst)
  72. copy_special_mapset_files(path_src, path_dst)
  73. src = Mapset(msrc, lsrc, gsrc)
  74. dst = Mapset(mdst, ldst, gdst)
  75. dst.visible.extend(src.visible)
  76. return src, dst
  77. def copy_groups(groups, gisrc_src, gisrc_dst, region=None):
  78. """Copy group from one mapset to another, crop the raster to the region.
  79. """
  80. env = os.environ.copy()
  81. # instantiate modules
  82. get_grp = Module('i.group', flags='lg', stdout_=sub.PIPE, run_=False)
  83. set_grp = Module('i.group')
  84. get_grp.run_ = True
  85. for grp in groups:
  86. # change gisdbase to src
  87. env['GISRC'] = gisrc_src
  88. get_grp(group=grp, env_=env)
  89. rasts = get_grp.outputs.stdout.split()
  90. copy_rasters(rasts, gisrc_src, gisrc_dst, region=region)
  91. # change gisdbase to dst
  92. env['GISRC'] = gisrc_dst
  93. set_grp(group=grp,
  94. input=[r.split('@')[0] if '@' in r else r for r in rasts],
  95. env_=env)
  96. def set_region(region, gisrc_src, gisrc_dst, env):
  97. """Set a region into two different mapsets."""
  98. reg_str = "g.region n=%(north)r s=%(south)r " \
  99. "e=%(east)r w=%(west)r " \
  100. "nsres=%(nsres)r ewres=%(ewres)r"
  101. reg_cmd = reg_str % dict(region.items())
  102. env['GISRC'] = gisrc_src
  103. sub.Popen(reg_cmd, shell=True, env=env)
  104. env['GISRC'] = gisrc_dst
  105. sub.Popen(reg_cmd, shell=True, env=env)
  106. def copy_rasters(rasters, gisrc_src, gisrc_dst, region=None):
  107. """Copy rasters from one mapset to another, crop the raster to the region.
  108. """
  109. env = os.environ.copy()
  110. if region:
  111. set_region(region, gisrc_src, gisrc_dst, env)
  112. path_dst = os.path.join(*read_gisrc(gisrc_dst))
  113. nam = "copy%d__%s" % (id(gisrc_dst), '%s')
  114. # instantiate modules
  115. mpclc = Module('r.mapcalc')
  116. rpck = Module('r.pack')
  117. rupck = Module('r.unpack')
  118. remove = Module('g.remove')
  119. for rast in rasters:
  120. rast_clean = rast.split('@')[0] if '@' in rast else rast
  121. # change gisdbase to src
  122. env['GISRC'] = gisrc_src
  123. name = nam % rast_clean
  124. mpclc(expression="%s=%s" % (name, rast), overwrite=True, env_=env)
  125. file_dst = "%s.pack" % os.path.join(path_dst, name)
  126. rpck(input=name, output=file_dst, overwrite=True, env_=env)
  127. remove(rast=name, env_=env)
  128. # change gisdbase to dst
  129. env['GISRC'] = gisrc_dst
  130. rupck(input=file_dst, output=rast_clean, overwrite=True, env_=env)
  131. os.remove(file_dst)
  132. def copy_vectors(vectors, gisrc_src, gisrc_dst):
  133. """Copy vectors from one mapset to another, crop the raster to the region.
  134. """
  135. env = os.environ.copy()
  136. path_dst = os.path.join(*read_gisrc(gisrc_dst))
  137. nam = "copy%d__%s" % (id(gisrc_dst), '%s')
  138. # instantiate modules
  139. vpck = Module('v.pack')
  140. vupck = Module('v.unpack')
  141. remove = Module('g.remove')
  142. for vect in vectors:
  143. # change gisdbase to src
  144. env['GISRC'] = gisrc_src
  145. name = nam % vect
  146. file_dst = "%s.pack" % os.path.join(path_dst, name)
  147. vpck(input=name, output=file_dst, overwrite=True, env_=env)
  148. remove(vect=name, env_=env)
  149. # change gisdbase to dst
  150. env['GISRC'] = gisrc_dst
  151. vupck(input=file_dst, output=vect, overwrite=True, env_=env)
  152. os.remove(file_dst)
  153. def get_cmd(cmdd):
  154. """Transform a cmd dictionary to a list of parameters"""
  155. cmd = [cmdd['name'], ]
  156. cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['inputs']
  157. if not isinstance(v, list)))
  158. cmd.extend(("%s=%s" % (k, ','.join(vals if isinstance(vals[0], str)
  159. else [repr(v) for v in vals]))
  160. for k, vals in cmdd['inputs']
  161. if isinstance(vals, list)))
  162. cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['outputs']
  163. if not isinstance(v, list)))
  164. cmd.extend(("%s=%s" % (k, ','.join([repr(v) for v in vals]))
  165. for k, vals in cmdd['outputs'] if isinstance(vals, list)))
  166. cmd.extend(("%s" % (flg) for flg in cmdd['flags'] if len(flg) == 1))
  167. cmd.extend(("--%s" % (flg[0]) for flg in cmdd['flags'] if len(flg) > 1))
  168. return cmd
  169. def cmd_exe(args):
  170. """Create a mapset, and execute a cmd inside."""
  171. bbox, mapnames, gisrc_src, gisrc_dst, cmd, groups = args
  172. get_mapset(gisrc_src, gisrc_dst)
  173. env = os.environ.copy()
  174. env['GISRC'] = gisrc_dst
  175. if mapnames:
  176. inputs = dict(cmd['inputs'])
  177. # reset the inputs to
  178. for key in mapnames:
  179. inputs[key] = mapnames[key]
  180. cmd['inputs'] = inputs.items()
  181. # set the region to the tile
  182. sub.Popen(['g,region', 'rast=%s' % key], env=env).wait()
  183. else:
  184. # set the computational region
  185. lcmd = ['g.region', ]
  186. lcmd.extend(["%s=%s" % (k, v) for k, v in bbox.iteritems()])
  187. sub.Popen(lcmd, env=env).wait()
  188. if groups:
  189. copy_groups(groups, gisrc_src, gisrc_dst)
  190. # run the grass command
  191. sub.Popen(get_cmd(cmd), env=env).wait()
  192. # remove temp GISRC
  193. os.remove(gisrc_dst)
  194. class GridModule(object):
  195. """Run GRASS raster commands in a multiproccessing mode.
  196. Parameters
  197. -----------
  198. cmd: raster GRASS command
  199. Only command staring with r.* are valid.
  200. width: integer
  201. Width of the tile, in pixel.
  202. height: integer
  203. Height of the tile, in pixel.
  204. overlap: integer
  205. Overlap between tiles, in pixel.
  206. processes: number of threads
  207. Default value is equal to the number of processor available.
  208. split: boolean
  209. If True use r.tile to split all the inputs.
  210. run_: boolean
  211. If False only instantiate the object.
  212. args and kargs: cmd parameters
  213. Give all the parameters to the command.
  214. Examples
  215. --------
  216. ::
  217. >>> grd = GridModule('r.slope.aspect',
  218. ... width=500, height=500, overlap=2,
  219. ... processes=None, split=True,
  220. ... elevation='elevation',
  221. ... slope='slope', aspect='aspect', overwrite=True)
  222. >>> grd.run()
  223. """
  224. def __init__(self, cmd, width=None, height=None, overlap=0, processes=None,
  225. split=False, debug=False, region=None, move=None, log=False,
  226. start_row=0, start_col=0, out_prefix='',
  227. *args, **kargs):
  228. kargs['run_'] = False
  229. self.mset = Mapset()
  230. self.module = Module(cmd, *args, **kargs)
  231. self.width = width
  232. self.height = height
  233. self.overlap = overlap
  234. self.processes = processes
  235. self.region = region if region else Region()
  236. self.start_row = start_row
  237. self.start_col = start_col
  238. self.out_prefix = out_prefix
  239. self.log = log
  240. self.move = move
  241. self.gisrc_src = os.environ['GISRC']
  242. self.n_mset, self.gisrc_dst = None, None
  243. if self.move:
  244. self.n_mset = copy_mapset(self.mset, self.move)
  245. self.gisrc_dst = write_gisrc(self.n_mset.gisdbase,
  246. self.n_mset.location,
  247. self.n_mset.name)
  248. rasters = [r for r in select(self.module.inputs, 'raster')]
  249. if rasters:
  250. copy_rasters(rasters, self.gisrc_src, self.gisrc_dst,
  251. region=self.region)
  252. vectors = [v for v in select(self.module.inputs, 'vector')]
  253. if vectors:
  254. copy_vectors(vectors, self.gisrc_src, self.gisrc_dst)
  255. groups = [g for g in select(self.module.inputs, 'group')]
  256. if groups:
  257. copy_groups(groups, self.gisrc_src, self.gisrc_dst,
  258. region=self.region)
  259. self.bboxes = split_region_tiles(region=region,
  260. width=width, height=height,
  261. overlap=overlap)
  262. self.msetstr = cmd.replace('.', '') + "_%03d_%03d"
  263. self.inlist = None
  264. if split:
  265. self.split()
  266. self.debug = debug
  267. def __del__(self):
  268. if self.gisrc_dst:
  269. # remove GISRC file
  270. os.remove(self.gisrc_dst)
  271. def clean_location(self, location=None):
  272. """Remove all created mapsets."""
  273. location = location if location else Location()
  274. mapsets = location.mapsets(self.msetstr.split('_')[0] + '_*')
  275. for mset in mapsets:
  276. Mapset(mset).delete()
  277. def split(self):
  278. """Split all the raster inputs using r.tile"""
  279. rtile = Module('r.tile')
  280. inlist = {}
  281. for inm in select(self.module.inputs, 'raster'):
  282. rtile(input=inm.value, output=inm.value,
  283. width=self.width, height=self.height,
  284. overlap=self.overlap)
  285. patt = '%s-*' % inm.value
  286. inlist[inm.value] = sorted(self.mset.glist(type='rast',
  287. pattern=patt))
  288. self.inlist = inlist
  289. def get_works(self):
  290. """Return a list of tuble with the parameters for cmd_exe function"""
  291. works = []
  292. reg = Region()
  293. if self.move:
  294. mdst, ldst, gdst = read_gisrc(self.gisrc_dst)
  295. else:
  296. ldst, gdst = self.mset.location, self.mset.gisdbase
  297. cmd = self.module.get_dict()
  298. groups = [g for g in select(self.module.inputs, 'group')]
  299. for row, box_row in enumerate(self.bboxes):
  300. for col, box in enumerate(box_row):
  301. inms = None
  302. if self.inlist:
  303. inms = {}
  304. cols = len(box_row)
  305. for key in self.inlist:
  306. indx = row * cols + col
  307. inms[key] = "%s@%s" % (self.inlist[key][indx],
  308. self.mset.name)
  309. # set the computational region, prepare the region parameters
  310. bbox = dict([(k[0], str(v)) for k, v in box.items()[:-2]])
  311. bbox['nsres'] = '%f' % reg.nsres
  312. bbox['ewres'] = '%f' % reg.ewres
  313. new_mset = self.msetstr % (self.start_row + row,
  314. self.start_col + col),
  315. works.append((bbox, inms,
  316. self.gisrc_src,
  317. write_gisrc(gdst, ldst, new_mset),
  318. cmd, groups))
  319. return works
  320. def define_mapset_inputs(self):
  321. """Add the mapset information to the input maps
  322. """
  323. for inmap in self.module.inputs:
  324. inm = self.module.inputs[inmap]
  325. if inm.type in ('raster', 'vector') and inm.value:
  326. if '@' not in inm.value:
  327. mset = get_mapset_raster(inm.value)
  328. inm.value = inm.value + '@%s' % mset
  329. def run(self, patch=True, clean=True):
  330. """Run the GRASS command."""
  331. self.module.flags.overwrite = True
  332. self.define_mapset_inputs()
  333. if self.debug:
  334. for wrk in self.get_works():
  335. cmd_exe(wrk)
  336. else:
  337. pool = mltp.Pool(processes=self.processes)
  338. result = pool.map_async(cmd_exe, self.get_works())
  339. result.wait()
  340. if not result.successful():
  341. raise RuntimeError
  342. if patch:
  343. if self.move:
  344. os.environ['GISRC'] = self.gisrc_dst
  345. self.n_mset.current()
  346. self.patch()
  347. os.environ['GISRC'] = self.gisrc_src
  348. self.mset.current()
  349. # copy the outputs from dst => src
  350. routputs = [self.out_prefix + o
  351. for o in select(self.module.outputs, 'raster')]
  352. copy_rasters(routputs, self.gisrc_dst, self.gisrc_src)
  353. else:
  354. self.patch()
  355. if self.log:
  356. # record in the temp directory
  357. from grass.lib.gis import G_tempfile
  358. tmp, dummy = os.path.split(G_tempfile())
  359. tmpdir = os.path.join(tmp, self.module.name)
  360. for k in self.module.outputs:
  361. par = self.module.outputs[k]
  362. if par.typedesc == 'raster' and par.value:
  363. dirpath = os.path.join(tmpdir, par.name)
  364. if not os.path.isdir(dirpath):
  365. os.makedirs(dirpath)
  366. fil = open(os.path.join(dirpath,
  367. self.out_prefix + par.value), 'w+')
  368. fil.close()
  369. if clean:
  370. self.clean_location()
  371. self.rm_tiles()
  372. if self.n_mset:
  373. gisdbase, location = os.path.split(self.move)
  374. self.clean_location(Location(location, gisdbase))
  375. # rm temporary gis_rc
  376. os.remove(self.gisrc_dst)
  377. self.gisrc_dst = None
  378. sht.rmtree(os.path.join(self.move, 'PERMANENT'))
  379. sht.rmtree(os.path.join(self.move, self.mset.name))
  380. def patch(self):
  381. """Patch the final results."""
  382. bboxes = split_region_tiles(width=self.width, height=self.height)
  383. for otmap in self.module.outputs:
  384. otm = self.module.outputs[otmap]
  385. if otm.typedesc == 'raster' and otm.value:
  386. rpatch_map(otm.value,
  387. self.mset.name, self.msetstr, bboxes,
  388. self.module.flags.overwrite,
  389. self.start_row, self.start_col, self.out_prefix)
  390. def rm_tiles(self):
  391. """Remove all the tiles."""
  392. # if split, remove tiles
  393. if self.inlist:
  394. grm = Module('g.remove')
  395. for key in self.inlist:
  396. grm(rast=self.inlist[key])