grid.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. from __future__ import (
  2. nested_scopes,
  3. generators,
  4. division,
  5. absolute_import,
  6. with_statement,
  7. print_function,
  8. unicode_literals,
  9. )
  10. import os
  11. import sys
  12. import multiprocessing as mltp
  13. import subprocess as sub
  14. import shutil as sht
  15. from math import ceil
  16. from grass.script.setup import write_gisrc
  17. from grass.script import append_node_pid, legalize_vector_name
  18. from grass.pygrass.gis import Mapset, Location
  19. from grass.pygrass.gis.region import Region
  20. from grass.pygrass.modules import Module
  21. from grass.pygrass.utils import get_mapset_raster, findmaps
  22. from grass.pygrass.modules.grid.split import split_region_tiles
  23. from grass.pygrass.modules.grid.patch import rpatch_map, rpatch_map_r_patch_backend
  24. def select(parms, ptype):
  25. """Select only a certain type of parameters.
  26. :param parms: a DictType parameter with inputs or outputs of a Module class
  27. :type parms: DictType parameters
  28. :param ptype: String define the type of parameter that we want to select,
  29. valid ptype are: 'raster', 'vector', 'group'
  30. :type ptype: str
  31. :returns: An iterator with the value of the parameter.
  32. >>> slp = Module('r.slope.aspect',
  33. ... elevation='ele', slope='slp', aspect='asp',
  34. ... run_=False)
  35. >>> for rast in select(slp.outputs, 'raster'):
  36. ... print(rast)
  37. ...
  38. slp
  39. asp
  40. """
  41. for k in parms:
  42. par = parms[k]
  43. if par.type == ptype or par.typedesc == ptype and par.value:
  44. if par.multiple:
  45. for val in par.value:
  46. yield val
  47. else:
  48. yield par.value
  49. def copy_special_mapset_files(path_src, path_dst):
  50. """Copy all the special GRASS files that are contained in
  51. a mapset to another mapset
  52. :param path_src: the path to the original mapset
  53. :type path_src: str
  54. :param path_dst: the path to the new mapset
  55. :type path_dst: str
  56. """
  57. for fil in (fi for fi in os.listdir(path_src) if fi.isupper()):
  58. sht.copy(os.path.join(path_src, fil), path_dst)
  59. def copy_mapset(mapset, path):
  60. """Copy mapset to another place without copying raster and vector data.
  61. :param mapset: a Mapset instance to copy
  62. :type mapset: Mapset object
  63. :param path: path where the new mapset must be copied
  64. :type path: str
  65. :returns: the instance of the new Mapset.
  66. >>> from grass.script.core import gisenv
  67. >>> mname = gisenv()['MAPSET']
  68. >>> mset = Mapset()
  69. >>> mset.name == mname
  70. True
  71. >>> import tempfile as tmp
  72. >>> import os
  73. >>> path = os.path.join(tmp.gettempdir(), 'my_loc', 'my_mset')
  74. >>> copy_mapset(mset, path) # doctest: +ELLIPSIS
  75. Mapset(...)
  76. >>> sorted(os.listdir(path)) # doctest: +ELLIPSIS
  77. [...'PERMANENT'...]
  78. >>> sorted(os.listdir(os.path.join(path, 'PERMANENT')))
  79. ['DEFAULT_WIND', 'PROJ_INFO', 'PROJ_UNITS', 'VAR', 'WIND']
  80. >>> sorted(os.listdir(os.path.join(path, mname))) # doctest: +ELLIPSIS
  81. [...'SEARCH_PATH',...'WIND']
  82. >>> import shutil
  83. >>> shutil.rmtree(path)
  84. """
  85. per_old = os.path.join(mapset.gisdbase, mapset.location, "PERMANENT")
  86. per_new = os.path.join(path, "PERMANENT")
  87. map_old = mapset.path()
  88. map_new = os.path.join(path, mapset.name)
  89. if not os.path.isdir(per_new):
  90. os.makedirs(per_new)
  91. if not os.path.isdir(map_new):
  92. os.mkdir(map_new)
  93. copy_special_mapset_files(per_old, per_new)
  94. copy_special_mapset_files(map_old, map_new)
  95. gisdbase, location = os.path.split(path)
  96. return Mapset(mapset.name, location, gisdbase)
  97. def read_gisrc(gisrc):
  98. """Read a GISRC file and return a tuple with the mapset, location
  99. and gisdbase.
  100. :param gisrc: the path to GISRC file
  101. :type gisrc: str
  102. :returns: a tuple with the mapset, location and gisdbase
  103. >>> import os
  104. >>> from grass.script.core import gisenv
  105. >>> genv = gisenv()
  106. >>> (read_gisrc(os.environ['GISRC']) == (genv['MAPSET'],
  107. ... genv['LOCATION_NAME'],
  108. ... genv['GISDBASE']))
  109. True
  110. """
  111. with open(gisrc, "r") as gfile:
  112. gis = dict(
  113. [(k.strip(), v.strip()) for k, v in [row.split(":", 1) for row in gfile]]
  114. )
  115. return gis["MAPSET"], gis["LOCATION_NAME"], gis["GISDBASE"]
  116. def get_mapset(gisrc_src, gisrc_dst):
  117. """Get mapset from a GISRC source to a GISRC destination.
  118. :param gisrc_src: path to the GISRC source
  119. :type gisrc_src: str
  120. :param gisrc_dst: path to the GISRC destination
  121. :type gisrc_dst: str
  122. :returns: a tuple with Mapset(src), Mapset(dst)
  123. """
  124. msrc, lsrc, gsrc = read_gisrc(gisrc_src)
  125. mdst, ldst, gdst = read_gisrc(gisrc_dst)
  126. path_src = os.path.join(gsrc, lsrc, msrc)
  127. path_dst = os.path.join(gdst, ldst, mdst)
  128. if not os.path.isdir(path_dst):
  129. os.makedirs(path_dst)
  130. copy_special_mapset_files(path_src, path_dst)
  131. src = Mapset(msrc, lsrc, gsrc)
  132. dst = Mapset(mdst, ldst, gdst)
  133. visible = [m for m in src.visible]
  134. if src.name not in visible:
  135. visible.append(src.name)
  136. dst.visible.extend(visible)
  137. return src, dst
  138. def copy_groups(groups, gisrc_src, gisrc_dst, region=None):
  139. """Copy group from one mapset to another, crop the raster to the region
  140. :param groups: a list of strings with the group that must be copied
  141. from a master to another.
  142. :type groups: list of strings
  143. :param gisrc_src: path of the GISRC file from where we want to copy the groups
  144. :type gisrc_src: str
  145. :param gisrc_dst: path of the GISRC file where the groups will be created
  146. :type gisrc_dst: str
  147. :param region: a region like object or a dictionary with the region
  148. parameters that will be used to crop the rasters of the
  149. groups
  150. :type region: Region object or dictionary
  151. :returns: None
  152. """
  153. def rmloc(r):
  154. return r.split("@")[0] if "@" in r else r
  155. env = os.environ.copy()
  156. # instantiate modules
  157. get_grp = Module("i.group", flags="lg", stdout_=sub.PIPE, run_=False)
  158. set_grp = Module("i.group")
  159. get_grp.run_ = True
  160. src = read_gisrc(gisrc_src)
  161. dst = read_gisrc(gisrc_dst)
  162. rm = True if src[2] != dst[2] else False
  163. all_rasts = [r[0] for r in findmaps("raster", location=dst[1], gisdbase=dst[2])]
  164. for grp in groups:
  165. # change gisdbase to src
  166. env["GISRC"] = gisrc_src
  167. get_grp(group=grp, env_=env)
  168. rasts = [r for r in get_grp.outputs.stdout.split()]
  169. # change gisdbase to dst
  170. env["GISRC"] = gisrc_dst
  171. rast2cp = [r for r in rasts if rmloc(r) not in all_rasts]
  172. if rast2cp:
  173. copy_rasters(rast2cp, gisrc_src, gisrc_dst, region=region)
  174. set_grp(
  175. group=grp,
  176. input=[rmloc(r) for r in rasts] if rast2cp or rm else rasts,
  177. env_=env,
  178. )
  179. def set_region(region, gisrc_src, gisrc_dst, env):
  180. """Set a region into two different mapsets.
  181. :param region: a region like object or a dictionary with the region
  182. parameters that will be used to crop the rasters of the
  183. groups
  184. :type region: Region object or dictionary
  185. :param gisrc_src: path of the GISRC file from where we want to copy the groups
  186. :type gisrc_src: str
  187. :param gisrc_dst: path of the GISRC file where the groups will be created
  188. :type gisrc_dst: str
  189. :param env:
  190. :type env:
  191. :returns: None
  192. """
  193. reg_str = (
  194. "g.region n=%(north)r s=%(south)r "
  195. "e=%(east)r w=%(west)r "
  196. "nsres=%(nsres)r ewres=%(ewres)r"
  197. )
  198. reg_cmd = reg_str % dict(region.items())
  199. env["GISRC"] = gisrc_src
  200. sub.Popen(reg_cmd, shell=True, env=env)
  201. env["GISRC"] = gisrc_dst
  202. sub.Popen(reg_cmd, shell=True, env=env)
  203. def copy_rasters(rasters, gisrc_src, gisrc_dst, region=None):
  204. """Copy rasters from one mapset to another, crop the raster to the region.
  205. :param rasters: a list of strings with the raster map that must be copied
  206. from a master to another.
  207. :type rasters: list
  208. :param gisrc_src: path of the GISRC file from where we want to copy the groups
  209. :type gisrc_src: str
  210. :param gisrc_dst: path of the GISRC file where the groups will be created
  211. :type gisrc_dst: str
  212. :param region: a region like object or a dictionary with the region
  213. parameters that will be used to crop the rasters of the
  214. groups
  215. :type region: Region object or dictionary
  216. :returns: None
  217. """
  218. env = os.environ.copy()
  219. if region:
  220. set_region(region, gisrc_src, gisrc_dst, env)
  221. path_dst = os.path.join(*read_gisrc(gisrc_dst)[::-1])
  222. nam = "copy%d__%s" % (id(gisrc_dst), "%s")
  223. # instantiate modules
  224. mpclc = Module("r.mapcalc")
  225. rpck = Module("r.pack")
  226. rupck = Module("r.unpack")
  227. remove = Module("g.remove")
  228. for rast in rasters:
  229. rast_clean = rast.split("@")[0] if "@" in rast else rast
  230. # change gisdbase to src
  231. env["GISRC"] = gisrc_src
  232. name = nam % rast_clean
  233. mpclc(expression="%s=%s" % (name, rast), overwrite=True, env_=env)
  234. file_dst = "%s.pack" % os.path.join(path_dst, name)
  235. rpck(input=name, output=file_dst, overwrite=True, env_=env)
  236. remove(flags="f", type="raster", name=name, env_=env)
  237. # change gisdbase to dst
  238. env["GISRC"] = gisrc_dst
  239. rupck(input=file_dst, output=rast_clean, overwrite=True, env_=env)
  240. os.remove(file_dst)
  241. def copy_vectors(vectors, gisrc_src, gisrc_dst):
  242. """Copy vectors from one mapset to another, crop the raster to the region.
  243. :param vectors: a list of strings with the vector map that must be copied
  244. from a master to another.
  245. :type vectors: list
  246. :param gisrc_src: path of the GISRC file from where we want to copy the groups
  247. :type gisrc_src: str
  248. :param gisrc_dst: path of the GISRC file where the groups will be created
  249. :type gisrc_dst: str
  250. :returns: None
  251. """
  252. env = os.environ.copy()
  253. path_dst = os.path.join(*read_gisrc(gisrc_dst))
  254. nam = "copy%d__%s" % (id(gisrc_dst), "%s")
  255. # instantiate modules
  256. vpck = Module("v.pack")
  257. vupck = Module("v.unpack")
  258. remove = Module("g.remove")
  259. for vect in vectors:
  260. # change gisdbase to src
  261. env["GISRC"] = gisrc_src
  262. name = nam % vect
  263. file_dst = "%s.pack" % os.path.join(path_dst, name)
  264. vpck(input=name, output=file_dst, overwrite=True, env_=env)
  265. remove(flags="f", type="vector", name=name, env_=env)
  266. # change gisdbase to dst
  267. env["GISRC"] = gisrc_dst
  268. vupck(input=file_dst, output=vect, overwrite=True, env_=env)
  269. os.remove(file_dst)
  270. def get_cmd(cmdd):
  271. """Transform a cmd dictionary to a list of parameters. It is useful to
  272. pickle a Module class and cnvert into a string that can be used with
  273. `Popen(get_cmd(cmdd), shell=True)`.
  274. :param cmdd: a module dictionary with all the parameters
  275. :type cmdd: dict
  276. >>> slp = Module('r.slope.aspect',
  277. ... elevation='ele', slope='slp', aspect='asp',
  278. ... overwrite=True, run_=False)
  279. >>> get_cmd(slp.get_dict()) # doctest: +ELLIPSIS
  280. ['r.slope.aspect', 'elevation=ele', 'format=degrees', ..., '--o']
  281. """
  282. cmd = [
  283. cmdd["name"],
  284. ]
  285. cmd.extend(("%s=%s" % (k, v) for k, v in cmdd["inputs"] if not isinstance(v, list)))
  286. cmd.extend(
  287. (
  288. "%s=%s"
  289. % (
  290. k,
  291. ",".join(vals if isinstance(vals[0], str) else [repr(v) for v in vals]),
  292. )
  293. for k, vals in cmdd["inputs"]
  294. if isinstance(vals, list)
  295. )
  296. )
  297. cmd.extend(
  298. ("%s=%s" % (k, v) for k, v in cmdd["outputs"] if not isinstance(v, list))
  299. )
  300. cmd.extend(
  301. (
  302. "%s=%s" % (k, ",".join([repr(v) for v in vals]))
  303. for k, vals in cmdd["outputs"]
  304. if isinstance(vals, list)
  305. )
  306. )
  307. cmd.extend(("-%s" % (flg) for flg in cmdd["flags"] if len(flg) == 1))
  308. cmd.extend(("--%s" % (flg[0]) for flg in cmdd["flags"] if len(flg) > 1))
  309. return cmd
  310. def cmd_exe(args):
  311. """Create a mapset, and execute a cmd inside.
  312. :param args: is a tuple that contains several information see below
  313. :type args: tuple
  314. :returns: None
  315. The puple has to contain:
  316. - bbox (dict): a dict with the region parameters (n, s, e, w, etc.)
  317. that we want to set before to apply the command.
  318. - mapnames (dict): a dictionary to substitute the input if the domain has
  319. been split in several tiles.
  320. - gisrc_src (str): path of the GISRC file from where we want to copy the
  321. groups.
  322. - gisrc_dst (str): path of the GISRC file where the groups will be created.
  323. - cmd (dict): a dictionary with all the parameter of a GRASS module.
  324. - groups (list): a list of strings with the groups that we want to copy in
  325. the mapset.
  326. """
  327. bbox, mapnames, gisrc_src, gisrc_dst, cmd, groups = args
  328. src, dst = get_mapset(gisrc_src, gisrc_dst)
  329. env = os.environ.copy()
  330. env["GISRC"] = gisrc_dst
  331. shell = True if sys.platform == "win32" else False
  332. if mapnames:
  333. inputs = dict(cmd["inputs"])
  334. # reset the inputs to
  335. for key in mapnames:
  336. inputs[key] = mapnames[key]
  337. cmd["inputs"] = inputs.items()
  338. # set the region to the tile
  339. sub.Popen(["g.region", "raster=%s" % key], shell=shell, env=env).wait()
  340. else:
  341. # set the computational region
  342. lcmd = [
  343. "g.region",
  344. ]
  345. lcmd.extend(["%s=%s" % (k, v) for k, v in bbox.items()])
  346. sub.Popen(lcmd, shell=shell, env=env).wait()
  347. if groups:
  348. copy_groups(groups, gisrc_src, gisrc_dst)
  349. # run the grass command
  350. sub.Popen(get_cmd(cmd), shell=shell, env=env).wait()
  351. # remove temp GISRC
  352. os.remove(gisrc_dst)
  353. class GridModule(object):
  354. # TODO maybe also i.* could be supported easily
  355. """Run GRASS raster commands in a multiprocessing mode.
  356. :param cmd: raster GRASS command, only command staring with r.* are valid.
  357. :type cmd: str
  358. :param width: width of the tile, in pixel
  359. :type width: int
  360. :param height: height of the tile, in pixel.
  361. :type height: int
  362. :param overlap: overlap between tiles, in pixel.
  363. :type overlap: int
  364. :param processes: number of threads, default value is equal to the number
  365. of processor available.
  366. :param split: if True use r.tile to split all the inputs.
  367. :type split: bool
  368. :param mapset_prefix: if specified created mapsets start with this prefix
  369. :type mapset_prefix: str
  370. :param patch_backend: "r.patch", "RasterRow", or None for for default
  371. :type patch_backend: None or str
  372. :param run_: if False only instantiate the object
  373. :type run_: bool
  374. :param args: give all the parameters to the command
  375. :param kargs: give all the parameters to the command
  376. When patch_backend is None, the RasterRow method is used for patching the result.
  377. When patch_backend is "r.patch", r.patch is used with nprocs=processes.
  378. r.patch can only be used when overlap is 0.
  379. >>> grd = GridModule('r.slope.aspect',
  380. ... width=500, height=500, overlap=2,
  381. ... processes=None, split=False,
  382. ... elevation='elevation',
  383. ... slope='slope', aspect='aspect', overwrite=True)
  384. >>> grd.run()
  385. Temporary mapsets created start with a generated prefix which is unique for each
  386. process (includes PID and node name). If more instances of this class are used in
  387. parallel from one process with the same module, a custom *mapset_prefix* needs to
  388. be provided.
  389. """
  390. def __init__(
  391. self,
  392. cmd,
  393. width=None,
  394. height=None,
  395. overlap=0,
  396. processes=None,
  397. split=False,
  398. debug=False,
  399. region=None,
  400. move=None,
  401. log=False,
  402. start_row=0,
  403. start_col=0,
  404. out_prefix="",
  405. mapset_prefix=None,
  406. patch_backend=None,
  407. *args,
  408. **kargs,
  409. ):
  410. kargs["run_"] = False
  411. self.mset = Mapset()
  412. self.module = Module(cmd, *args, **kargs)
  413. self.width = width
  414. self.height = height
  415. self.overlap = overlap
  416. self.processes = processes
  417. self.region = region if region else Region()
  418. self.start_row = start_row
  419. self.start_col = start_col
  420. self.out_prefix = out_prefix
  421. self.log = log
  422. self.move = move
  423. # by default RasterRow is used as previously
  424. # if overlap > 0, r.patch won't work properly
  425. if not patch_backend:
  426. self.patch_backend = "RasterRow"
  427. elif patch_backend not in ("r.patch", "RasterRow"):
  428. raise RuntimeError(
  429. _("Parameter patch_backend must be 'r.patch' or 'RasterRow'")
  430. )
  431. elif patch_backend == "r.patch" and self.overlap:
  432. raise RuntimeError(
  433. _("Patching backend 'r.patch' doesn't work for overlap > 0")
  434. )
  435. else:
  436. self.patch_backend = patch_backend
  437. self.gisrc_src = os.environ["GISRC"]
  438. self.n_mset, self.gisrc_dst = None, None
  439. self.estimate_tile_size()
  440. if self.move:
  441. self.n_mset = copy_mapset(self.mset, self.move)
  442. self.gisrc_dst = write_gisrc(
  443. self.n_mset.gisdbase, self.n_mset.location, self.n_mset.name
  444. )
  445. rasters = [r for r in select(self.module.inputs, "raster")]
  446. if rasters:
  447. copy_rasters(
  448. rasters, self.gisrc_src, self.gisrc_dst, region=self.region
  449. )
  450. vectors = [v for v in select(self.module.inputs, "vector")]
  451. if vectors:
  452. copy_vectors(vectors, self.gisrc_src, self.gisrc_dst)
  453. groups = [g for g in select(self.module.inputs, "group")]
  454. if groups:
  455. copy_groups(groups, self.gisrc_src, self.gisrc_dst, region=self.region)
  456. self.bboxes = split_region_tiles(
  457. region=region, width=self.width, height=self.height, overlap=overlap
  458. )
  459. if mapset_prefix:
  460. self.mapset_prefix = mapset_prefix
  461. else:
  462. self.mapset_prefix = append_node_pid("grid_" + legalize_vector_name(cmd))
  463. self.msetstr = self.mapset_prefix + "_%03d_%03d"
  464. self.inlist = None
  465. if split:
  466. self.split()
  467. self.debug = debug
  468. def __del__(self):
  469. if self.gisrc_dst:
  470. # remove GISRC file
  471. os.remove(self.gisrc_dst)
  472. def clean_location(self, location=None):
  473. """Remove all created mapsets.
  474. :param location: a Location instance where we are running the analysis
  475. :type location: Location object
  476. """
  477. if location is None:
  478. if self.n_mset:
  479. self.n_mset.current()
  480. location = Location()
  481. mapsets = location.mapsets(self.mapset_prefix + "_*")
  482. for mset in mapsets:
  483. Mapset(mset).delete()
  484. if self.n_mset and self.n_mset.is_current():
  485. self.mset.current()
  486. def split(self):
  487. """Split all the raster inputs using r.tile"""
  488. rtile = Module("r.tile")
  489. inlist = {}
  490. for inm in select(self.module.inputs, "raster"):
  491. rtile(
  492. input=inm.value,
  493. output=inm.value,
  494. width=self.width,
  495. height=self.height,
  496. overlap=self.overlap,
  497. )
  498. patt = "%s-*" % inm.value
  499. inlist[inm.value] = sorted(self.mset.glist(type="raster", pattern=patt))
  500. self.inlist = inlist
  501. def estimate_tile_size(self):
  502. """Estimates tile width and height based on number of processes.
  503. Keeps width and height if provided by user. If one dimension
  504. is provided the other is computed as the minimum number of tiles
  505. to keep all requested processes working (initially).
  506. If no dimensions are provided, tiling is 1 column x N rows
  507. which speeds up patching.
  508. """
  509. region = Region()
  510. if self.width and self.height:
  511. return
  512. if self.width:
  513. n_tiles_x = ceil(region.cols / self.width)
  514. n_tiles_y = ceil(self.processes / n_tiles_x)
  515. self.height = ceil(region.rows / n_tiles_y)
  516. elif self.height:
  517. n_tiles_y = ceil(region.rows / self.height)
  518. n_tiles_x = ceil(self.processes / n_tiles_y)
  519. self.width = ceil(region.cols / n_tiles_x)
  520. else:
  521. self.width = region.cols
  522. self.height = ceil(region.rows / self.processes)
  523. def get_works(self):
  524. """Return a list of tuble with the parameters for cmd_exe function"""
  525. works = []
  526. reg = Region()
  527. if self.move:
  528. mdst, ldst, gdst = read_gisrc(self.gisrc_dst)
  529. else:
  530. ldst, gdst = self.mset.location, self.mset.gisdbase
  531. cmd = self.module.get_dict()
  532. groups = [g for g in select(self.module.inputs, "group")]
  533. for row, box_row in enumerate(self.bboxes):
  534. for col, box in enumerate(box_row):
  535. inms = None
  536. if self.inlist:
  537. inms = {}
  538. cols = len(box_row)
  539. for key in self.inlist:
  540. indx = row * cols + col
  541. inms[key] = "%s@%s" % (self.inlist[key][indx], self.mset.name)
  542. # set the computational region, prepare the region parameters
  543. bbox = dict([(k[0], str(v)) for k, v in box.items()[:-2]])
  544. bbox["nsres"] = "%f" % reg.nsres
  545. bbox["ewres"] = "%f" % reg.ewres
  546. new_mset = (
  547. self.msetstr % (self.start_row + row, self.start_col + col),
  548. )
  549. works.append(
  550. (
  551. bbox,
  552. inms,
  553. self.gisrc_src,
  554. write_gisrc(gdst, ldst, new_mset),
  555. cmd,
  556. groups,
  557. )
  558. )
  559. return works
  560. def define_mapset_inputs(self):
  561. """Add the mapset information to the input maps"""
  562. for inmap in self.module.inputs:
  563. inm = self.module.inputs[inmap]
  564. if inm.type in ("raster", "vector") and inm.value:
  565. if "@" not in inm.value:
  566. mset = get_mapset_raster(inm.value)
  567. inm.value = inm.value + "@%s" % mset
  568. def run(self, patch=True, clean=True):
  569. """Run the GRASS command
  570. :param patch: set False if you does not want to patch the results
  571. :type patch: bool
  572. :param clean: set False if you does not want to remove all the stuff
  573. created by GridModule
  574. :type clean: bool
  575. """
  576. self.module.flags.overwrite = True
  577. self.define_mapset_inputs()
  578. if self.debug:
  579. for wrk in self.get_works():
  580. cmd_exe(wrk)
  581. else:
  582. pool = mltp.Pool(processes=self.processes)
  583. result = pool.map_async(cmd_exe, self.get_works())
  584. result.wait()
  585. pool.close()
  586. pool.join()
  587. if not result.successful():
  588. raise RuntimeError(_("Execution of subprocesses was not successful"))
  589. if patch:
  590. if self.move:
  591. os.environ["GISRC"] = self.gisrc_dst
  592. self.n_mset.current()
  593. self.patch()
  594. os.environ["GISRC"] = self.gisrc_src
  595. self.mset.current()
  596. # copy the outputs from dst => src
  597. routputs = [
  598. self.out_prefix + o for o in select(self.module.outputs, "raster")
  599. ]
  600. copy_rasters(routputs, self.gisrc_dst, self.gisrc_src)
  601. else:
  602. self.patch()
  603. if self.log:
  604. # record in the temp directory
  605. from grass.lib.gis import G_tempfile
  606. tmp, dummy = os.path.split(G_tempfile())
  607. tmpdir = os.path.join(tmp, self.module.name)
  608. for k in self.module.outputs:
  609. par = self.module.outputs[k]
  610. if par.typedesc == "raster" and par.value:
  611. dirpath = os.path.join(tmpdir, par.name)
  612. if not os.path.isdir(dirpath):
  613. os.makedirs(dirpath)
  614. fil = open(os.path.join(dirpath, self.out_prefix + par.value), "w+")
  615. fil.close()
  616. if clean:
  617. self.clean_location()
  618. self.rm_tiles()
  619. if self.n_mset:
  620. gisdbase, location = os.path.split(self.move)
  621. self.clean_location(Location(location, gisdbase))
  622. # rm temporary gis_rc
  623. os.remove(self.gisrc_dst)
  624. self.gisrc_dst = None
  625. sht.rmtree(os.path.join(self.move, "PERMANENT"))
  626. sht.rmtree(os.path.join(self.move, self.mset.name))
  627. def patch(self):
  628. """Patch the final results."""
  629. bboxes = split_region_tiles(width=self.width, height=self.height)
  630. loc = Location()
  631. mset = loc[self.mset.name]
  632. mset.visible.extend(loc.mapsets())
  633. noutputs = 0
  634. for otmap in self.module.outputs:
  635. otm = self.module.outputs[otmap]
  636. if otm.typedesc == "raster" and otm.value:
  637. if self.patch_backend == "RasterRow":
  638. rpatch_map(
  639. raster=otm.value,
  640. mapset=self.mset.name,
  641. mset_str=self.msetstr,
  642. bbox_list=bboxes,
  643. overwrite=self.module.flags.overwrite,
  644. start_row=self.start_row,
  645. start_col=self.start_col,
  646. prefix=self.out_prefix,
  647. )
  648. else:
  649. rpatch_map_r_patch_backend(
  650. raster=otm.value,
  651. mset_str=self.msetstr,
  652. bbox_list=bboxes,
  653. overwrite=self.module.flags.overwrite,
  654. start_row=self.start_row,
  655. start_col=self.start_col,
  656. prefix=self.out_prefix,
  657. processes=self.processes,
  658. )
  659. noutputs += 1
  660. if noutputs < 1:
  661. msg = "No raster output option defined for <{}>".format(self.module.name)
  662. if self.module.name == "r.mapcalc":
  663. msg += ". Use <{}.simple> instead".format(self.module.name)
  664. raise RuntimeError(msg)
  665. def rm_tiles(self):
  666. """Remove all the tiles."""
  667. # if split, remove tiles
  668. if self.inlist:
  669. grm = Module("g.remove")
  670. for key in self.inlist:
  671. grm(flags="f", type="raster", name=self.inlist[key])