#!/bin/sh
#
############################################################################
#
# MODULE:	r.fillnulls
# AUTHOR(S):	Markus Neteler <neteler itc it>
#               Updated to GRASS 5.7 by Michael Barton
#               Updated to GRASS 6.0 by Markus Neteler
#               Ring improvements by Hamish Bowman
# PURPOSE:	fills NULL (no data areas) in raster maps
#               The script respects a user mask (MASK) if present.
#
# COPYRIGHT:	(C) 2001,2004-2005 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 v.surf.rst splines interpolation
#% keywords: raster, elevation, interpolation
#%End
#%option
#% key: input
#% gisprompt: old,cell,raster
#% type: string
#% description: Raster map in which to fill nulls
#% required : yes
#%end
#%option
#% key: output
#% gisprompt: new,cell,raster
#% type: string
#% description: Output raster map with nulls filled by interpolation from surrounding values
#% 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

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

if [ "$1" != "@ARGS_PARSED@" ] ; then
  exec g.parser "$0" "$@"
fi

PROG=`basename $0`

#### check if we have awk
if [ ! -x "`which awk`" ] ; then
    echo "$PROG: awk required, please install awk or gawk first" 2>&1
    exit 1
fi

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

eval `g.gisenv`
: ${GISBASE?} ${GISDBASE?} ${LOCATION_NAME?} ${MAPSET?}
LOCATION=$GISDBASE/$LOCATION_NAME/$MAPSET

# what to do in case of user break:
exitprocedure()
{
 echo "User break!"
 #delete internal mask and any TMP files:
 g.remove rast=$TMP1,$TMP1.buf,${TMP1}_filled > /dev/null
 g.remove vect=${VECTTMP} > /dev/null
 
 #restore user mask if present:
 if test -f $LOCATION/cell/$USERMASK ; then
  g.remove rast=MASK > /dev/null
  g.rename $USERMASK,MASK > /dev/null
 fi
 exit 1
}
# shell check for user break (signal list: trap -l)
trap "exitprocedure" 2 3 15

program=`basename $0`

#test:
if [ ! $GIS_OPT_INPUT ]
then
 echo "Please provide an input map name."
 exit 1
fi
if [ ! $GIS_OPT_OUTPUT ]
then
 echo "Please provide an output name."
 exit 1
fi

#check if input file exists
#if test -f $LOCATION/cell/$GIS_OPT_INPUT
if [ -z `g.findfile elem=cell file="$GIS_OPT_INPUT" | grep ^file | cut -f2 -d=` ] ; then
    echo "<$GIS_OPT_INPUT> does not exist! Aborting."
    exit 1
fi

#check if a MASK is already present:
MASKTMP=mask.$$
USERMASK=usermask_${MASKTMP}
if test -f $LOCATION/cell/MASK
then
 echo "A user raster mask (MASK) is present. Saving it..."
 g.rename MASK,$USERMASK > /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

echo "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`
RES="`echo $nsres $ewres | awk '{printf "%f", ($1+$2) * 3. / 2.}'`"  # avg*3

r.buffer input=$TMP1 dist=$RES out=$TMP1.buf
if [ $? -ne 0 ] ; then
    echo "ERROR detected: $0 abandoned. Removing temporary map, restoring user mask if needed:"
    g.remove rast=MASK,$TMP1,$TMP1.buf,${TMP1}_filled > /dev/null
    g.remove vect=${VECTTMP} > /dev/null
    g.rename $USERMASK,MASK > /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_$$
echo "Creating interpolation points..."
r.to.vect input=$GIS_OPT_INPUT output=${VECTTMP} feature=point
if [ $? -ne 0 ] ; then
    echo "ERROR detected: $0 abandoned. Removing temporary maps, restoring user mask if needed:"
    g.remove rast=MASK,$TMP1,$TMP1.buf,${TMP1}_filled > /dev/null
    g.remove vect=${VECTTMP} > /dev/null
    g.rename $USERMASK,MASK > /dev/null
    exit 1
fi

#count number of points to control segmax parameter for interpolation:
POINTSNUMBER="`v.info map=${VECTTMP} | grep 'Number of points' | awk '{print $5}'`"

echo ""
echo "Interpolating $POINTSNUMBER points"
echo ""

if [ $POINTSNUMBER -lt 2 ] ; then
  echo "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:
  if test -f $LOCATION/cell/$USERMASK
  then
   echo "Restoring user mask (MASK)..."
   g.rename $USERMASK,MASK > /dev/null
  fi
  
  #cleanup
  g.remove rast=$TMP1,$TMP1.buf,${TMP1}_filled > /dev/null
  g.remove vect=${VECTTMP} > /dev/null
  exit 1
fi

echo ""
echo "Note: Below warnings may be ignored."
echo "------------------------------------"
echo ""

#remove internal MASK first -- WHY???? MN 10/2005
g.remove MASK > /dev/null

if test -f $LOCATION/cell/$USERMASK
then
  echo "Using user mask while interpolating"
  RST_CMD="v.surf.rst zcol=value tension=$GIS_OPT_TENSION smooth=$GIS_OPT_SMOOTH maskmap=$USERMASK"
else
  RST_CMD="v.surf.rst zcol=value tension=$GIS_OPT_TENSION smooth=$GIS_OPT_SMOOTH"
fi

SEGMAX=600
if [ $POINTSNUMBER  -ge $SEGMAX ] ; then
  echo "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:
  echo "Using no segmentation for interpolation as not needed..."
  $RST_CMD input=${VECTTMP} elev=${TMP1}_filled segmax=$SEGMAX #2> /dev/null
fi

echo "------------------------------------"
echo "Note: Above warnings may be ignored."
echo ""


#restoring user's mask, if present:
if test -f $LOCATION/cell/$USERMASK
then
 echo "Restoring user mask (MASK)..."
 g.rename $USERMASK,MASK > /dev/null
fi

#patch orig and fill map
echo "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 > /dev/null
g.remove vect=${VECTTMP} > /dev/null

echo ""
echo "Filled raster map is: $GIS_OPT_OUTPUT"
echo
echo "Done."
