123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- #!/usr/bin/env python3
- ############################################################################
- #
- # MODULE: r3.in.xyz
- # AUTHOR: M. Hamish Bowman, Dunedin, New Zealand
- # PURPOSE: Run r.in.xyz in a loop for various z-levels and construct
- # a 3D raster. Unlike r.in.xyz, reading from stdin and z-scaling
- # won't work.
- #
- # COPYRIGHT: (c) 2011-2012 Hamish Bowman, and the GRASS Development Team
- # Port of r3.in.xyz(.sh) for GRASS 6.4.
- # This program is free software under the GNU General Public
- # License (>=v2). Read the file COPYING that comes with GRASS
- # for details.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- #############################################################################
- # %Module
- # % description: Create a 3D raster map from an assemblage of many coordinates using univariate statistics
- # % keyword: raster3d
- # % keyword: import
- # % keyword: voxel
- # % keyword: LIDAR
- # % keyword: statistics
- # % keyword: conversion
- # % keyword: aggregation
- # % keyword: binning
- # %End
- # %Flag
- # % key: s
- # % description: Scan data file for extent then exit
- # %End
- # %Flag
- # % key: g
- # % description: In scan mode, print using shell script style
- # %End
- # %Flag
- # % key: i
- # % description: Ignore broken lines
- # %End
- # %Option G_OPT_F_INPUT
- # % required: yes
- # % description: ASCII file containing input data
- # %End
- # %Option
- # % key: output
- # % type: string
- # % required: yes
- # % multiple: no
- # % key_desc: name
- # % description: Name for output raster map
- # % gisprompt: new,grid3,3d-raster
- # %End
- # %Option
- # % key: method
- # % type: string
- # % required: no
- # % multiple: no
- # % options: n,min,max,range,sum,mean,stddev,variance,coeff_var,median,percentile,skewness,trimmean
- # % description: Statistic to use for raster values
- # % answer: mean
- # % guisection: Statistic
- # %End
- # %Option
- # % key: type
- # % type: string
- # % required: no
- # % multiple: no
- # % options: float,double
- # % description: Storage type for resultant raster map
- # % answer: float
- # %End
- # %Option G_OPT_F_SEP
- # % guisection: Input
- # %End
- # %Option
- # % key: x
- # % type: integer
- # % required: no
- # % multiple: no
- # % description: Column number of x coordinates in input file (first column is 1)
- # % answer: 1
- # % guisection: Input
- # %End
- # %Option
- # % key: y
- # % type: integer
- # % required: no
- # % multiple: no
- # % description: Column number of y coordinates in input file
- # % answer: 2
- # % guisection: Input
- # %End
- # %Option
- # % key: z
- # % type: integer
- # % required: no
- # % multiple: no
- # % description: Column number of z coordinates in input file
- # % answer: 3
- # % guisection: Input
- # %End
- # %Option
- # % key: value_column
- # % type: integer
- # % required: no
- # % multiple: no
- # % label: Column number of data values in input file
- # % description: If not given or set to 0, the data points' z-values are used
- # % answer: 0
- # % guisection: Input
- # %End
- # %Option
- # % key: vrange
- # % type: double
- # % required: no
- # % key_desc: min,max
- # % description: Filter range for value column data (min,max)
- # %End
- # %option
- # % key: vscale
- # % type: double
- # % required: no
- # % multiple: no
- # % description: Scaling factor to apply to value column data
- # % answer: 1.0
- # %end
- # %Option
- # % key: percent
- # % type: integer
- # % required: no
- # % multiple: no
- # % options: 1-100
- # % description: Percent of map to keep in memory
- # % answer: 100
- # %End
- # %Option
- # % key: pth
- # % type: integer
- # % required: no
- # % multiple: no
- # % options: 1-100
- # % description: pth percentile of the values
- # % guisection: Statistic
- # %End
- # %Option
- # % key: trim
- # % type: double
- # % required: no
- # % multiple: no
- # % options: 0-50
- # % description: Discard <trim> percent of the smallest and <trim> percent of the largest observations
- # % guisection: Statistic
- # %End
- # %Option
- # % key: workers
- # % type: integer
- # % required: no
- # % multiple: no
- # % options: 1-256
- # % answer: 1
- # % description: Number of parallel processes to launch
- # %End
- import sys
- import os
- import atexit
- from grass.script import core as grass
- from grass.exceptions import CalledModuleError
- def cleanup():
- grass.run_command(
- "g.remove",
- flags="f",
- type="rast",
- pattern="tmp.r3xyz.%d.*" % os.getpid(),
- quiet=True,
- )
- def main():
- infile = options["input"]
- output = options["output"]
- method = options["method"]
- dtype = options["type"]
- fs = options["separator"]
- x = options["x"]
- y = options["y"]
- z = options["z"]
- value_column = options["value_column"]
- vrange = options["vrange"]
- vscale = options["vscale"]
- percent = options["percent"]
- pth = options["pth"]
- trim = options["trim"]
- workers = int(options["workers"])
- scan_only = flags["s"]
- shell_style = flags["g"]
- ignore_broken = flags["i"]
- if workers == 1 and "WORKERS" in os.environ:
- workers = int(os.environ["WORKERS"])
- if not os.path.exists(infile):
- grass.fatal(_("Unable to read input file <%s>") % infile)
- addl_opts = {}
- if pth:
- addl_opts["pth"] = "%s" % pth
- if trim:
- addl_opts["trim"] = "%s" % trim
- if value_column:
- addl_opts["value_column"] = "%s" % value_column
- if vrange:
- addl_opts["vrange"] = "%s" % vrange
- if vscale:
- addl_opts["vscale"] = "%s" % vscale
- if ignore_broken:
- addl_opts["flags"] = "i"
- if scan_only or shell_style:
- if shell_style:
- doShell = "g"
- else:
- doShell = ""
- grass.run_command(
- "r.in.xyz",
- flags="s" + doShell,
- input=infile,
- output="dummy",
- sep=fs,
- x=x,
- y=y,
- z=z,
- **addl_opts,
- )
- sys.exit()
- if dtype == "float":
- data_type = "FCELL"
- else:
- data_type = "DCELL"
- region = grass.region(region3d=True)
- if region["nsres"] != region["nsres3"] or region["ewres"] != region["ewres3"]:
- grass.run_command("g.region", flags="3p")
- grass.fatal(_("The 2D and 3D region settings are different. Can not continue."))
- grass.verbose(
- _("Region bottom=%.15g top=%.15g vertical_cell_res=%.15g (%d depths)")
- % (region["b"], region["t"], region["tbres"], region["depths"])
- )
- grass.verbose(_("Creating slices ..."))
- # to avoid a point which falls exactly on a top bound from being
- # considered twice, we shrink the
- # For the top slice we keep it though, as someone scanning the bounds
- # may have set the bounds exactly to the data extent (a bad idea, but
- # it happens..)
- eps = 1.0e-15
- # if there are thousands of depths hopefully this array doesn't get too
- # large and so we don't have to worry much about storing/looping through
- # all the finished process infos.
- proc = {}
- pout = {}
- depths = list(range(1, 1 + region["depths"]))
- for i in depths:
- tmp_layer_name = "tmp.r3xyz.%d.%s" % (os.getpid(), "%05d" % i)
- zrange_min = region["b"] + (region["tbres"] * (i - 1))
- if i < region["depths"]:
- zrange_max = region["b"] + (region["tbres"] * i) - eps
- else:
- zrange_max = region["b"] + (region["tbres"] * i)
- # spawn depth layer import job in the background
- # grass.debug("slice %d, <%s> %% %d" % (band, image[band], band % workers))
- grass.message(
- _("Processing horizontal slice %d of %d [%.15g,%.15g) ...")
- % (i, region["depths"], zrange_min, zrange_max)
- )
- proc[i] = grass.start_command(
- "r.in.xyz",
- input=infile,
- output=tmp_layer_name,
- sep=fs,
- method=method,
- x=x,
- y=y,
- z=z,
- percent=percent,
- type=data_type,
- zrange="%.15g,%.15g" % (zrange_min, zrange_max),
- **addl_opts,
- )
- grass.debug("i=%d, %%=%d (workers=%d)" % (i, i % workers, workers))
- # print sys.getsizeof(proc) # sizeof(proc array) [not so big]
- if i % workers == 0:
- # wait for the ones launched so far to finish
- for p_i in depths[:i]:
- pout[p_i] = proc[p_i].communicate()[0]
- if proc[p_i].wait() != 0:
- grass.fatal(_("Trouble importing data. Aborting."))
- # wait for jSobs to finish, collect any stray output
- for i in depths:
- pout[i] = proc[i].communicate()[0]
- if proc[i].wait() != 0:
- grass.fatal(_("Trouble importing data. Aborting."))
- del proc
- grass.verbose(_("Assembling 3D cube ..."))
- # input order: lower most strata first
- slices = grass.read_command(
- "g.list", type="raster", sep=",", pattern="tmp.r3xyz.%d.*" % os.getpid()
- ).rstrip(os.linesep)
- grass.debug(slices)
- try:
- grass.run_command("r.to.rast3", input=slices, output=output)
- except CalledModuleError:
- grass.message(_("Done. 3D raster map <%s> created.") % output)
- if __name__ == "__main__":
- options, flags = grass.parser()
- atexit.register(cleanup)
- main()
|