#!/bin/sh
#
############################################################################
#
# MODULE:       v.in.garmin
#
# AUTHOR(S):    Based on v.in.garmin.sh for GRASS 5.0 by Andreas Lange
#                  with bug fixes by Glynn Clements and Markus Neteler
#               Updated for GRASS 5.3 and 5.7 by Hamish Bowman
#
# PURPOSE:      Import GPS data from Garmin receiver into a GRASS vector map
#
# COPYRIGHT:    Original version (c) Andreas Lange
#               Updates by Hamish Bowman (c) the GRASS Development Team
#
#############################################################################
#
# REQUIREMENTS:
#      -  gpstrans from Carsten Tschach et al. 
#         At the time of writing, gpstrans is at version 0.39.
#           get gpstrans from:
#             http://gpstrans.sourceforge.net
#           older versions might be found at:
#             http://www.metalab.unc.edu/pub/Linux/science/cartography/
#             ftp://www.mayko.com/pub/gpstrans
#      -  unix tools: grep, cat, tac, cut, paste, awk/nawk or gawk, sed
#      -  cs2cs from PROJ.4    http://proj.maptools.org
#

#%Module
#%  description: Upload Waypoints, Routes, and Tracks from a Garmin GPS receiver into a vector map.
#%  keywords: vector, import, GPS
#%End
#%flag
#%  key: v
#%  description: verbose mode
#%end
#%flag
#%  key: w
#%  description: upload Waypoints from GPS
#%end
#%flag
#%  key: r
#%  description: upload Routes from GPS
#%end
#%flag
#%  key: t
#%  description: upload Track from GPS
#%end
#%flag
#%  key: p
#%  description: force import of track or route data as points
#%end
#%flag
#%  key: k
#%  description: do not attempt projection transform from WGS84
#%end
#%option
#% key: output
#% type: string
#% gisprompt: new,vector,vector
#% description: name for new vector map (omit for display to stdout)
#% required : no
#%end
#%option
#% key: port
#% type: string
#% description: port Garmin receiver is connected to
#% answer: /dev/gps
#% required : no
#%end

if [ -z "$GISBASE" ] ; then
    echo "You must be in GRASS GIS to run this program."
    exit 1
fi

if [ "$1" != "@ARGS_PARSED@" ] ; then
  exec g.parser "$0" "$@"
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"

PROG=`basename $0`


#### check for gpstrans 
if [ ! -x "`which gpstrans`" ] ; then
    echo "$PROG: gpstrans program not found, install it first" 1>&2
    echo "       http://gpstrans.sourceforge.net" 1>&2
    exit 1
fi


#### check for cs2cs
if [ ! -x "`which cs2cs`" ] ; then
    echo "$PROG: cs2cs program not found, install it first" 1>&2
    echo "       http://proj.maptools.org" 1>&2
    exit 1
fi


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


#### setup temporary file
TMP="`g.tempfile pid=$$`"
if [ $? -ne 0 ] || [ -z "$TMP" ] ; then
    echo "ERROR: unable to create temporary files" 1>&2
    exit 1
fi


#### trap ctrl-c so that we can clean up tmp
trap 'rm -f ${TMP}*' 2 3 15


#### process command line arguments 
WPT=0 ; RTE=0 ; TRK=0 ; KEEP_WGS84=0; VERBOSE=0


if [ $GIS_FLAG_V -eq 1 ] ; then
    VERBOSE=1
fi

if [ -n "$GIS_OPT_OUTPUT" ] ; then
    NAME="$GIS_OPT_OUTPUT"
    if [ $VERBOSE -eq 1 ] ; then
	echo "output=$NAME" 1>&2
    fi
fi
if [ -n "$GIS_OPT_PORT" ] ; then
    GPSPORT="-p$GIS_OPT_PORT"
    if [ $VERBOSE -eq 1 ] ; then
	echo "port=$GIS_OPT_PORT" 1>&2
    fi
fi

if [ $GIS_FLAG_W -eq 1 ] && ([ $GIS_FLAG_R -eq 1 ] || [ $GIS_FLAG_T -eq 1 ]) ; then
    echo "One feature at a time please. Use v.patch if you need to combine them."
    rm -f ${TMP}*
    exit 1
fi
# logic eludes me at pressent..  [combine with above]
if [ $GIS_FLAG_R -eq 1 ] && [ $GIS_FLAG_T -eq 1 ] ; then
    echo "One feature at a time please. Use v.patch if you need to combine them."
    rm -f ${TMP}*
    exit 1
fi

if [ $GIS_FLAG_W -eq 1 ] ; then
    WPT=1
    CREATE_POINTS=1
fi
if [ $GIS_FLAG_R -eq 1 ] ; then
    RTE=1
    CREATE_POINTS=0
fi
if [ $GIS_FLAG_T -eq 1 ] ; then
    TRK=1
    CREATE_POINTS=0
fi

if [ $GIS_FLAG_P -eq 1 ] ; then
    CREATE_POINTS=1
fi
if [ $GIS_FLAG_K -eq 1 ] ; then
    KEEP_WGS84=1
fi

if [ "`expr $WPT + $TRK + $RTE`" -eq 0 ] ; then
    echo "Must request either waypoints, route, or track."
    rm -f ${TMP}*
    exit 1
fi


#### check that receiver is responding on $GPSPORT
# sadly gpstrans 0.39 returns 0 after timeout.. hopefully fixed someday.
gpstrans "$GPSPORT" -i 1> /dev/null
if [ $? -ne 0 ] ; then
    echo "$PROG: Receiver on $GPSPORT not responding, exiting" 1>&2
    rm -f ${TMP}*
    exit 1
fi

#### set up projection info
IN_PROJ="+proj=longlat +towgs84=0.000,0.000,0.000"
OUT_PROJ="`g.proj -jf`"
PROJ_TYPE=`g.region -p | grep 'projection' | cut -f2 -d" "`
if [ $PROJ_TYPE -eq 0 ] && [ $KEEP_WGS84 -ne 1 ] ; then
    echo "Cannot project to a XY location." 1>&2
    rm -f ${TMP}*
    exit 1
fi

#### receive data
if [ $WPT -eq 1 ] ; then
    echo "Uploading Waypoints" 1>&2
    gpstrans "$GPSPORT" -dw > "$TMP".gpst 2>/dev/null
fi
if [ $RTE -eq 1 ] ; then
    echo "Uploading Routes" 1>&2
    gpstrans "$GPSPORT" -dr > "$TMP".gpst 2>/dev/null
fi
if [ $TRK -eq 1 ] ; then
    echo "Uploading Track" 1>&2
    gpstrans "$GPSPORT" -dt > "$TMP".gpst 2>/dev/null 
fi

#### check success/failure
if [ $? -ne 0 ] ; then
    echo "$PROG: Error retrieving data" 1>&2
    rm -f ${TMP}*
    exit 1
fi

if [ `wc -l < "$TMP".gpst` -le 1 ] ; then
    g.message -w 'No Data! Exiting.'
    rm -f "${TMP}"*
    exit 0
fi

#### check which projection we are working with
PROJ="`head -n 1 "$TMP".gpst | sed -e 's/Format: //' | sed -e 's/  UTC.*//'`"
if [ $VERBOSE -eq 1 ] ; then
    echo Coordinate format: ${PROJ} 1>&2
fi
if [ "$PROJ" != "DDD" ] ; then
    # Why? Because v.in.ascii cannot, and cs2cs with trouble deals with DMS 
    # or DM.MM formatted input and I'm not going to start doing that sort
    # of math in a shell script.
    echo "Only DDD coordinates currently supported. [$PROJ]"
    rm -f ${TMP}*
    exit 1
fi
IS_WGS84="`head -n 1 "$TMP".gpst | grep 'WGS 84'`"


###################################
if [ $CREATE_POINTS -eq 1 ] ; then
###################################

    if [ $WPT -eq 1 ] ; then
	cat "${TMP}.gpst" | sed -e '1d' | awk -F '\t' '{print $6 " " $5}' > "$TMP".base
	cat "${TMP}.gpst" | sed -e '1d' | cut -f1,2,3 | tr '\t' '|' > "$TMP".attributes
    fi
    if [ $RTE -eq 1 ] ; then
 	cat "${TMP}.gpst" | sed -e '1d' | grep '^W' | awk -F '\t' '{print $6 " " $5}' > "$TMP".base
 	cat "${TMP}.gpst" | sed -e '1d' | grep '^W' | cut -f1,2,3 | tr '\t' '|' > "$TMP".attributes
    fi
    if [ $TRK -eq 1 ] ; then
	# we use uniq here as gpstrans can dupe up track points !?
	uniq "${TMP}.gpst" | sed -e '1d' | grep '^T' | awk -F '\t' '{print $4 " " $3}' > "$TMP".base
	uniq "${TMP}.gpst" | sed -e '1d' | grep '^T' | cut -f1,2 | tr '\t' '|' > "$TMP".attributes
    fi

    #### reproject if needed
    if [ -z "$IS_WGS84" ] || [ $KEEP_WGS84 -eq 1 ] ; then
    	echo "No projection transformation performed" 1>&2
    	cp "${TMP}.base" "${TMP}.P_base"
    else
	echo "Attempting waypoint projection transform with cs2cs" 1>&2
	cs2cs -f %.7f $IN_PROJ +to $OUT_PROJ < "${TMP}.base" > "${TMP}.P_base"
	EXITCODE=$?
	# check if transform REALLY worked (e.g. grid file not found)
	BAD_PTS="`grep -c "^*" "${TMP}.P_base"`"
	if [ "$BAD_PTS" -gt 0 ] ; then
	    echo 1>&2
	    echo "WARNING: $BAD_PTS point(s) failed reprojection." 1>&2
	    EXITCODE=1
	fi
	if [ $EXITCODE -ne 0 ] ; then
	    echo "Projection transform failed, retaining WGS84" 1>&2
	    echo 1>&2
	    cp -f "${TMP}.base" "${TMP}.P_base"
	fi
    fi
    cat "${TMP}.P_base" | awk '{print $1 "|" $2}' > "${TMP}.vertices"

    #### and put back together
    # wpt list:   x|y|    W   MOB    04-OCT-03 08:07 
    paste -d"|" "$TMP".vertices "$TMP".attributes > "$TMP".asc


    if [ -z "$NAME" ] ; then
	echo "ASCII file redirected to stdout" 1>&2
	cat "${TMP}.asc" 2> /dev/null
    else
	#### import into new ascii points file
	if [ $WPT -eq 1 ] ; then
	    echo "Importing Waypoints..."
	    cat "${TMP}.asc" | v.in.ascii output="$NAME" cat=0 \
	    	columns='x double precision, y double precision, type varchar(1), name varchar(10), comments varchar(40)'
	    EXITCODE=$?
	fi
	if [ $RTE -eq 1 ] ; then
	    echo "Importing Routes as points..."
	    cat "${TMP}.asc" | v.in.ascii output="$NAME" cat=0 \
	    	columns='x double precision, y double precision, type varchar(1), name varchar(10), comments varchar(40)'
	    EXITCODE=$?
	fi
	if [ $TRK -eq 1 ] ; then
	    echo "Importing Track as points..."
	    cat "${TMP}.asc" | v.in.ascii output="$NAME" cat=0 \
	    	columns='x double precision, y double precision, type varchar(1), localtime varchar(40)'
	    EXITCODE=$?
	fi

	if [ $EXITCODE -ne 0 ] ; then
	    echo "$PROG: Error importing data" 1>&2
	    rm -f ${TMP}*
	    exit 1
	fi

    fi

#####################
else # CREATE_LINES
#####################

    #### prepare line components
    if [ $RTE -eq 1 ] ; then
	# add vertex counts
	cat "${TMP}.gpst" | sed -e '1d' | tac | awk 'BEGIN { FS="\t" ; R=0 } \
	    $1=="W" { printf(" %.7f %.7f\n", $6, $5) ; ++R } ; \
	    $1=="R" { printf("L %d 1\n", R) ; R=0 } END {;}' | tac > "${TMP}.base"

	# create attr table: cat(int), id number(int 0-19), name varchar(16+), starting_wpt(varchar 10)
	cat "${TMP}.gpst" | grep '^R' | cut -f2,3 | grep -n '^' | \
	    sed -e 's/:/\t/' > "${TMP}.route_ids"
	cat "${TMP}.gpst" | sed -e '1d' | grep '^R' -A 1 | grep '^W' | cut -f2 > "${TMP}.route_start"
	paste "${TMP}.route_ids" "${TMP}.route_start" > "${TMP}.route_atts"
	ATTR_FILE="${TMP}.route_atts"
	ATTR_COLS='cat int, route_id int, name varchar(20), start_wpt varchar(10)'
    fi

    if [ $TRK -eq 1 ] ; then
	# add vertex counts
	cat "${TMP}.gpst" | sed -e '1d' | tac | awk 'BEGIN { FS="\t" ; R=0 } \
	    $1=="T" { printf(" %.7f %.7f\n", $4, $3) ; ++R } ; \
	    $1=="" { printf("L %d 1\n", R) ; R=0 } END {;}' | tac > "${TMP}.base"

	# create attr table: cat(int), start_time varchar(40), start_lat(double precision), start_lon(double precision), \
	#			end_time varchar(40), end_lat(double precision), end_lon(double precision)
	cat "${TMP}.gpst" | sed -e '1d' | grep '^$' -B 1 | grep '^T' | cut -f2,3,4 > "${TMP}.end_time"
	cat "${TMP}.gpst" | grep '^T' | tail -n 1 | cut -f2,3,4 >> "${TMP}.end_time"
	cat "${TMP}.gpst" | sed -e '1d' | grep '^$' -A 1 | grep '^T' | cut -f2,3,4 | \
	    grep -n '^' | sed -e 's/:/\t/'  > "${TMP}.start_time"
	paste "${TMP}.start_time" "${TMP}.end_time" > "${TMP}.track_atts"
	ATTR_FILE="${TMP}.track_atts"
	ATTR_COLS='cat int, start_time varchar(40), start_lat double precision, start_lon double precision, end_time varchar(40), end_lat double precision, end_lon double precision'
    fi


    #### reproject if needed
    if [ -z "$IS_WGS84" ] || [ $KEEP_WGS84 -eq 1 ] ; then
    	echo "No projection transformation performed" 1>&2
	cat "${TMP}.base" | awk 'BEGIN {;} $1=="L" { print } ; \
	    $1!="L" { printf(" %.7f %.7f\n", $1, $2) } END {;}' > "${TMP}.P_base"
    else
    	echo "Attempting waypoint projection transform with cs2cs" 1>&2
    	cs2cs -tL -f %.7f $IN_PROJ +to $OUT_PROJ < "${TMP}.base" > "${TMP}.P_mid"
	EXITCODE=$?
	# check if transform REALLY worked (e.g. grid file not found)
	BAD_PTS="`grep -c "^*" "${TMP}.P_mid"`"
	if [ "$BAD_PTS" -gt 0 ] ; then
	    echo 1>&2
	    echo "WARNING: $BAD_PTS vertices failed reprojection." 1>&2
	    EXITCODE=1
	fi
    	if [ $EXITCODE -ne 0 ] ; then
    	    echo "Projection transform failed, retaining WGS84" 1>&2
	    echo 1>&2
	    cat "${TMP}.base" | awk 'BEGIN {;} $1=="L" { print } ; \
		$1!="L" { printf(" %.7f %.7f\n", $1, $2) } END {;}' > "${TMP}.P_base"
    	else
	    cat "${TMP}.P_mid" | awk 'BEGIN {;} $1=="L" { print } ; \
		$1!="L" { printf(" %.7f %.7f\n", $1, $2) } END {;}' > "${TMP}.P_base"
	fi
    fi

    # add category numbers
    cat "${TMP}.P_base" | awk 'BEGIN { FS=" " ; R=0 } \
	$1=="L" { printf(" 1 %d\nL %d 1\n", R, $2) ; ++R } ; \
	$1!="L" { print } END { printf(" 1 %d\n", R) }' | sed -e '1d' > "${TMP}.P_baseC"


    #### create digit header
    cat << EOF > "${TMP}.dig"
ORGANIZATION: GRASSroots organization
DIGIT DATE:   `date +%D`
DIGIT NAME:   $PROG
MAP NAME:     $NAME
MAP DATE:     `date +%Y`
MAP SCALE:    1
OTHER INFO:   Imported by `echo $USER@$HOSTNAME`
ZONE:         0
MAP THRESH:   0
VERTI:
EOF

    #### merge in vertices
    cat "${TMP}.P_baseC" >> "${TMP}.dig"

    #### if no name for vector file given, cat to stdout
    if [ -z "$NAME" ] ; then
	echo "ASCII file redirected to stdout" 1>&2
	cat "${TMP}.dig" 2> /dev/null
    else
	#### import to binary vector file 
	echo "Importing with v.in.ascii" 1>&2
	v.in.ascii format=standard output="$NAME" input="${TMP}.dig"

	#### check success/failure
	if [ $? -eq 0 ] ; then
	    echo "$PROG: line vector \"$NAME@$MAPSET\" successfully created" 1>&2
	else
	    echo "$PROG: An error occured on creating \"$NAME\" in mapset \"$MAPSET\"," 1>&2
	    echo 'please check!' 1>&2
	    exit 1
	fi

	# create db links if they don't exist
	DRIVER="`db.connect -p | grep '^driver:' | cut -f2 -d:`"
	DATABASE="`db.connect -p | grep '^database:' | cut -f2 -d:`"
	if [ "$DRIVER" = "(null)" ] || [ "$DATABASE" = "(null)" ] ; then
	    echo "Setting default database driver to DBF."
	    db.connect driver=dbf database='$GISDBASE/$LOCATION_NAME/$MAPSET/dbf/'
	fi
	DRIVER="`db.connect -p | grep '^driver:' | cut -f2 -d:`"
	if [ "$DRIVER" = "dbf" ] && [ ! -d "$LOCATION/dbf/" ] ; then
	    mkdir "$LOCATION/dbf/"
	fi

	echo "Creating new table.."
	echo "CREATE TABLE $NAME ($ATTR_COLS)" | db.execute

	echo "Populating table with attributes.."
# echo "COPY $NAME FROM '$ATTR_FILE'" | db.execute

# "COPY table FROM 'filename.txt'" does not work for DBF and there is no
#   db.in.ascii yet, so we use a hack ...

### <hack>
	if [ $RTE -eq 1 ] ; then
	    # =>  echo "INSERT INTO $NAME VALUES (2, 1, 'AB', 'B')" | db.execute
	    cat "$ATTR_FILE" | \
	      awk -F'\t' '{ printf("echo \"INSERT INTO $NAME VALUES (%d, %d, ^%s^, ^%s^)\" | db.execute\n", $1, $2, $3, $4) }' \
	        | tr '^' \' >  "$ATTR_FILE"_exe
	fi
	if [ $TRK -eq 1 ] ; then
	    # =>  echo "INSERT INTO $NAME VALUES ()" | db.execute
	    cat "$ATTR_FILE" | \
	      awk -F'\t' '{ printf("echo \"INSERT INTO $NAME VALUES (%d, ^%s^, %.7f, %.7f, ^%s^, %.7f, %.7f)\" | db.execute\n", $1, $2, $3, $4, $5, $6, $7) }' \
	        | tr '^' \' >  "$ATTR_FILE"_exe
	fi

	. "$ATTR_FILE"_exe
### </hack>

	echo "Connecting attribute table to vector file.."
	v.db.connect map="$NAME" table="$NAME"
    fi

fi  # if CREATE_POINTS or CREATE_LINES


# list column names
if [ $VERBOSE -eq 1 ] && [ -n "$NAME" ] ; then
    echo
    echo "<$NAME> columns:"
    db.columns "$NAME"
fi


#### clean up the mess
rm -f "${TMP}"*

#### end
echo 1>&2
echo "Done." 1>&2

exit 0
