r.fillnulls.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. #!/usr/bin/env python
  2. #
  3. ############################################################################
  4. #
  5. # MODULE: r.fillnulls
  6. # AUTHOR(S): Markus Neteler
  7. # Updated to GRASS 5.7 by Michael Barton
  8. # Updated to GRASS 6.0 by Markus Neteler
  9. # Ring and zoom improvements by Hamish Bowman
  10. # Converted to Python by Glynn Clements
  11. # Add support to v.surf.bspline by Luca Delucchi
  12. # PURPOSE: fills NULL (no data areas) in raster maps
  13. # The script respects a user mask (MASK) if present.
  14. #
  15. # COPYRIGHT: (C) 2001-2012 by the GRASS Development Team
  16. #
  17. # This program is free software under the GNU General Public
  18. # License (>=v2). Read the file COPYING that comes with GRASS
  19. # for details.
  20. #
  21. #############################################################################
  22. #%module
  23. #% description: Fills no-data areas in raster maps using spline interpolation.
  24. #% keywords: raster
  25. #% keywords: elevation
  26. #% keywords: interpolation
  27. #%end
  28. #%option G_OPT_R_INPUT
  29. #%end
  30. #%option G_OPT_R_OUTPUT
  31. #%end
  32. #%option
  33. #% key: tension
  34. #% type: double
  35. #% description: Spline tension parameter
  36. #% required : no
  37. #% answer : 40.
  38. #%end
  39. #%option
  40. #% key: smooth
  41. #% type: double
  42. #% description: Spline smoothing parameter
  43. #% required : no
  44. #% answer : 0.1
  45. #%end
  46. #%option
  47. #% key: method
  48. #% type: string
  49. #% description: Interpolation method
  50. #% required : yes
  51. #% options : bilinear,bicubic,rst
  52. #% answer : rst
  53. #%end
  54. import sys
  55. import os
  56. import atexit
  57. import grass.script as grass
  58. vecttmp = None
  59. tmp1 = None
  60. usermask = None
  61. mapset = None
  62. # what to do in case of user break:
  63. def cleanup():
  64. #delete internal mask and any TMP files:
  65. if tmp1:
  66. rasts = [tmp1 + ext for ext in ['', '.buf', '_filled']]
  67. grass.run_command('g.remove', quiet = True, flags = 'f', rast = rasts)
  68. if vecttmp:
  69. grass.run_command('g.remove', quiet = True, flags = 'f', vect = vecttmp)
  70. grass.run_command('g.remove', quiet = True, rast = 'MASK')
  71. if usermask and mapset:
  72. if grass.find_file(usermask, mapset = mapset)['file']:
  73. grass.run_command('g.rename', quiet = True, rast = (usermask, 'MASK'))
  74. def main():
  75. global vecttmp, tmp1, usermask, mapset
  76. input = options['input']
  77. output = options['output']
  78. tension = options['tension']
  79. smooth = options['smooth']
  80. method = options['method']
  81. mapset = grass.gisenv()['MAPSET']
  82. unique = str(os.getpid())
  83. #check if input file exists
  84. if not grass.find_file(input)['file']:
  85. grass.fatal(_("<%s> does not exist.") % input)
  86. # check if a MASK is already present:
  87. usermask = "usermask_mask." + unique
  88. if grass.find_file('MASK', mapset = mapset)['file']:
  89. grass.message(_("A user raster mask (MASK) is present. Saving it..."))
  90. grass.run_command('g.rename', quiet = True, rast = ('MASK',usermask))
  91. #make a mask of NULL cells
  92. tmp1 = "r_fillnulls_" + unique
  93. # save original region
  94. reg_org = grass.region()
  95. #check if method is rst to use v.surf.rst
  96. if method == 'rst':
  97. # idea: filter all NULLS and grow that area(s) by 3 pixel, then
  98. # interpolate from these surrounding 3 pixel edge
  99. grass.message(_("Locating and isolating NULL areas..."))
  100. #creating 0/1 map:
  101. grass.mapcalc("$tmp1 = if(isnull($input),1,null())",
  102. tmp1 = tmp1, input = input)
  103. #generate a ring:
  104. # the buffer is set to three times the map resolution so you get nominally
  105. # three points around the edge. This way you interpolate into the hole with
  106. # a trained slope & curvature at the edges, otherwise you just get a flat plane.
  107. # With just a single row of cells around the hole you often get gaps
  108. # around the edges when distance > mean (.5 of the time? diagonals? worse
  109. # when ewres!=nsres).
  110. # r.buffer broken in trunk for latlon, disabled
  111. #reg = grass.region()
  112. #res = (float(reg['nsres']) + float(reg['ewres'])) * 3 / 2
  113. #if grass.run_command('r.buffer', input = tmp1, distances = res, out = tmp1 + '.buf') != 0:
  114. # much easier way: use r.grow with radius=3.01
  115. if grass.run_command('r.grow', input = tmp1, radius = 3.01,
  116. old = 1, new = 2, out = tmp1 + '.buf') != 0:
  117. grass.fatal(_("abandoned. Removing temporary map, restoring user mask if needed:"))
  118. grass.mapcalc("MASK = if($tmp1.buf == 2, 1, null())", tmp1 = tmp1)
  119. # now we only see the outlines of the NULL areas if looking at INPUT.
  120. # Use this outline (raster border) for interpolating the fill data:
  121. vecttmp = "vecttmp_fillnulls_" + unique
  122. grass.message(_("Creating interpolation points..."))
  123. ## use the -b flag to avoid topology building on big jobs?
  124. ## no, can't, 'g.region vect=' currently wants to see level 2
  125. if grass.run_command('r.to.vect', input = input, output = vecttmp,
  126. type = 'point', flags = 'z'):
  127. grass.fatal(_("abandoned. Removing temporary maps, restoring user mask if needed:"))
  128. # count number of points to control segmax parameter for interpolation:
  129. pointsnumber = grass.vector_info_topo(map = vecttmp)['points']
  130. grass.message(_("Interpolating %d points") % pointsnumber)
  131. if pointsnumber < 2:
  132. grass.fatal(_("Not sufficient points to interpolate. Maybe no hole(s) to fill in the current map region?"))
  133. # remove internal MASK first -- WHY???? MN 10/2005
  134. grass.run_command('g.remove', quiet = True, rast = 'MASK')
  135. # print message is a usermask it was present
  136. if grass.find_file(usermask, mapset = mapset)['file']:
  137. grass.message(_("Using user mask while interpolating"))
  138. maskmap = usermask
  139. else:
  140. maskmap = None
  141. grass.message(_("Note: The following 'consider changing' warnings may be ignored."))
  142. # clone current region
  143. grass.use_temp_region()
  144. grass.run_command('g.region', vect = vecttmp, align = input)
  145. # set the max number before segmentation
  146. segmax = 600
  147. if pointsnumber > segmax:
  148. grass.message(_("Using segmentation for interpolation..."))
  149. segmax = None
  150. else:
  151. grass.message(_("Using no segmentation for interpolation as not needed..."))
  152. # launch v.surf.rst
  153. grass.message(_("Using RST interpolation..."))
  154. grass.run_command('v.surf.rst', input = vecttmp, elev = tmp1 + '_filled',
  155. zcol = 'value', tension = tension, smooth = smooth,
  156. maskmap = maskmap, segmax = segmax, flags = 'z')
  157. grass.message(_("Note: Above warnings may be ignored."))
  158. #check if method is different from rst to use r.resamp.bspline
  159. if method != 'rst':
  160. grass.message(_("Using %s bspline interpolation") % method)
  161. # clone current region
  162. grass.use_temp_region()
  163. grass.run_command('g.region', align = input)
  164. reg = grass.region()
  165. # launch r.resamp.bspline
  166. if grass.find_file(usermask, mapset = mapset)['file']:
  167. grass.run_command('r.resamp.bspline', input = input, mask = usermask,
  168. output = tmp1 + '_filled', method = method,
  169. se = 3 * reg['ewres'], sn = 3 * reg['nsres'],
  170. flags = 'n')
  171. else:
  172. grass.run_command('r.resamp.bspline', input = input,
  173. output = tmp1 + '_filled', method = method,
  174. se = 3 * reg['ewres'], sn = 3 * reg['nsres'],
  175. flags = 'n')
  176. # restoring user's mask, if present:
  177. if grass.find_file(usermask, mapset = mapset)['file']:
  178. grass.message(_("Restoring user mask (MASK)..."))
  179. grass.run_command('g.rename', quiet = True, rast = (usermask, 'MASK'))
  180. # set region to original extents, align to input
  181. grass.run_command('g.region', n = reg_org['n'], s = reg_org['s'],
  182. e = reg_org['e'], w = reg_org['w'], align = input)
  183. # patch orig and fill map
  184. grass.message(_("Patching fill data into NULL areas..."))
  185. # we can use --o here as g.parser already checks on startup
  186. grass.run_command('r.patch', input = (input,tmp1 + '_filled'), output = output, overwrite = True)
  187. # restore the real region
  188. grass.del_temp_region()
  189. grass.message(_("Filled raster map is: %s") % output)
  190. # write cmd history:
  191. grass.raster_history(output)
  192. grass.message(_("Done."))
  193. if __name__ == "__main__":
  194. options, flags = grass.parser()
  195. atexit.register(cleanup)
  196. main()