#! /usr/bin/perl  -w
#ifnot1argument: 
if ( $#ARGV != 0 || $ARGV[0] eq '-?' ) {
#-----------------adescription1------------------------------------
print "Convert CAI GPS waypoint file \$1 to GPX/XML format text file \n";
print "  NB: outputs to stdout in DOS format \n";
print "      save it with .txt tail filename as otherwise MapSource may not read! it \n";
exit 0; }
### Intended for use with Garmin GPSmap60 GPS
### Output file can be read by Garmin's MapSource software and transferred to GPS
### Jack Glendening  drjack@drjack.info  July 28 2007
###############################################################################

###############################################################################
### PROGRAMMING NOTES
### Currently set to add 1000ft to elevation so can use Garmin's Glide-Ratio function
### Removes spaces from waypoint name (currently has 14 character limit)
### Uses 'Airport'/'Glider Area' symbols for airport/non-airport locations
###   for some reason I had difficulty getting a symbol which would display 
###   as other than the default "Blue Flag" in my GPSmap60.
###   for example - "Dot" displays as Blue Flag while "Soft Field" and "Cross with X" 
###   would display a symbol in MapSource but be displayed as a Blue Flag in the GPSmap60
###   I needed a "non-airport" symbol and finally chose "Glider Area" 
###   since that gave a non-Blue-Flag and I wanted to ensure that these "aviation"
###   waypoints differed from any other waypoints symbol
### Determines airport/non-airport locations based on CAI file indication but may not include all possibilities
###############################################################################
### HOLLISTER SPECIFIC NOTES:
### DZ uses a parachute symbol
###############################################################################

###############################################################################

##########   USER ALTERABLE PARAMETERS   ##########

### SET MAXIMUM NO. OF CHARACTERS IN NAME
### value 14 determined by checking waypoint name editing allowed by GPSmap60csx
###    but seems ok to have more than 14 & have extra chars stripped from end by GPS itself
$MAXNAMECHARS = 14 ;

### SET ELEVATION OFFSET FEET 
### (sum of $ALTOFFSETFT & actual field elevation is put into gps elevation field)
 $ALTOFFSETFT = 1000. ;

###############################################################################

###############################################################################
  ### BEHAVIOUR INITIALIZATIONS
  ### for cgi use taint checking option -T
  ### FOR VISUAL DEBUGGER:  /usr/bin/perl -d:ptkdb example.pl
  ### FOR DEBUG MODE: run with -d flag 
  ###    i debug mode, set package name + local variables so X,V don't show "main" variables, ie:
  # package Main; local ($a,$b,...);
  ### To restrict unsafe constructs (vars,refs,subs)
  ###    vars requires variables to be declared with "my" or fully qualified or imported
  ###    refs generates error if symbolic references uses instead of hard refs
  ###    subs requires subroutines to be predeclared
  #    use strict;
  ### To provide aliases for buit-in punctuation variables (p403)
use English;
  ### for non-buffered STDOUT,STDERR ouput:
select STDERR; $|=1;
select STDOUT; $|=1;   #must be last select
  #old  use FileHandle; STDOUT->autoflush(1);  # or autoflush HANDLE EXPR (needs "use FileHandle;")
  ### to append new line to each print record:     $\="\n"; #($OUTPUT_RECORD_SEPARATOR)
  ### for error statements with subroutine traceback
use Carp ();
local $SIG{__WARN__} = \&Carp::cluck;
  ### To enable verbose diagnostics:
  #   use diagnostics;
###############################################################################

### INITIALIZE
$datime = `date -u +%Y'-'%m'-'%d'T'%H':'%M':'%S'Z'` ;
chomp ( $datime );
my ( $id );

### READ INPUT FILE
open( INFILE,"<$ARGV[0]") or die 'ERROR EXIT - input file not found';
@lines = <INFILE>;

### WRITE GPX FILE HEADER
printf "%s\r\n", '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>';
printf "%s\r\n",'<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="Jack Glendening" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' ;

### READ INPUT FILE AND CREATE OUTPUT LINE
### PLACEMENT WITHIN "WAYPOINT NAME" LINES OF INPUT FILE
$idlast = 0;
foreach $line (@lines)
{
  ### SKIP COMMENT LINES STARTING WITH ASTERISK
  if( $line =~ m|^\*| ) { next; }
  ### SKIP LINES WITH NO COMMAS
  if( $line !~ m|,| ) { next; }
  ### CONVERT CAI FILE CSV LINE TO VARIABLES
  ($id,$alatin,$alonin,$elevin,$type,$namein,$commentin) = split /,/, $line ;
#TESTING
print STDERR "$id,$alatin,$alonin,$elevin,$type,$namein,$commentin \n" ;
  ### IF $id NOT NUMBER, MUST BE END OF FILE
  if( $id !~ m|^[0-9]+$| ) { goto END ; }
  chomp( $commentin );
  ### assume dos file 
  chop( $commentin );
  $name = $namein ;
### REMOVE ANY ENDING "space capitalletter number' FROM NAME
$name =~ s/ +[A-Z]+[0-9]+$// ;
  ### REMOVE ANY SPACES FROM NAME
  $name =~ s/ //g ;
  ### REMOVE ANY ASTERISK FROM NAME
  $name =~ s/\*//g ;

  ### REQUIRE NAME TO BE LESS THAN CHARACTER LIMIT
  if( length $name > $MAXNAMECHARS )
  {
    $name = substr  $name, 0, $MAXNAMECHARS ;
    print STDERR "***WARNING: name $namein truncated to $MAXNAMECHARS chars = $name \n";
  }
  ### CONVERT TO DECIMAL DEG
  $alat = dms2deg( $alatin );
  if( $alatin =~ m|S$| )  { $alat = -1.*$alat; }
  $alon = dms2deg( $alonin );
  if( $alonin =~ m|W$| )  { $alon = -1.*$alon; }
  ### ADD OFFSET IN FT TO ELEV THEN CONVERT TO METERS
  if( defined $elevin )
  {
    ( $feet = $elevin ) =~ s/[Ff]// ;
    $altm = 0.3048 * ( $ALTOFFSETFT + $feet ) ;
    ### ADD ELEV TO COMMENT
    $comment = "${feet}ft $commentin" ;
    ### REMOVE AMPERSAND - not a valid garmin character, causes MapSource to reject file
    $comment =~ s|\s+\&\s+|+| ;
    ### REMOVE ANY TRAILING WHITESPACE FROM COMMENT
    $comment =~ s|\s+$|| ;
  }
  ### CONVERT TYPE TO SYMBOL
  ### Determines airport/non-airport locations based on CAI file indication but may not include all possibilities
  if( $type =~ m|A| || $type =~ m|L| ) 
#old  if( $type eq 'TA' || $type eq 'TL' || $type eq 'A' || $type eq 'AT' || $type eq 'TAHSF' || $type eq 'TASFH' || $type eq 'ATF' ) 
    {  $symbol = 'Airport' ; }
  else
#old  elsif( $type eq 'T' || $type eq 'TL' || $type eq 'TS' ) 
    {  $symbol = 'Glider Area' ; }          # gives glider symbol in MapSource, HG symbol in gpsmap60
    #alt   {  $symbol = 'Man Overboard' ; } # gives red/orange square symbol in MapSource & gpsmap60
    #old   {  $symbol = 'Dot' ; }           # gives black square symbol in MapSource, blue flag symbol in gpsmap60
#old  else
#old    {  die "UNKNOWN TYPE FOR $id $name = $type" ; }

  ### TREAT SPECIAL CASES
  ### treat Drop Zone
  if( $name eq 'DZ' ) { $symbol = "Parachute Area"; }

  ### WRITE OUTPUT
  printf "\r\n" ;
  printf "  <wpt lat=\"%.9f\" lon=\"%.9f\">\r\n",$alat,$alon;
  if( defined $elevin )
  {
    printf "    <ele>%.6f</ele>\r\n", $altm ;
  }
  ### *NB* THE ORDER OF THESE STATEMENTS APPEARS SIGNIFICANT TO GET MapSource READABLE FILE !?
  printf "    <time>2007-07-06T04:07:31Z</time>\r\n", $datime ;
  printf "    <name>%s</name>\r\n", $name ;
  printf "    <cmt>%s</cmt>\r\n", $comment ;
  printf "    <desc>%s</desc>\r\n", $comment ;
  printf "    <sym>$symbol</sym>\r\n", $symbol ;
  printf "    <extensions>\r\n";
  printf "      <gpxx:WaypointExtension xmlns:gpxx=\"http://www.garmin.com/xmlschemas/GpxExtensions/v3\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensions/v3/GpxExtensionsv3.xsd\">\r\n";
  printf "        <gpxx:DisplayMode>SymbolAndName</gpxx:DisplayMode>\r\n";
  printf "      </gpxx:WaypointExtension>\r\n";
  printf "    </extensions>\r\n";
  printf "  </wpt>\r\n";
  $idlast = $id;
}

### WRITE GPX FILE FOOTER
END:
printf "\r\n";
printf "</gpx>\r\n";
exit ;

### CONVERT DEG:MIN:SEC? INTO DECIMAL DEGREE
sub dms2deg
{
  ( $dms = $_[0] ) =~ s/[NSEWnsew]// ;
  ($dd,$mm,$ss) = split /:/, $dms ;
  if( defined $ss )
  {
    $deg = $dd + ( $mm  + ( $ss / 60. ) ) / 60. ;
  }
  else
  {
    $deg = $dd + $mm / 60. ;
  }
  return $deg ;
}
  
