r3.in.xyz.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. #!/usr/bin/env python
  2. ############################################################################
  3. #
  4. # MODULE: r3.in.xyz
  5. # AUTHOR: M. Hamish Bowman, Dunedin, New Zealand
  6. # PURPOSE: Run r.in.xyz in a loop for various z-levels and construct
  7. # a 3D raster. Unlike r.in.xyz, reading from stdin and z-scaling
  8. # won't work.
  9. #
  10. # COPYRIGHT: (c) 2011-2012 Hamish Bowman, and the GRASS Development Team
  11. # Port of r3.in.xyz(.sh) for GRASS 6.4.
  12. # This program is free software under the GNU General Public
  13. # License (>=v2). Read the file COPYING that comes with GRASS
  14. # for details.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. #############################################################################
  22. #%Module
  23. #% description: Create a 3D raster map from an assemblage of many coordinates using univariate statistics
  24. #% keyword: raster3d
  25. #% keyword: import
  26. #% keyword: voxel
  27. #% keyword: LIDAR
  28. #% keyword: statistics
  29. #% keyword: conversion
  30. #% keyword: aggregation
  31. #% keyword: binning
  32. #%End
  33. #%Flag
  34. #% key: s
  35. #% description: Scan data file for extent then exit
  36. #%End
  37. #%Flag
  38. #% key: g
  39. #% description: In scan mode, print using shell script style
  40. #%End
  41. #%Flag
  42. #% key: i
  43. #% description: Ignore broken lines
  44. #%End
  45. #%Option G_OPT_F_INPUT
  46. #% required: yes
  47. #% description: ASCII file containing input data
  48. #%End
  49. #%Option
  50. #% key: output
  51. #% type: string
  52. #% required: yes
  53. #% multiple: no
  54. #% key_desc: name
  55. #% description: Name for output raster map
  56. #% gisprompt: new,grid3,3d-raster
  57. #%End
  58. #%Option
  59. #% key: method
  60. #% type: string
  61. #% required: no
  62. #% multiple: no
  63. #% options: n,min,max,range,sum,mean,stddev,variance,coeff_var,median,percentile,skewness,trimmean
  64. #% description: Statistic to use for raster values
  65. #% answer: mean
  66. #% guisection: Statistic
  67. #%End
  68. #%Option
  69. #% key: type
  70. #% type: string
  71. #% required: no
  72. #% multiple: no
  73. #% options: float,double
  74. #% description: Storage type for resultant raster map
  75. #% answer: float
  76. #%End
  77. #%Option G_OPT_F_SEP
  78. #% guisection: Input
  79. #%End
  80. #%Option
  81. #% key: x
  82. #% type: integer
  83. #% required: no
  84. #% multiple: no
  85. #% description: Column number of x coordinates in input file (first column is 1)
  86. #% answer: 1
  87. #% guisection: Input
  88. #%End
  89. #%Option
  90. #% key: y
  91. #% type: integer
  92. #% required: no
  93. #% multiple: no
  94. #% description: Column number of y coordinates in input file
  95. #% answer: 2
  96. #% guisection: Input
  97. #%End
  98. #%Option
  99. #% key: z
  100. #% type: integer
  101. #% required: no
  102. #% multiple: no
  103. #% description: Column number of z coordinates in input file
  104. #% answer: 3
  105. #% guisection: Input
  106. #%End
  107. #%Option
  108. #% key: value_column
  109. #% type: integer
  110. #% required: no
  111. #% multiple: no
  112. #% label: Column number of data values in input file
  113. #% description: If not given or set to 0, the data points' z-values are used
  114. #% answer: 0
  115. #% guisection: Input
  116. #%End
  117. #%Option
  118. #% key: vrange
  119. #% type: double
  120. #% required: no
  121. #% key_desc: min,max
  122. #% description: Filter range for value column data (min,max)
  123. #%End
  124. #%option
  125. #% key: vscale
  126. #% type: double
  127. #% required: no
  128. #% multiple: no
  129. #% description: Scaling factor to apply to value column data
  130. #% answer: 1.0
  131. #%end
  132. #%Option
  133. #% key: percent
  134. #% type: integer
  135. #% required: no
  136. #% multiple: no
  137. #% options: 1-100
  138. #% description: Percent of map to keep in memory
  139. #% answer: 100
  140. #%End
  141. #%Option
  142. #% key: pth
  143. #% type: integer
  144. #% required: no
  145. #% multiple: no
  146. #% options: 1-100
  147. #% description: pth percentile of the values
  148. #% guisection: Statistic
  149. #%End
  150. #%Option
  151. #% key: trim
  152. #% type: double
  153. #% required: no
  154. #% multiple: no
  155. #% options: 0-50
  156. #% description: Discard <trim> percent of the smallest and <trim> percent of the largest observations
  157. #% guisection: Statistic
  158. #%End
  159. #%Option
  160. #% key: workers
  161. #% type: integer
  162. #% required: no
  163. #% multiple: no
  164. #% options: 1-256
  165. #% answer: 1
  166. #% description: Number of parallel processes to launch
  167. #%End
  168. import sys
  169. import os
  170. import atexit
  171. from grass.script import core as grass
  172. from grass.exceptions import CalledModuleError
  173. # i18N
  174. import gettext
  175. gettext.install('grassmods', os.path.join(os.getenv("GISBASE"), 'locale'))
  176. def cleanup():
  177. grass.run_command('g.remove', flags='f',
  178. type="rast", pattern='tmp.r3xyz.%d.*' % os.getpid(),
  179. quiet=True)
  180. def main():
  181. infile = options['input']
  182. output = options['output']
  183. method = options['method']
  184. dtype = options['type']
  185. fs = options['separator']
  186. x = options['x']
  187. y = options['y']
  188. z = options['z']
  189. value_column = options['value_column']
  190. vrange = options['vrange']
  191. vscale = options['vscale']
  192. percent = options['percent']
  193. pth = options['pth']
  194. trim = options['trim']
  195. workers = int(options['workers'])
  196. scan_only = flags['s']
  197. shell_style = flags['g']
  198. ignore_broken = flags['i']
  199. if workers is 1 and "WORKERS" in os.environ:
  200. workers = int(os.environ["WORKERS"])
  201. if not os.path.exists(infile):
  202. grass.fatal(_("Unable to read input file <%s>") % infile)
  203. addl_opts = {}
  204. if pth:
  205. addl_opts['pth'] = '%s' % pth
  206. if trim:
  207. addl_opts['trim'] = '%s' % trim
  208. if value_column:
  209. addl_opts['value_column'] = '%s' % value_column
  210. if vrange:
  211. addl_opts['vrange'] = '%s' % vrange
  212. if vscale:
  213. addl_opts['vscale'] = '%s' % vscale
  214. if ignore_broken:
  215. addl_opts['flags'] = 'i'
  216. if scan_only or shell_style:
  217. if shell_style:
  218. doShell = 'g'
  219. else:
  220. doShell = ''
  221. grass.run_command('r.in.xyz', flags='s' + doShell, input=infile,
  222. output='dummy', sep=fs, x=x, y=y, z=z,
  223. **addl_opts)
  224. sys.exit()
  225. if dtype == 'float':
  226. data_type = 'FCELL'
  227. else:
  228. data_type = 'DCELL'
  229. region = grass.region(region3d=True)
  230. if region['nsres'] != region['nsres3'] or region['ewres'] != region['ewres3']:
  231. grass.run_command('g.region', flags='3p')
  232. grass.fatal(_("The 2D and 3D region settings are different. Can not continue."))
  233. grass.verbose(_("Region bottom=%.15g top=%.15g vertical_cell_res=%.15g (%d depths)")
  234. % (region['b'], region['t'], region['tbres'], region['depths']))
  235. grass.verbose(_("Creating slices ..."))
  236. # to avoid a point which falls exactly on a top bound from being
  237. # considered twice, we shrink the
  238. # For the top slice we keep it though, as someone scanning the bounds
  239. # may have set the bounds exactly to the data extent (a bad idea, but
  240. # it happens..)
  241. eps = 1.0e-15
  242. # if there are thousands of depths hopefully this array doesn't get too
  243. # large and so we don't have to worry much about storing/looping through
  244. # all the finished process infos.
  245. proc = {}
  246. pout = {}
  247. depths = list(range(1, 1 + region['depths']))
  248. for i in depths:
  249. tmp_layer_name = 'tmp.r3xyz.%d.%s' % (os.getpid(), '%05d' % i)
  250. zrange_min = region['b'] + (region['tbres'] * (i - 1))
  251. if i < region['depths']:
  252. zrange_max = region['b'] + (region['tbres'] * i) - eps
  253. else:
  254. zrange_max = region['b'] + (region['tbres'] * i)
  255. # spawn depth layer import job in the background
  256. #grass.debug("slice %d, <%s> %% %d" % (band, image[band], band % workers))
  257. grass.message(_("Processing horizontal slice %d of %d [%.15g,%.15g) ...")
  258. % (i, region['depths'], zrange_min, zrange_max))
  259. proc[i] = grass.start_command('r.in.xyz', input=infile, output=tmp_layer_name,
  260. sep=fs, method=method, x=x, y=y, z=z,
  261. percent=percent, type=data_type,
  262. zrange='%.15g,%.15g' % (zrange_min, zrange_max),
  263. **addl_opts)
  264. grass.debug("i=%d, %%=%d (workers=%d)" % (i, i % workers, workers))
  265. # print sys.getsizeof(proc) # sizeof(proc array) [not so big]
  266. if i % workers is 0:
  267. # wait for the ones launched so far to finish
  268. for p_i in depths[:i]:
  269. pout[p_i] = proc[p_i].communicate()[0]
  270. if proc[p_i].wait() is not 0:
  271. grass.fatal(_("Trouble importing data. Aborting."))
  272. # wait for jSobs to finish, collect any stray output
  273. for i in depths:
  274. pout[i] = proc[i].communicate()[0]
  275. if proc[i].wait() is not 0:
  276. grass.fatal(_("Trouble importing data. Aborting."))
  277. del proc
  278. grass.verbose(_("Assembling 3D cube ..."))
  279. # input order: lower most strata first
  280. slices = grass.read_command('g.list', type='raster', sep=',',
  281. pattern='tmp.r3xyz.%d.*' % os.getpid()).rstrip(os.linesep)
  282. grass.debug(slices)
  283. try:
  284. grass.run_command('r.to.rast3', input=slices, output=output)
  285. except CalledModuleError:
  286. grass.message(_("Done. 3D raster map <%s> created.") % output)
  287. if __name__ == "__main__":
  288. options, flags = grass.parser()
  289. atexit.register(cleanup)
  290. main()