r3.in.xyz.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. #% keywords: raster3d, import, voxel, LiDAR
  25. #%End
  26. #%Flag
  27. #% key: s
  28. #% description: Scan data file for extent then exit
  29. #%End
  30. #%Flag
  31. #% key: g
  32. #% description: In scan mode, print using shell script style
  33. #%End
  34. #%Flag
  35. #% key: i
  36. #% description: Ignore broken lines
  37. #%End
  38. #%Option
  39. #% key: input
  40. #% type: string
  41. #% required: yes
  42. #% multiple: no
  43. #% key_desc: name
  44. #% description: ASCII file containing input data
  45. #% gisprompt: old_file,file,input
  46. #%End
  47. #%Option
  48. #% key: output
  49. #% type: string
  50. #% required: yes
  51. #% multiple: no
  52. #% key_desc: name
  53. #% description: Name for output raster map
  54. #% gisprompt: new,grid3,3d-raster
  55. #%End
  56. #%Option
  57. #% key: method
  58. #% type: string
  59. #% required: no
  60. #% multiple: no
  61. #% options: n,min,max,range,sum,mean,stddev,variance,coeff_var,median,percentile,skewness,trimmean
  62. #% description: Statistic to use for raster values
  63. #% answer: mean
  64. #% guisection: Statistic
  65. #%End
  66. #%Option
  67. #% key: type
  68. #% type: string
  69. #% required: no
  70. #% multiple: no
  71. #% options: float,double
  72. #% description: Storage type for resultant raster map
  73. #% answer: float
  74. #%End
  75. #%Option
  76. #% key: fs
  77. #% type: string
  78. #% required: no
  79. #% multiple: no
  80. #% key_desc: character
  81. #% description: Field separator
  82. #% answer: |
  83. #% guisection: Input
  84. #%End
  85. #%Option
  86. #% key: x
  87. #% type: integer
  88. #% required: no
  89. #% multiple: no
  90. #% description: Column number of x coordinates in input file (first column is 1)
  91. #% answer: 1
  92. #% guisection: Input
  93. #%End
  94. #%Option
  95. #% key: y
  96. #% type: integer
  97. #% required: no
  98. #% multiple: no
  99. #% description: Column number of y coordinates in input file
  100. #% answer: 2
  101. #% guisection: Input
  102. #%End
  103. #%Option
  104. #% key: z
  105. #% type: integer
  106. #% required: no
  107. #% multiple: no
  108. #% description: Column number of data values in input file
  109. #% answer: 3
  110. #% guisection: Input
  111. #%End
  112. #%Option
  113. #% key: percent
  114. #% type: integer
  115. #% required: no
  116. #% multiple: no
  117. #% options: 1-100
  118. #% description: Percent of map to keep in memory
  119. #% answer: 100
  120. #%End
  121. #%Option
  122. #% key: pth
  123. #% type: integer
  124. #% required: no
  125. #% multiple: no
  126. #% options: 1-100
  127. #% description: pth percentile of the values
  128. #% guisection: Statistic
  129. #%End
  130. #%Option
  131. #% key: trim
  132. #% type: double
  133. #% required: no
  134. #% multiple: no
  135. #% options: 0-50
  136. #% description: Discard <trim> percent of the smallest and <trim> percent of the largest observations
  137. #% guisection: Statistic
  138. #%End
  139. #%Option
  140. #% key: workers
  141. #% type: integer
  142. #% required: no
  143. #% multiple: no
  144. #% options: 1-256
  145. #% answer: 1
  146. #% description: Number of parallel processes to launch
  147. #%End
  148. import sys
  149. import os
  150. from grass.script import core as grass
  151. def cleanup():
  152. grass.run_command('g.mremove', flags = 'f', rast = 'tmp.r3xyz.%d.*' % os.getpid(),
  153. quiet = True)
  154. def main():
  155. infile = options['input']
  156. output = options['output']
  157. method = options['method']
  158. dtype = options['type']
  159. fs = options['fs']
  160. x = options['x']
  161. y = options['y']
  162. z = options['z']
  163. percent = options['percent']
  164. pth = options['pth']
  165. trim = options['trim']
  166. workers = options['workers']
  167. scan_only = flags['s']
  168. shell_style = flags['g']
  169. ignore_broken = flags['i']
  170. if workers is 1 and "WORKERS" in os.environ:
  171. workers = int(os.environ["WORKERS"])
  172. if not os.path.exists(infile):
  173. grass.fatal(_("Unable to read input file <%s>") % infile)
  174. if scan_only or shell_style:
  175. if shell_style:
  176. doShell = 'g'
  177. else:
  178. doShell = ''
  179. grass.run_command('r.in.xyz', flags = 's' + doShell, input = infile,
  180. output = 'dummy', fs = fs, x = x, y = y, z = z)
  181. sys.exit()
  182. addl_opts = []
  183. if pth:
  184. addl_opts.append("pth = '%s'" % pth)
  185. if trim:
  186. addl_opts.append("trim = '%s'" % trim)
  187. if ignore_broken:
  188. addl_opts.append("flags = 'i'")
  189. if dtype is 'float':
  190. data_type = 'FCELL'
  191. else:
  192. data_type = 'DCELL'
  193. region = grass.region(region3d = True)
  194. if region['nsres'] != region['nsres3'] or region['ewres'] != region['ewres3']:
  195. grass.run_command('g.region', flags = '3p')
  196. grass.fatal(_("The 2D and 3D region settings are different. Can not continue."))
  197. grass.verbose(_("Region bottom=%.15g top=%.15g vertical_cell_res=%.15g (depths %d)")
  198. % (region['b'], region['t'], region['tbres'], region['depths']))
  199. grass.verbose(_("Creating slices ..."))
  200. # to avoid a point which falls exactly on a top bound from being
  201. # considered twice, we shrink the
  202. # For the top slice we keep it though, as someone scanning the bounds
  203. # may have set the bounds exactly to the data extent (a bad idea, but
  204. # it happens..)
  205. eps = 1.0e-15
  206. # if there are thousands of depths hopefully this array doesn't get too large
  207. # and so we don't have to worry much about storing/looping through the finished process infos.
  208. proc = {}
  209. for i in range(1, 1 + depths):
  210. tmp_layer_name = 'tmp.r3xyz.%d.%s' % (os.getpid(), '%05d' % i)
  211. zrange_min = region['b'] + (region['tbres'] * (i-1))
  212. if i < region['depths']:
  213. zrange_max = region['b'] + (region['tbres'] * i) - eps
  214. else:
  215. zrange_max = region['b'] + (region['tbres'] * i)
  216. # spawn depth layerimport job in the background
  217. #grass.debug("slice %d, <%s> %% %d" % (band, image[band], band % workers))
  218. grass.message(_("Processing horizontal slice %d of %d [%.15g,%.15g) ..."),
  219. % (i, depths, zrange_min, zrange_max))
  220. proc[i] = grass.start_command('r.in.xyz', input = infile, output = tmp_layer_name,
  221. fs = fs, method = method, x = x, y = y, z = z,
  222. percent = percent, type = data_type,
  223. zrange = '%.15g,%.15g' % (zrange_min, zrange_max),
  224. addl_opts)
  225. if i % workers is 0:
  226. # wait for the ones launched so far to finish
  227. for p_i in range(1, 1 + depths)[:i]:
  228. if not proc[p_i].stdout.closed:
  229. pout[p_i] = proc[p_i].communicate()[0]
  230. if proc[p_i].wait() is not 0:
  231. grass.fatal(_("Trouble importing data. Aborting."))
  232. # wait for jobs to finish, collect any stray output
  233. for p_i in range(1, 1 + depths):
  234. if not proc[p_i].stdout.closed:
  235. pout[p_i] = proc[p_i].communicate()[0]
  236. if proc[p_i].wait() is not 0:
  237. grass.fatal(_("Trouble importing data. Aborting."))
  238. del proc
  239. grass.verbose(_("Assembling 3D cube ..."))
  240. #input order: lower most strata first
  241. slices = grass.read_command('g.mlist', type = 'rast', sep = ',',
  242. pattern = 'tmp.r3xyz.%d.*' % os.getpid())
  243. if grass.run_command('r.to.rast3', input = slices, output = output) is 0:
  244. grass.message(_("Done. 3D raster map <%s> created.") % output)
  245. if __name__ == "__main__":
  246. options, flags = grass.parser()
  247. atexit.register(cleanup)
  248. main()