r.fillnulls.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python
  2. #
  3. ############################################################################
  4. #
  5. # MODULE: r.fillnulls
  6. # AUTHOR(S): Markus Neteler <neteler itc it>
  7. # Updated to GRASS 5.7 by Michael Barton
  8. # Updated to GRASS 6.0 by Markus Neteler
  9. # Ring improvements by Hamish Bowman
  10. # Converted to Python by Glynn Clements
  11. # PURPOSE: fills NULL (no data areas) in raster maps
  12. # The script respects a user mask (MASK) if present.
  13. #
  14. # COPYRIGHT: (C) 2001,2004-2005,2008 by the GRASS Development Team
  15. #
  16. # This program is free software under the GNU General Public
  17. # License (>=v2). Read the file COPYING that comes with GRASS
  18. # for details.
  19. #
  20. #############################################################################
  21. #%Module
  22. #% description: Fills no-data areas in raster maps using v.surf.rst splines interpolation
  23. #% keywords: raster, elevation, interpolation
  24. #%End
  25. #%option
  26. #% key: input
  27. #% gisprompt: old,cell,raster
  28. #% type: string
  29. #% description: Raster map in which to fill nulls
  30. #% required : yes
  31. #%end
  32. #%option
  33. #% key: output
  34. #% gisprompt: new,cell,raster
  35. #% type: string
  36. #% description: Output raster map with nulls filled by interpolation from surrounding values
  37. #% required : yes
  38. #%end
  39. #%option
  40. #% key: tension
  41. #% type: double
  42. #% description: Spline tension parameter
  43. #% required : no
  44. #% answer : 40.
  45. #%end
  46. #%option
  47. #% key: smooth
  48. #% type: double
  49. #% description: Spline smoothing parameter
  50. #% required : no
  51. #% answer : 0.1
  52. #%end
  53. import sys
  54. import os
  55. import atexit
  56. import grass.script as grass
  57. # what to do in case of user break:
  58. def cleanup():
  59. #delete internal mask and any TMP files:
  60. rasts = [tmp1 + ext for ext in ['', '.buf', '_filled']]
  61. grass.run_command('g.remove', flags = 'f', rast = rasts)
  62. grass.run_command('g.remove', flags = 'f', vect = vecttmp)
  63. grass.run_command('g.remove', rast = 'MASK')
  64. if grass.find_file(usermask, mapset = mapset)['file']:
  65. grass.run_command('g.rename', rast = (usermask, 'MASK'))
  66. def main():
  67. global vecttmp, tmp1, usermask, mapset
  68. input = options['input']
  69. output = options['output']
  70. tension = options['tension']
  71. smooth = options['smooth']
  72. #check if input file exists
  73. if not grass.find_file(input)['file']:
  74. grass.fatal("<%s> does not exist." % input)
  75. mapset = grass.gisenv()['MAPSET']
  76. unique = str(os.getpid())
  77. # check if a MASK is already present:
  78. usermask = "usermask_mask." + unique
  79. if grass.find_file('MASK', mapset = mapset)['file']:
  80. grass.message("A user raster mask (MASK) is present. Saving it...")
  81. grass.run_command('g.rename', rast = ('MASK',usermask))
  82. #make a mask of NULL cells
  83. tmp1 = "r_fillnulls_" + unique
  84. # idea: filter all NULLS and grow that area(s) by 3 pixel, then
  85. # interpolate from these surrounding 3 pixel edge
  86. grass.message("Locating and isolating NULL areas...")
  87. #creating 0/1 map:
  88. grass.mapcalc("$tmp1 = if(isnull($input),1,null())",
  89. tmp1 = tmp1, input = input)
  90. #generate a ring:
  91. # the buffer is set to three times the map resolution so you get nominally
  92. # three points around the edge. This way you interpolate into the hole with
  93. # a trained slope & curvature at the edges, otherwise you just get a flat plane.
  94. # With just a single row of cells around the hole you often get gaps
  95. # around the edges when distance > mean (.5 of the time? diagonals? worse
  96. # when ewres!=nsres).
  97. reg = grass.region()
  98. res = (float(reg['nsres']) + float(reg['ewres'])) * 3 / 2
  99. if grass.run_command('r.buffer', input = tmp1, distances = res, out = tmp1 + '.buf') != 0:
  100. grass.fatal("abandoned. Removing temporary map, restoring user mask if needed:")
  101. grass.mapcalc("MASK=if($tmp1.buf==2,1,null())", tmp1 = tmp1)
  102. # now we only see the outlines of the NULL areas if looking at INPUT.
  103. # Use this outline (raster border) for interpolating the fill data:
  104. vecttmp = "vecttmp_fillnulls_" + unique
  105. grass.message("Creating interpolation points...")
  106. if grass.run_command('r.to.vect', input = input, output = vecttmp, feature = 'point'):
  107. grass.fatal("abandoned. Removing temporary maps, restoring user mask if needed:")
  108. # count number of points to control segmax parameter for interpolation:
  109. pointsnumber = grass.vector_info_topo(map = vecttmp)['points']
  110. grass.message("Interpolating %d points" % pointsnumber)
  111. if pointsnumber < 2:
  112. grass.fatal("Not sufficient points to interpolate. Maybe no hole(s) to fill in the current map region?")
  113. grass.message("Note: The following warnings may be ignored.")
  114. # remove internal MASK first -- WHY???? MN 10/2005
  115. grass.run_command('g.remove', rast = 'MASK')
  116. if grass.find_file(usermask, mapset = mapset)['file']:
  117. grass.message("Using user mask while interpolating")
  118. maskmap = usermask
  119. else:
  120. maskmap = None
  121. segmax = 600
  122. if pointsnumber > segmax:
  123. grass.message("Using segmentation for interpolation...")
  124. segmax = None
  125. else:
  126. grass.message("Using no segmentation for interpolation as not needed...")
  127. grass.run_command('v.surf.rst', input = vecttmp, elev = tmp1 + '_filled',
  128. zcol = 'value', tension = tension, smooth = smooth,
  129. maskmap = maskmap, segmax = segmax)
  130. grass.message("Note: Above warnings may be ignored.")
  131. # restoring user's mask, if present:
  132. if grass.find_file(usermask, mapset = mapset)['file']:
  133. grass.message("Restoring user mask (MASK)...")
  134. grass.run_command('g.rename', rast = (usermask, 'MASK'))
  135. # patch orig and fill map
  136. grass.message("Patching fill data into NULL areas...")
  137. # we can use --o here as g.parser already checks on startup
  138. grass.run_command('r.patch', input = (input,tmp1 + '_filled'), output = output, overwrite = True)
  139. grass.message("Filled raster map is: %s" % output)
  140. # write cmd history:
  141. grass.raster_history(output)
  142. grass.message("Done.")
  143. if __name__ == "__main__":
  144. options, flags = grass.parser()
  145. atexit.register(cleanup)
  146. main()