#!/bin/sh
#
############################################################################
#
# MODULE:	r.fillnulls
# AUTHOR(S):	Markus Neteler
#               Updated to GRASS 5.7 by Michael Barton
#               Updated to GRASS 6.0 by Markus Neteler
#               Ring improvements by Hamish Bowman
#               v.surf.bspline support by Markus Neteler
# PURPOSE:	fills NULL (no data areas) in raster maps
#               The script respects a user mask (MASK) if present.
#
# COPYRIGHT:	(C) 2001-2012 by the GRASS Development Team
#
#		This program is free software under the GNU General Public
#		License (>=v2). Read the file COPYING that comes with GRASS
#		for details.
#
#############################################################################


#%Module
#% description: Fills no-data areas in raster maps using spline interpolation.
#% keywords: raster, elevation, interpolation
#%End
#%option
#% key: input
#% gisprompt: old,cell,raster
#% type: string
#% description: Name of input raster map in which to fill nulls
#% required : yes
#%end
#%option
#% key: output
#% gisprompt: new,cell,raster
#% type: string
#% description: Name for output raster map with nulls filled by interpolation
#% required : yes
#%end
#%option
#% key: tension
#% type: double
#% description: Spline tension parameter
#% required : no
#% answer : 40.
#%end
#%option
#% key: smooth
#% type: double
#% description: Spline smoothing parameter
#% required : no
#% answer : 0.1
#%end
#%option
#% key: method
#% type: string
#% description: Interpolation method
#% options:bilinear,bicubic,rst
#% answer:rst
#% required:yes
#%end

if test "$GISBASE" = ""; then
 echo "You must be in GRASS GIS to run this program." >&2
 exit 1
fi   

# save command line
if [ "$1" != "@ARGS_PARSED@" ] ; then
    CMDLINE=`basename "$0"`
    for arg in "$@" ; do
        CMDLINE="$CMDLINE \"$arg\""
    done
    export CMDLINE
    exec g.parser "$0" "$@"
fi

PROG=`basename "$0"`

#### check if we have awk
if [ ! -x "`which awk`" ] ; then
    g.message -e "awk required, please install awk or gawk first"
    exit 1
fi

# setting environment, so that awk works properly in all languages
unset LC_ALL
LC_NUMERIC=C
export LC_NUMERIC

# what to do in case of user break:
exitprocedure()
{
   g.message -e 'User break!'
   #delete internal mask and any TMP files:
   g.remove rast="$TMP1","$TMP1.buf","${TMP1}_filled" --quiet > /dev/null
   g.remove vect="$VECTTMP" --quiet > /dev/null

   #restore user mask if present:
   eval `g.findfile element=cell file=$USERMASK`
   if [ "$file" ] ; then
      g.remove rast=MASK --quiet > /dev/null
      g.rename "$USERMASK",MASK --quiet > /dev/null
   fi

   # remove temporary region
   unset WIND_OVERRIDE
   g.remove region="tmp_rfillnulls.$$" --quiet

   exit 1
}
# shell check for user break (signal list: trap -l)
trap "exitprocedure" 2 3 15

#test:
if [ -z "$GIS_OPT_INPUT" ] ; then
   g.message "Please provide an input map name."
   exit 1
fi
if [ -z "$GIS_OPT_OUTPUT" ] ; then
   g.message "Please provide an output name."
   exit 1
fi

#check if input file exists
eval `g.findfile element=cell file="$GIS_OPT_INPUT"`
if [ ! "$file" ] ; then
    g.message -e "<$GIS_OPT_INPUT> does not exist! Aborting."
    exit 1
fi
case "$GIS_OPT_METHOD" in
  rst | bilinear | bicubic) ;;
  *) GIS_OPT_METHOD=rst ;;
esac

#check if a MASK is already present:
MASKTMP=mask.$$
USERMASK="usermask_$MASKTMP"
eval `g.findfile element=cell file=MASK`
if [ "$file" ] ; then
    g.message "A user raster mask (MASK) is present. Saving it..."
    g.rename MASK,"$USERMASK" --quiet > /dev/null
fi

#make a mask of NULL cells
TMP1=r_fillnulls_$$
#echo "Using tmp file $TMP1"

# idea: filter all NULLS and grow that area(s) by 3 pixel, then
# interpolate from these surrounding 3 pixel edge

g.message "Locating and isolating NULL areas..."
#creating 0/1 map:
r.mapcalc "$TMP1 = if(isnull(\"$GIS_OPT_INPUT\"),1,null())"

#generate a ring:
# the buffer is set to three times the map resolution so you get nominally
# three points around the edge. This way you interpolate into the hole with 
# a trained slope & curvature at the edges, otherwise you just get a flat plane.
# With just a single row of cells around the hole you often get gaps
# around the edges when distance > mean (.5 of the time? diagonals? worse 
# when ewres!=nsres).
eval `g.region -gm | grep res`
# check-me: quoting ok?
RES="`echo $nsres $ewres | awk '{printf "%f", ($1+$2) * 3. / 2.}'`"  # avg*3

# maybe use r.grow here..
r.buffer input="$TMP1" dist="$RES" out="$TMP1.buf"
if [ $? -ne 0 ] ; then
    g.message -e "$0 abandoned. Removing temporary map, restoring user mask if needed:"
    g.remove rast=MASK,"$TMP1","$TMP1.buf","${TMP1}_filled" --quiet > /dev/null
    g.remove vect="$VECTTMP" --quiet > /dev/null
    g.rename "$USERMASK",MASK --quiet > /dev/null
    exit 1
fi

r.mapcalc "MASK = if($TMP1.buf==2,1,null())"

# now we only see the outlines of the NULL areas if looking at INPUT.
# Use this outline (raster border) for interpolating the fill data:
VECTTMP=vecttmp_fillnulls_$$
g.message "Creating interpolation points..."

## use the -b flag to avoid topology building on big jobs?
## no, can't, 'g.region vect=' currently wants to see level 2
r.to.vect -z input="$GIS_OPT_INPUT" output="$VECTTMP" feature=point

if [ $? -ne 0 ] ; then
    g.message -e "$0 abandoned. Removing temporary maps, restoring user mask if needed:"
    g.remove rast=MASK,"$TMP1","$TMP1.buf","${TMP1}_filled" --quiet > /dev/null
    g.remove vect="$VECTTMP" --quiet > /dev/null
    g.rename "$USERMASK",MASK --quiet > /dev/null
    exit 1
fi
# this mask not needed any more
g.remove rast=MASK --quiet

#count number of points to control segmax parameter for interpolation:
# check-me: quoting?
POINTSNUMBER="`v.info -t map="$VECTTMP" | grep points | cut -d'=' -f2`"

g.message "Interpolating $POINTSNUMBER points"

if [ "$POINTSNUMBER" -lt 2 ] ; then
  g.message -w "Not sufficient points to interpolate. Maybe no hole(s) to fill in the current map region?"
  #remove internal MASK first
  g.remove MASK > /dev/null

  #restoring user's mask, if present:
  eval `g.findfile element=cell file=$USERMASK`
  if [ "$file" ] ; then
    g.message "Restoring user mask (MASK)..."
    g.rename "$USERMASK",MASK --quiet > /dev/null
  fi
  
  #cleanup
  g.remove rast="$TMP1","$TMP1.buf","${TMP1}_filled" --quiet > /dev/null
  g.remove vect="$VECTTMP" --quiet > /dev/null
  exit 1
fi


if [ "$GIS_OPT_METHOD" = "rst" ] ; then

   g.message "Using RST interpolation"

   ### zooming made it take slightly longer for bspline, so only use for rst
   # setup internal region
   g.region save="tmp_rfillnulls.$$"
   WIND_OVERRIDE="tmp_rfillnulls.$$"
   export WIND_OVERRIDE
   g.region vect="$VECTTMP" align="$GIS_OPT_INPUT"

   eval `g.findfile element=cell file=$USERMASK`
   if [ "$file" ] ; then
      g.message "Using user mask while interpolating"
      RST_CMD="v.surf.rst -z layer=0 tension=$GIS_OPT_TENSION smooth=$GIS_OPT_SMOOTH maskmap=$USERMASK"
   else
      RST_CMD="v.surf.rst -z layer=0 tension=$GIS_OPT_TENSION smooth=$GIS_OPT_SMOOTH"
   fi

   g.message "Note: The following warnings may be ignored."

   SEGMAX=600
   if [ "$POINTSNUMBER" -ge "$SEGMAX" ] ; then
      g.message "Using segmentation for interpolation..."
      $RST_CMD input="$VECTTMP" elev="${TMP1}_filled" #2> /dev/null
   else
      #if less than $SEGMAX points, use no segmentation for speedup:
      g.message "Not using segmentation for interpolation as it's not needed..."
      $RST_CMD input="$VECTTMP" elev="${TMP1}_filled" segmax="$SEGMAX" #2> /dev/null
   fi
   g.message "Note: Above warnings may be ignored."

   #restoring user's mask, if present:
   eval `g.findfile element=cell file=$USERMASK`
   if [ "$file" ] ; then
      g.message "Restoring user mask (MASK)..."
      g.rename "$USERMASK",MASK --quiet > /dev/null
   fi

   # restore real region
   unset WIND_OVERRIDE
   g.remove region="tmp_rfillnulls.$$" --quiet

else
   g.message "Using $GIS_OPT_METHOD (v.surf.bspline) interpolation"

   #restoring user's mask, if present
   # pointless for now, since v.surf.bspline ignores MASK, but someday...
   eval `g.findfile element=cell file=$USERMASK`
   if [ "$file" ] ; then
      g.message "Restoring user mask (MASK)..."
      g.rename "$USERMASK",MASK --quiet
   fi

   eval `g.region -g`
   SIE=`echo "$ewres" | awk '{printf "%f", $1 * 3.}'`
   SIN=`echo "$nsres" | awk '{printf "%f", $1 * 3.}'`

   v.surf.bspline input="$VECTTMP" raster="${TMP1}_filled" layer=0 \
      method="$GIS_OPT_METHOD" sie="$SIE" sin="$SIN" lambda_i=0.005

fi


#patch orig and fill map
g.message "Patching fill data into NULL areas..."
# we can use --o here as g.parser already checks on startup
r.patch in="$GIS_OPT_INPUT","${TMP1}_filled" out="$GIS_OPT_OUTPUT" --o

#cleanup
g.remove rast="$TMP1","$TMP1.buf","${TMP1}_filled" --quiet > /dev/null
g.remove vect="$VECTTMP" --quiet > /dev/null

g.message "Filled raster map is: $GIS_OPT_OUTPUT"

# write cmd history:
r.support "$GIS_OPT_OUTPUT" history="${CMDLINE}"

g.message "Done."
