#!/usr/local/bin/expect --
#
# Change the above pointer as necessary for the expect installation on the
# local machine (i.e., to path output by UNIX/Linux command 'which expect')
#
# Define path to settings input file; can be changed by user as desired. 
#
  set input_file ./obs_tbl_ele.inp
# 
# Automate the Horizons session required to produce an OBSERVER TABLE given
# user-specified heliocentric ecliptic osculating orbital elements.
#
# The latest version of this script (along with other scripts) is available at:
#
#   ftp://ssd.jpl.nasa.gov/pub/ssd/SCRIPTS/obs_tbl_ele
#
# A corrssponding example input file is at:
# 
#   ftp://ssd.jpl.nasa.gov/pub/ssd/SCRIPTS/obs_tbl_ele.inp
#
# Version 1.0                                                    (Expect v5.45)
#
# Modification History:
#
#  DATE         Who  Change
#  -----------  ---  ----------------------------------------------------------
#  2015-Aug-25  JDG  Version 1.0: 
#                     New script variation supporting observer tables from 
#                     osculating element input
#  2015-Sep-23  JDG  Added comments on obliquity and other default constants
#  2016-Feb-04  JDG  Added I/O model specification
#  2017-Mar-13  JDG  Updated "http:" references to "https:"
#  2017-May-31  JDG  Added ANG_RATE_CUTOFF support
#
# Key:
#  JDG= Jon.D.Giorgini@jpl.nasa.gov
#
# BACKGROUND:
# -----------
#
#   This script ("obs_tbl_ele") and companion input file ("obs_tbl_ele.inp") 
# allows a user to type one command on a workstation and produce a numerically
# integrated Horizons ASCII observer-table file on that same machine, based
# on user-supplied J2000 heliocentric ecliptic osculating orbital elements.
#
#   The script offers network transparency by connecting to the JPL Horizons
# ephemeris system, automating the interaction with it, then transferring the 
# file back to the user's local machine using FTP.
#
# COMMAND LINE:
# -------------
#
#   obs_tbl_ele [object_name] [elements] {output_file}
#
# EXPLANATION:
# ------------
#
#   [object_name]
#
#     Name of object. REQUIRED. Not used other than to store in file comments.
#
#   [elements]
#
#     A single quoted string containing Horizons format heliocentric 
#     J2000 ecliptic osculating orbital elements. REQUIRED. 
#
#     Example (see below for detailed discussion):
#       "EPOCH=2449526.5 EC=.6570220840219289 QR=.5559654280797371 TP=2449448.890787227 OM=78.10766874391773 W=77.40198125423228 IN=24.4225258251465 H=21.3 G=0.13"
#
#   {output_file}
#
#     OPTIONAL name to give the output file on your system. If not
#     specified, the command-line [object_name] string is used to assign 
#     a local file name in the current directory using the template:
#               
#     [object_name].txt
# 
# Command-line arguments require quotes if they contain spaces or a semi-colon. 
# The quotes guarantee the whole argument is passed literally to the script 
# without being improperly parsed into components. Instead of remembering this, 
# one could also just enclose all command-line arguments in quotes as routine 
# practice.
#
# Example command-line:
#
# obs_tbl_ele Test "EPOCH=2449526.5 EC=.6570220840219289 QR=.5559654280797371 TP= 2449448.890787227 OM=78.10766874391773 W=77.40198125423228 IN=24.4225258251465 H=21.3 G=0.13" test_out.txt
#
# As far as the small-body OSCULATING HELIOCENTRIC ECLIPTIC elements and other 
# parameters the can be specified ... specification string format is:   
# 
#          LABEL= VALUE LABEL= VALUE ...
#
# ... where acceptable label strings are defined as follows:
#
#       EPOCH ....  Julian ephemeris date (TDB) of osculating elements
#       EC .......  Eccentricity
#       QR .......  Perihelion distance in (au)
#       TP .......  Perihelion Julian date
#       OM .......  Longitude of ascending node (degrees) wrt ecliptic
#       W ........  Argument of perihelion (degrees) wrt ecliptic
#       IN .......  Inclination (degrees) wrt ecliptic
#
# Instead of {TP, QR}, {MA, A} or {MA,N} may be specified (not both):
#       MA .......  Mean anomaly (degrees)
#       A ........  Semi-major axis (au)
#       N ........  Mean motion (deg/day)
#
# Note that if you specify elements using MA, {TP, QR} will be derived from
# them; the program always uses TP and QR internally.
#
# OPTIONAL INPUTS
#
#       RAD ......  Object radius (km)
#       PENAM ....  Planetary ephemeris of input orbital elements. Can be 
#                    'DE405' or 'DE431'. If not specified, DE431 is assumed.
#       SRC ......  JPL square root covariance vector. Vector-stored upper-
#                     triangular matrix with order {EC,QR,TP,OM,W,IN,{ESTL}}
#
#  For asteroids, additional OPTIONAL parameters can be given:
#       H ........  Absolute magnitude parameter (asteroid)
#       G ........  Magnitude slope parameter; can be < 0 (asteroid)
#
#  For comets, additional OPTIONAL parameters can be given:
#       M1 ........ Total absolute magnitude (comet)
#       M2 ........ Nuclear absolute magnitude (comet)
#       K1 ........ Total magnitude scaling factor (comet)
#       K2 ........ Nuclear magnitude scaling factor (comet)
#       PHCOF ..... Phase coefficient for k2=5 (comet)
#
#  Non-gravitational models (allowed for comets OR asteroids)
#       A1 ........ Radial non-grav accel (comet), au/d^2
#       A2 ........ Transverse non-grav accel (comet),  au/d^2
#       A3 ........ Normal non-grav accel (comet), au/d^2
#       R0 ........ Non-grav. model constant, normalizing distance, au  [2.808]
#       ALN ....... Non-grav. model constant, normalizing factor [0.1112620426]
#       NM ........ Non-grav. model constant, exponent m                 [2.15]
#       NN ........ Non-grav. model constant, exponent n                [5.093]
#       NK ........ Non-grav. model constant, exponent k               [4.6142]
#       DT ........ Non-grav lag/delay parameter (comets only), days
#
#       AMRAT ..... Solar pressure model, area/mass ratio, m^2/kg
#
# Check current Horizons system documentation for any future changes in the
# above list:  https://ssd.jpl.nasa.gov/?horizons_doc#user-specified
#
# If the SRC is not provided, no statistical uncertainty related quantities 
# will be output.
#
# If {R0,ALN,NM,NN,NK} are not specified, but the non-gravitational model is 
# activated with a non-zero A1, A2, or A3, the default bracketed values shown 
# above are assumed, as described in "Cometary Orbit Determination and 
# Nongravitational Forces", in Comets II, University of Arizona Press (2004),
# pp. 137-151.
#
# For the AMRAT solar pressure model, total absorption is assumed. Scale the
# value to account for reflectivity. For example, if 15% of light is reflected, 
# specify a value for AMRAT for which the actual value is multiplied by 1.15.
#
# For J2000 heliocentric ecliptic elements, an obliquity angle (epsilon) of 
# 84381.448 arcsec (IAU76) is used to transform to the equatorial system of 
# the planetary ephemeris (PENAM) and obtain the initial state for numerical 
# integration. 
#
# There are slightly different values of epsilon in different sets of constants, 
# but this value is used by Horizons. Convert your input elements as necessary 
# to match the ecliptic definition expected by Horizons to avoid loss of 
# accuracy.
#
# For PENAMs of at least DE430 and later, 1 au is taken to be 149597870.70 km,
# by IAU standard.
#
# SCRIPT REQUIREMENTS
# -------------------
#
# #1) This script looks for an input file whose pathname is defined above
# in the variable "input_file". This input file contains minimal control
# settings to define the Horizons request:
#
# Example input file:
#
#  set   EMAIL_ADDR           " "
#  set   CENTER               "@spitzer"
#  set   START_TIME           "2004-Jan-1 12:00"
#  set   STOP_TIME            "2004-Mar-7" 
#  set   STEP_SIZE            "1d"
#  set   QUANTITIES           "1,4,9,8"
#
# #2) "obs_tbl_ele" is written in the Expect automation language. The Expect
# interpretation program must be present on any computer system before 
# "obs_tbl_ele" can run.
#
#   The Expect language is available for download at the URL:
#
#                      http://expect.nist.gov/
#
#   Expect is an extension of the Tcl language, which is also required.
# The web site provides appropriate links. Installation procedures are
# provided and all packages can typically be installed and their self-check
# tests completed in about 45 minutes by following the directions.
#
#   Expect is primarily supported on UNIX platforms, but versions for other 
# systems do exist. A useful book on the language is "Exploring Expect" by
# Don Libes (ISBN 1-56592-090-2)
# 
#   Once the Expect language is installed on your machine, you may need to
# alter the very first line of this script ("#!/usr/local/bin/expect") to
# point to the installation location on that machine.  The script will then
# be able to execute.
#
#   The user's machine must be able to resolve Internet domain names and
# support internet FTP.
#
# USAGE
# -----
#
# This script will handle typical errors and respond with an indicator 
# message, but is not necessarily bullet-proof, being intended mostly as 
# a functional example users can customize.
#
# See Horizons documentations for additional information on that program's
# operation.
#
# It could take a few seconds or longer to generate and transfer the table, 
# depending on how much numerical integration is required and the network 
# state. 
#
# The script returns a standard exit status code (0=OK, 1=FAILED) which 
# can be checked by calling programs. 
#
#------------------------------------------------------------------------------
# Establish defaults & turn debugging on or off
#
  exp_internal 0   ; # Diagnostic output: 1= on, 0=off
  set timeout  15  ; # Set timeout to prevent infinite hang if network down
  remove_nulls 0   ; # Disable null removal from Horizons output
#
# Turn off output mirroring (set quiet 0; set quiet 1 to observe process)
#
  set quiet 0
  log_user $quiet
#
# Initialize local script variables
  set start_flag 0
# 
# Set Horizons server constants
#
  set horizons_server   ssd.jpl.nasa.gov
  set horizons_ftp_dir  pub/ssd/
#
# Process script command-line values; check for input problems
#
  set argc [llength $argv]
  if {$argc < 2} {
   puts "\nMissing argument. Usage:"
   puts { obs_tbl_ele [object_name] [elements] {output_file} }
   puts " "
   puts "Example --"
   puts { obs_tbl_ele "test" "EPOCH=... EC=..." test_out.txt}
   puts " "
   exit 1 
} elseif {$argc > 3} {
   puts "\nToo many arguments. May need to use quotes.  Usage:"
   puts { obs_tbl_ele [object_name] [elements] {output_file} }
   puts " "
   puts "Example --"
   puts { obs_tbl_ele "test" "EPOCH=... EC=..." test_out.txt}
   puts " "
   exit 1 
} elseif {$argc == 2} {
   set local_file [join [lindex $argv 0] ""]
   set local_file [string trim $local_file]
   set local_file [string trimright $local_file ";"].txt
} elseif {$argc == 3} {
   set local_file [lindex $argv 2]
  }
#
# Initialize output table default over-rides to null
#
  set REF_SYSTEM           "" 
  set TIME_ZONE            ""
  set CAL_FORMAT           ""
  set TIME_DIGITS          "" 
  set ANG_FORMAT           ""
  set EXTRA_PREC           ""
  set APPARENT             ""
  set RANGE_UNITS          ""
  set SUPPRESS_RANGE_RATE  ""
  set ELEV_CUT             ""
  set AIRMASS              ""
  set R_T_S_ONLY           ""
  set SKIP_DAYLT           ""
  set SOLAR_ELONG          ""
  set LHA_CUTOFF           ""
  set ANG_RAT_CUTOFF       ""
  set CSV_FORMAT           ""
#
# Load script's observer-table run-control settings file 
#
  if [ file exists $input_file ] {
   source $input_file 
} else {
   puts "Missing input file $input_file"
   exit 1
  }
#
# Create concatenated string of default over-rides
#
 set DEFAULTS ""
 set DEFAULTS "$DEFAULTS$REF_SYSTEM$TIME_ZONE$CAL_FORMAT$TIME_DIGITS"
 set DEFAULTS "$DEFAULTS$ANG_FORMAT$EXTRA_PREC$APPARENT$RANGE_UNITS"  
 set DEFAULTS "$DEFAULTS$SUPPRESS_RANGE_RATE$ELEV_CUT$AIRMASS$R_T_S_ONLY"
 set DEFAULTS "$DEFAULTS$SKIP_DAYLT$SOLAR_ELONG$LHA_CUTOFF"
 set DEFAULTS "$DEFAULTS$ANG_RATE_CUTOFF$CSV_FORMAT"
#
# Connect to Horizons 
#
  spawn telnet $horizons_server 6775
#
# Get main Horizons prompt and proceed 
#
  expect { 
   timeout        {puts "No response from $horizons_server"; exit 1} 
   "unknown host" {puts "This system cannot find $horizons_server"; exit 1}
   "Horizons> "   {send PAGE\r} }
  expect { 
    timeout       {puts "\r\rHorizons timed out (LEVEL=1). Try later or notify JPL." ; send x\r;  exit 1} 
   "Horizons> "   {send "##2\r"} }
  expect { 
   timeout        {puts "Horizons timed out (LEVEL=2). Try later or notify JPL."; send x\r; exit 1} 
   "Horizons> "   {send ";\r"} }
#
# Wait for element input prompt, send elements
#
  expect { 
    timeout       {puts "Horizons timed out (LEVEL=3). Try later or notify JPL."; send x\r; exit 1} 
    -re ".*: $"   {send [lindex $argv 1]\r\r }
   }
#
# Process any errors from element input and specify reference frame
#
  expect {
    timeout       {puts "Horizons timed out (LEVEL=4). Try later or notify JPL."; send x\r; exit 1} 
    -re "INPUT ERROR.*\r" {
      puts "Horizons encountered an input error and halted --"
      puts " "
      puts $expect_out(0,string)
      send q\r
      exit 1}
    -re "Error.*\r" {
      puts "Horizons encountered an input error and halted -- "
      puts " "
      puts $expect_out(0,string)
      send q\r
      exit 1}
    -re ".*Ecliptic frame of input.*: $" { send J2000\r }
   }
#
# Wait for input prompt, send object_name
#
  expect { 
    timeout        {puts "Horizons timed out (LEVEL=5). Try later or notify JPL."; send x\r; exit 1} 
    -re ".*Optional name of object.*: $" {send [lindex $argv 0]\r }
   }
#
# Request ephemeris
#
  expect { 
   -re ".*Select.*: $" {
        send E\r }
     } 
#
# Request observer table ephemeris type
#
  expect { 
   -re ".*Observe, Elements.*: $" {
        send O\r }
     } 
#
# Provide coordinate/observing center
#
  expect {
    -re ".*Coordinate center.*: $" {
        send -- $CENTER\r }
     }
#
# Handle coordinate/observing center error or confirmation 
#
  expect {
    -re ".*Cannot find central body.*: $" {
        send X\r  
        puts "\nCannot find CENTER given in $input_file (no match): CENTER= '$CENTER'"
        puts " "
        exit 1 }
    -re ".*Select.*<cr>: $" {
        send X\r  
        puts "\nNon-unique CENTER in $input_file (multiple matches): CENTER= '$CENTER'"
        puts " "
        exit 1 }
    -re ".*Coordinate center.*<cr>: $" {
        send X\r  
        puts "\nNon-unique CENTER in $input_file (multiple matches): CENTER= '$CENTER'"
        puts " "
        exit 1 }
    -re ".*Confirm selected.*> " {
        send Y\r }
    -re ".*Starting UT.*: $" {
        set start_flag 1
        send $START_TIME\r }
     }
#
# Specify start date 
#
  if { $start_flag < 1 } {
   expect {
     -re ".*Starting UT.*: $" {
         send $START_TIME\r }
      }
  }
#
# Handle start date error OR specify STOP date
#
  expect {
    -re ".*Cannot interpret.*: $" {
       send X\r
       puts "\nError in $input_file date format: START_TIME= '$START_TIME'"
       puts "See Horizons documentation for acceptable format."
       puts " "
       exit 1 }
    -re ".*No ephemeris.*: $" {
       send X\r
       puts "\nInput file $input_file START_TIME= '$START_TIME' prior to available ephemeris"
       puts " "
       exit 1 }
    -re ".*Ending.*: $" {
       send $STOP_TIME\r }
     }
#
# Handle stop date error OR specify step size
#
  expect {
    -re ".*Cannot interpret.*" {
       send X\r
       puts "\nError in $input_file date format: STOP_TIME= '$STOP_TIME'"
       puts "See Horizons documentation for acceptable format."
       puts " "
       exit 1 }
    -re ".*No ephemeris.*" {
       send X\r
       puts "\nInput file $input_file STOP_TIME= '$STOP_TIME' date beyond available ephemeris."
       puts " "
       exit 1 }
    -re ".*Output interval.*: $" {
       send $STEP_SIZE\r }
   }
#
# Handle step-size error or specify output QUANTITIES
#
  expect { 
   -re ".*Unknown.*\n" {
       send X\r
       puts [string trimright "$expect_out(0,string)" "\n"]
       puts "Input file $input_file STEP_SIZE= '$STEP_SIZE' error."
       exit 1 }
   -re "Projected.*\n" {
       puts [string trimright "$expect_out(0,string)" "\n"]
       puts "Input file $input_file STEP_SIZE= '$STEP_SIZE' error (or date range too big)."
       send X\r
       exit 1 }
   -re ".*Accept default.*: $" {
       if { [string length $DEFAULTS] > 0 } {
         send N\r 
        } else {
         send Y\r
        }
       }
    } 
#
# Set output quantities
#
  expect {
   -re ".*Select table quantities.*: $" {
       send $QUANTITIES\r }
   }
#
# Get errors on input QUANTITIES, or start through rest of default settings, 
# or match FTP request if no default changes
#
  expect {
    -re ".*Unknown quantity requested.*" {
        send X\r
        puts "\nUnknown value in $input_file QUANTITIES list."
        puts " "
        exit 1 }
    -re ".*Must specify QUANTITIES to output.*" {
        send X\r
        puts "\nMust assign non-null value in $input_file QUANTITIES list."
        puts " "
        exit 1 }
    -re ".*Select.*: $" {
        send F\r }
    -re ".*] : $" {
        send \r }
   }
#
# Change defaults if requested 
#
  if { [string length $DEFAULTS] > 0 } {
   while 1 {
    expect { 
     -re "(Cannot interpret.*\r)" {
        send X\r
        puts " "
        puts "Error in $input_file"
        puts "$expect_out(0,string)" 
        puts "See Horizons documentation for acceptable values."
        puts " "
        exit 1 }
     -re ".*frame.*] : $"               { send $REF_SYSTEM\r          }
     -re ".*zone.*] : $"                { send $TIME_ZONE\r           }
     -re ".*time format.*] : $"         { send $CAL_FORMAT\r          }
     -re ".*time digits.*] : $"         { send $TIME_DIGITS\r         }
     -re ".*R.A. format.*] : $"         { send $ANG_FORMAT\r          }
     -re ".*high precision.*] : $"      { send $EXTRA_PREC\r          }
     -re ".*APPARENT.*] : $"            { send $APPARENT\r            }
     -re ".*units for RANGE.*] : $"     { send $RANGE_UNITS\r         }
     -re ".*Suppress RANGE_RATE.*] : $" { send $SUPPRESS_RANGE_RATE\r }
     -re ".*Minimum elevation.*] : $"   { send $ELEV_CUT\r            }
     -re ".*Maximum air.*] : $"         { send $AIRMASS\r             }
     -re ".*rise-transit-set.*] : $"    { send $R_T_S_ONLY\r          }
     -re ".*daylight.*] : $"            { send $SKIP_DAYLT\r          }
     -re ".*elongation cut-off.*] : $"  { send $SOLAR_ELONG\r         }
     -re ".*ngle cut-off.*] : $"        { send $LHA_CUTOFF\r          }
     -re ".*ngular rate cut-off.*] : $" { send $ANG_RATE_CUTOFF\r     }
     -re ".*CSV format.*] : $"          { send $CSV_FORMAT\r          }
     -re ".*] : $"                      { send \r                     } ;# Skip unknown (new?) prompt
     -re ".*Select.*: $"                break                           ;# Done with default overrides
    }
   }
  }
#
# Observer-table output has been generated. Now sitting at post-output prompt. 
# Initiate FTP file transfer.
#
  send F\r 
#
# Pick out FTP file name
#
  expect {
   -re "File name   : (.*)\r\r\n   File type" {
       set ftp_name $expect_out(1,string) }
       send "X\r"
   }
#
# Retrieve file by anonymous FTP
#
  set timeout 15
  spawn ftp $horizons_server
  expect {
     timeout {puts "Cancelled -- FTP server not responding."; exit 1 }
     -re "Name.*: $"
   } 
  send "anonymous\r"
  expect "Password:"
#
# Next bit supports HP-UNIX work-around
#
  set oldpw $EMAIL_ADDR
  if [regsub @ $oldpw '\134@' pw] {
    set newpw $pw
  } else {
    set newpw $oldpw
  }
  send $newpw\r
#
# Process FTP connection errors OR transfer output file to local machine
#
  expect {
    "Login failed." { 
       send "quit\r" 
       puts "\nFTP login failed -- must use full Internet e-mail address."
       puts "Example:  'joe@your.domain.name'"
       puts " "
       exit 1 }
    "ftp> " { send ascii\r    } 
   }
  expect "ftp> " { send "cd pub/ssd\r" }
#
# set timeout -1   ;# This would disable timeout during actual transfer
#
  expect "ftp> " { send "get $ftp_name $local_file\r" }
  expect {
     -re ".*No such.*" {
       puts "\nError -- cannot find $ftp_name on server."
       puts " "
       exit 1 }
     "ftp> " { send "quit\r" }
   }
#
# Finished, set status code 
#
  exit 0
