package nxm.sys.libg;

import java.awt.Color;

import nxm.sys.inc.Constants;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Position;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StateVector;
import nxm.sys.lib.Table;
import nxm.sys.lib.Transform;

/**
  A class to hold and plot a geo event. This class is used to compute and draw
  Time Difference of Arrival (TDOA) and Frequency Difference of Arrival (FDOA)
  lines, to show horizon lines and generally hold geolocation information.

  @author Jeff Schoen
  @author Jay Hallam
  @author Neon Ngo
*/
public class LayerGeo extends Layer implements Constants {

  /** Currently changing site mode has no affect */
  public static final String sitemodeList = "Current,Offset,Fixed";
  /** Currently changing state vector mode has no affect */
  public static final String svecmodeList = sitemodeList;
  /** Site or state vector mode is current */
  public static final int CURRENT=1;
  /** Site or state vector mode is offset */
  public static final int OFFSET=2;
  /** Site or state vector mode is fixed */
  public static final int FIXED=3;

  /** The list of options for where the prediction reference (see {@link #setPredictReference(String)}.  Choose
   one of n. */
  public static final String predictReferenceList = "SITE,VEH";
  private int PREDICT_REF_SITE=1, PREDICT_REF_VEH=2;
  private int predictReference = PREDICT_REF_SITE;

  /** The list of options for drawing TDOA lines.  Options include Line,
      Contour, Region, or NONE. Use a (<code>|</code>) separated list of
      these names to set the desired flags (see {@link #setTdoaMode(String)}).
  */
  public static final String tdoamodeList = "Line,Contour,Region,None";

  /** The list of options for drawing FDOA lines.  Options include Line,
      Contour, Region, or NONE. Use a (<code>|</code>) separated list of
      these names to set the desired flags (see {@link #setFdoaMode(String)}).
  */
  public static final String fdoamodeList = tdoamodeList;
  /** Draw the TDOA or FDOA as a line (see {@link #tdoamodeList}), (see {@link #fdoamodeList}) */
  public static final int LINE=1;
  /** Draw the TDOA or FDOA as a line (see {@link #tdoamodeList}), (see {@link #fdoamodeList}) */
  public static final int CONTOUR=2;
  /** Draw the TDOA or FDOA as a line (see {@link #tdoamodeList}), (see {@link #fdoamodeList}) */
  public static final int REGION=3;
  /** Do not draw the TDOA or FDOA lines (see {@link #tdoamodeList}), (see {@link #fdoamodeList}) */
  public static final int NONE=4;

  /** The list of flag options. Use a | separated list of these names to set the desired flags. */
  public static final String flagsList =
          "LightTimeCorr,DownLink,Horizon,Markers,AutoTFdoa,ReadOut,FootPrint,HorizonAlt";
  /** Light Time Correlation. Default is on (currently not changeable). (see {@link #flagsList}) */
  public static final int LTC=0x1;
  /** Enable downlink. Default is on (currently not changeable). (see {@link #flagsList}) */
  public static final int DOWNLINK=0x2;
  /** Show horizon lines if enabled. Default is on. (see {@link #flagsList})  */
  public static final int HORIZON=0x4;
  /** Show target, site and state vector markers if enabled Default is on. (see {@link #flagsList}) */
  public static final int MARKERS=0x8;
  /** Auto compute the TDOA/FDOA lines. Default is on. (see {@link #flagsList}) */
  public static final int AUTOTF=0x10;
  /** Display the readout if enabled. (see {@link #flagsList}) */
  public static final int READOUT=0x20;
  /** Show the footprint at the target if enabled. (see {@link #flagsList}) */
  public static final int FOOTPRINT=0x40;
  /** Show horizon at altitude lines if enabled and altitidue greater than 0. Default is off. (see {@link #flagsList})  */
  @InternalUseOnly
  public static final int HORIZONALT=0x80;

  /* This was the original color used by most items */
  private static final Color T5_COLOR = MColor.getColor(20,100,20);
  /** Default site point and label color */
  public static final Color DEFAULT_SITE_COLOR = T5_COLOR;
  /** Default target point and label color */
  public static final Color DEFAULT_TARGET_COLOR = T5_COLOR;
  /** Default horizon line color */
  public static final Color DEFAULT_HORIZON_COLOR = MColor.getColor(0,60,0);
  /** Default horizon at altitude line color */
  @InternalUseOnly
  public static final Color DEFAULT_HORIZONALT_COLOR = Color.BLUE;
  /** Default FOOTPRINT line color */
  public static final Color DEFAULT_FOOTPRINT_COLOR = T5_COLOR;
  /** Default TDOA line color */
  public static final Color DEFAULT_TDOA_COLOR = MColor.getColor(80,0,0);
  /** Default FDOA line color */
  public static final Color DEFAULT_FDOA_COLOR = MColor.getColor(80,80,0);
  /** Default SVA point and label color */
  public static final Color DEFAULT_SVA_COLOR  = T5_COLOR;
  /** Default SVB point and label color */
  public static final Color DEFAULT_SVB_COLOR  = DEFAULT_SVA_COLOR;
  /** Default X Grid Size 1.5 ratio (x/y) */
  public static final int DEFAULT_X_GRID_SIZE  = 96;
  /** Default Y Grid Size 1.5 ratio (x/y) */
  public static final int DEFAULT_Y_GRID_SIZE  = 64;

  private int flags = LTC+DOWNLINK+HORIZON+MARKERS+AUTOTF; /* Flags setting from the flags list */
  private boolean drawConeToTarget = true; /* If true draw the footprints at the target,
                                              otherwise draw footprints at the pointing position */
  private int sitemode = CURRENT;  /* The site mode: current, offset or fixed */
  private int svecmode = CURRENT;  /* The state vector mode: current, offset or fixed */
  private int tdoamode = LINE;     /* The TDOA mode: Line,Contour,Region,None */
  private int fdoamode = LINE;     /* The FDOA mode: Line,Contour,Region,None */
  private Position pos;            /* Position of target */
  private Position siteA;          /* Position of site A */
  private Position siteB;          /* Position of site B, optional, only use when non-NULL */
  private Position pointPosA;      /* Pointing position of vehicle A */
  private Position pointPosB;      /* Pointing position of vehicle B */
  private StateVector sva;         /* State vectors for vehicle a  */
  private StateVector svb;         /* State vectors for vehicle b */
  private String svaLabel = "A";   /* The position label for vehicle A */
  private String svbLabel = "B";   /* The position label for vehicle B */
  private String siteLabelA = "SITE";   /* The position label for site A or only site. */
  private String siteLabelB = "SITEB";  /* The position label for site B */
  private String targetLabel = "T";/* The position label for target */
  private double cep=0;            /* The circular error probability or CEP */
  private double tdoa=0,dtdoa=-20; /* The time difference of arrival (TDOA) and delta TDOA */
  private double fdoa=0,dfdoa=-20; /* The frequency difference of arrival (FDOA) and delta FDOA */
  private double flinkt=1e6;       /* Link frequency */
  private double flinka=1e6;       /* Link frequency */
  private double flinkb=1e6;       /* Link frequency */
  private int conic=5;             /* The number of conics displayed when conics is enabled */
  private int cdiv=60;             /* The conic division */
  private double cangleA=1;        /* The conic angle for A */
  private double cangleB=cangleA;  /* The conic angle for A */
  private double crange=-1;        /* The conic range */
  private double horizonAlt=0;     /* The altitude to base horizon on */
  private Color colorTarget     = DEFAULT_TARGET_COLOR;     /* Target point and label color */
  private Color colorSiteA      = DEFAULT_SITE_COLOR;       /* Site A point and label color */
  private Color colorSiteB      = DEFAULT_SITE_COLOR;       /* Site B point and label color */
  private Color colorFootPrintA = DEFAULT_FOOTPRINT_COLOR;  /* Footprint line A color */
  private Color colorFootPrintB = DEFAULT_FOOTPRINT_COLOR;  /* Footprint line B color */
  private Color colorHorizonA   = DEFAULT_HORIZON_COLOR;    /* Horizon line A color */
  private Color colorHorizonB   = DEFAULT_HORIZON_COLOR;    /* Horizon line B color */
  private Color colorHznAtAltA  = DEFAULT_HORIZONALT_COLOR; /* Horizon line A color */
  private Color colorHznAtAltB  = DEFAULT_HORIZONALT_COLOR; /* Horizon line B color */
  private Color colorTDOA       = DEFAULT_TDOA_COLOR;       /* TDOA line color */
  private Color colorFDOA       = DEFAULT_FDOA_COLOR;       /* FDOA line color */
  private Color colorSVA        = DEFAULT_SVA_COLOR;        /* SVA point and label color */
  private Color colorSVB        = DEFAULT_SVB_COLOR;        /* SVB point and label color */
  private double[] tdoabuf,fdoabuf; /* The TDOA and FDOA buffers */
  private double[] aoaabuf,aoabbuf; /* The angle of arrival buffers */
//  private float[] cepbuf; /** TBD Unused */
  private MPoint pix;
  private double tmin,fmin,tmax,fmax;
  /** geodetic view value */
  private double gx1,gxd,gx2,gy1,gyd,gy2;
  /** predicted */
  private double pgx1=0,pgx2=0,pgy1=0,pgy2=0;
  private ContourHighPrecision contour = new ContourHighPrecision();
  /** Use method with traditional ray intersect sphere vs using more viaual math approach */
  private static boolean horizonAltTraditionalCode = false;

  /* Constructors */
  /** LayerGeo Default Constructor */
  public LayerGeo () {
    name = "GEO";
    colors = MColor.getColorMap(MColor.T5,-1);
    findRange();
    setGridSize(DEFAULT_X_GRID_SIZE, DEFAULT_Y_GRID_SIZE); // 2.7.2 and prior defaulted to (25,13)
    pix  = new MPoint();
    pos  = new Position(Position.GEODETIC);
    siteA = new Position(Position.GEODETIC);
    siteB = null; // Site B is optional, only use when non-NULL
    pointPosA = new Position(Position.GEODETIC);
    pointPosB = new Position(Position.GEODETIC);
    sva  = new StateVector();
    svb  = new StateVector();
    enable &= ~MPlot.EN_BSTORE;
    targetLabel = name; // For backward compatibility, use layer name for target label
  }

  @Override
  public void findRange () {
    // Set Minimum and Maximum x and y axis values
    x1=-180; x2=+180; y1=-90; y2=+90;
    // Set Minimum and Maximum actual auto-scale value (since 3.7.0 for NXM-2984)
    a1 = y1;
    a2 = y2;
  }

  public void process () {
    Position tpos = new Position();
    Predict pre = new Predict();
    if ((flags&AUTOTF)!=0) {
      pos.geo2car();
      predict(pos,pre);
      tdoa = pre.tdoa;
      fdoa = pre.fdoa;
    }
    tmin=fmin=+1e20;
    tmax=fmax=-1e20;
    gxd = (gx2-gx1)/(frame-1);
    gyd = (gy2-gy1)/(size-1);
    for (int i=0,iy=0; iy<size; iy++) {
      tpos.lat = gy1 + iy*gyd;
      for (int ix=0; ix<frame; ix++) {
        tpos.lon = gx1 + ix*gxd;
        tpos.geo2car();
        predict(tpos,pre);
        if (pre.tdoa>tmax) tmax=pre.tdoa;
        if (pre.tdoa<tmin) tmin=pre.tdoa;
        if (pre.fdoa>fmax) fmax=pre.fdoa;
        if (pre.fdoa<fmin) fmin=pre.fdoa;
        tdoabuf[i] = pre.tdoa;
        fdoabuf[i] = pre.fdoa;
        aoaabuf[i] = pre.aoaa;
        aoabbuf[i] = pre.aoab;
        i++;
      }
    }
    pgx1=gx1; pgy1=gy1; pgx2=gx2; pgy2=gy2;
  }

  /** draw the layer on the specified plot */
  public void draw (int flag) {
    boolean redo = needRefresh;
    super.draw(flag);
    // find viewable region intersected with gridable region
    double[] box;
    if (MP.view==MPlot.V_LATLONCONTINUOUS) { // Continuous Lat Lon mode added in 3.9.0 for NXM-3284
      box = MP.getXYGrid(MP.getOuterXMin(),MP.getOuterXMax(),y1,y2);
    }
    else {
      box = MP.getXYGrid(x1,x2,y1,y2);
    }
    gx1=box[0]; gx2=box[1]; gy1=box[2]; gy2=box[3];

    if (gx2<=gx1 || gy2<=gy1) return;
    if (gx1!=pgx1 || gx2!=pgx2 || gy1!=pgy1 || gy2!=pgy2) redo=true;
    if (redo) process();

    Line lin = line.copy();

    contour.setXAxis(gx1,gxd,frame);
    contour.setYAxis(gy1,gyd,size);

    if ((flags&HORIZON)!=0) {
      drawContour(lin, 0.0, colorHorizonA, aoaabuf);
      // TODO: When we support HORIZONALT without HORIZON we will need to ensure
      // contour.out, containing horizon pts., is populated. Example code below:
      //       contour.setZAxis(0,0,1);
      //       contour.process(sva);
      if (((flags&HORIZONALT)!=0) && (horizonAlt > 0)) drawHznAtAlt(colorHznAtAltA, sva);
      drawContour(lin, 0.0, colorHorizonB, aoabbuf);
      if (((flags&HORIZONALT)!=0) && (horizonAlt > 0)) drawHznAtAlt(colorHznAtAltB, svb);
    }

    double d,dd; int nd;
         if (tdoamode==LINE)    { dd=1;     d=tdoa;      nd=1; }
    else if (tdoamode==REGION)  { dd=dtdoa; d=tdoa-dd/2; nd=2; }
    else if (tdoamode!=CONTOUR) { dd=1;     d=1; nd=0; }
    else if (dtdoa>0)           { dd=dtdoa; d=dd*Math.ceil(tmin/dd); nd=(int)((tmax-d)/dd); }
    else {
      // A negative Delta TDOA indicates that ABS(dtdoa) lines are to be drawn.
      dd=(tmax-tmin+1e-10)/Math.max(2,-dtdoa);
      d=dd*Math.ceil(tmin/dd);
      nd=(int)(-dtdoa);
    }
    for (int n=0; n<nd; n++,d+=dd) drawContour(lin, d, colorTDOA, tdoabuf);

         if (fdoamode==LINE)    { dd=1;     d=fdoa;      nd=1; }
    else if (fdoamode==REGION)  { dd=dfdoa; d=fdoa-dd/2; nd=2; }
    else if (fdoamode!=CONTOUR) { dd=1;     d=1;         nd=0; }
    else if (dfdoa>0)           { dd=dfdoa; d=dd*Math.ceil(fmin/dd); nd=(int)((fmax-d)/dd); }
    else {
      // A negative Delta FDOA indicates that ABS(dfdoa) lines are to be drawn.
      dd=(fmax-fmin+1e-10)/Math.max(2,-dfdoa);
      d=dd*Math.ceil(fmin/dd);
      nd=(int)(-dfdoa);
    }
    for (int n=0; n<nd; n++,d+=dd) drawContour(lin, d, colorFDOA, fdoabuf);

    if ((flags&FOOTPRINT)!=0 && conic>0) {
      if (drawConeToTarget) {
        lin.setColor(colorFootPrintA);
        MP.drawCone(pos,sva,conic,cdiv,cangleA,crange,lin,true); // 3.9.1 enable high zoom code
        lin.setColor(colorFootPrintB);
        MP.drawCone(pos,svb,conic,cdiv,cangleB,crange,lin,true);
      } else {
        lin.setColor(colorFootPrintA);
        MP.drawCone(pointPosA,sva,conic,cdiv,cangleA,crange,lin,true);
        lin.setColor(colorFootPrintB);
        MP.drawCone(pointPosB,svb,conic,cdiv,cangleB,crange,lin,true);
      }
    }
    if ((flags&MARKERS)!=0) {
      MP.setColor(colorTarget);
      if (MP.getPoint(pos,pix)>0)  drawPoint(lin,pix,targetLabel);

      MP.setColor(colorSiteA);
      if (MP.getPoint(siteA,pix)>0) drawPoint(lin,pix,siteLabelA);
      if (siteB != null) { // Site B is optional
        MP.setColor(colorSiteB);
        if (MP.getPoint(siteB,pix)>0) drawPoint(lin,pix,siteLabelB);
      }


      MP.setColor(colorSVA);
      if (MP.getPoint(sva,pix)>0)  drawPoint(lin,pix,svaLabel);
      MP.setColor(colorSVB);
      if (MP.getPoint(svb,pix)>0)  drawPoint(lin,pix,svbLabel);
      MP.setColor(colors[0]);
    }
  }

  private void drawHznAtAlt (Color hznAtAltColor, StateVector sv) {
    Line lin = line.copy();
    int npts = (int)contour.out[0];
    for (int j=0; j < contour.nout; j+=6+(npts*2)) {
      npts = (int)contour.out[j];
      double[] ecefHznPts = Transform.lonlat2car(contour.out, j+6, npts);
      double[] hznAtAltPts;
      if (horizonAltTraditionalCode) {
        hznAtAltPts = Transform.hzn2AltHzn(ecefHznPts, 0, npts, sv, horizonAlt);
      }
      else { // use more visual Math code
        hznAtAltPts = Transform.horizon2HorizonAtAlt(ecefHznPts, 0, npts, horizonAlt,sv);
      }
      Transform.car2geo(hznAtAltPts, Transform.POS, npts);
      Transform.all2lla(hznAtAltPts, npts);
      lin.setColor(hznAtAltColor);
      MP.drawLine(hznAtAltPts,0,3,npts,lin, Pix.KEEP | Pix.CLIP,  true);
    }
  }

  private void drawPoint (Line lin, MPoint pix, String label) {
    if (MP.gc==null) return;
    if (pix.x<MP.ix1 || pix.x>MP.ix2) return;
    if (pix.y<MP.iy1 || pix.y>MP.iy2) return;
    int r = lin.getRadius();
    MP.gc.drawLine(pix.x-r,pix.y-r,pix.x+r,pix.y+r);
    MP.gc.drawLine(pix.x-r,pix.y+r,pix.x+r,pix.y-r);
    if (label!=null) MP.gc.drawString(label,pix.x+r,pix.y+r);
  }

  /** Draw a contour line given an array of points
    @param z The z value
    @param ic The color map color number
    @param dbuf The float array of points to be drawn
  */
  private void drawContour (Line lin, double z, int ic, double[] dbuf) {
    Color color = colors[ic];
    drawContour(lin, z, color, dbuf);
  }

  /** Draw a contour line given an array of points
    @param z The z value
    @param color The color of line to be drawn
    @param dbuf The float array of points to be drawn
  */
  private void drawContour (Line lin, double z, Color color, double[] dbuf) {
    int j,k,npts,ncol;
    double[] dout;
    lin.setColor(color);
    contour.setZAxis(z,z,1);
    contour.process(dbuf);
    dout = contour.out;
    for (j=0; j<contour.nout && !MP.cancel; j=k) {
      npts = (int)dout[j++];
      ncol = (int)dout[j++];
      k = j + (2+npts)*2;
      if (MP.view==MPlot.V_LATLONCONTINUOUS && MP.hasExtendedRange()) {
        // Lat Lon Continuous added in 3.9.0 for NXM-3284
        double value = dout[j++];
        if (value>=MP.rx2 && ((value-MPlot.LONGITUDE_RANGE)<MP.rx1)) continue;
        if (dout[j++]>=MP.ry2) continue;
        value = dout[j++];
        if (value<=MP.rx1 && ((value+360)>MP.rx2)) continue;
        if (dout[j++]<=MP.ry1) continue;
        MP.drawLineContour(dout,j,2,npts,lin); // 3.9.1 NXM-3131 need double precision
      }
      else if (MP.is2D) {
        if (dout[j++]>=MP.rx2) continue;
        if (dout[j++]>=MP.ry2) continue;
        if (dout[j++]<=MP.rx1) continue;
        if (dout[j++]<=MP.ry1) continue;
        MP.drawLineContour(dout,j,2,npts,lin);
      } else {
        MP.drawLineContour(dout,j+4,2,npts,lin);
      }
    }
  }

  /** Predict - JGS - TBD
    @param pos The position
    @param pre The predicted parameters
  */
  public void predict (Position pos, Predict pre) {
    double xta; // x distance between SVA and target
    double yta; // y distance between SVA and target
    double zta; // z distance between SVA and target
    double xtb; // x distance between SVB and target
    double ytb; // y distance between SVB and target
    double ztb; // z distance between SVB and target
    double rta; // distance between SVA and target
    double rtb; // distance between SVB and target
    double rtai; // inverse distance SVA and target
    double rtbi; // inverse distance SVA and target
    double rrta,rrtb;
    double xsa; // x distance between SVA and Site A
    double ysa; // y distance between SVA and Site A
    double zsa; // y distance between SVA and Site A
    double xsb; // x distance between SVB and Site B (or A if B null)
    double ysb; // y distance between SVB and Site B (or A if B null)
    double zsb; // z distance between SVB and Site B (or A if B null)
    double rsa; // distance between SVA and Site A
    double rsb; // x distance between SVB and Site B (or A if B null)
    double rsai; // inverse distance between SVA and Site A
    double rsbi; // x distance between SVB and Site B (or A if B null)
    double rrsa; // SVA rate to target
    double rrsb; // SVB rate to target
    double rti; // inverse of pos<ition> range (i.e. cartesian distance from point to 0,0,0)
    double cci; // inverse of speed of light

    // calc distances/rates to target
    xta=sva.x-pos.x; yta=sva.y-pos.y; zta=sva.z-pos.z;
    xtb=svb.x-pos.x; ytb=svb.y-pos.y; ztb=svb.z-pos.z;
    rta = Math.sqrt(xta*xta + yta*yta + zta*zta); rtai=1/rta;
    rtb = Math.sqrt(xtb*xtb + ytb*ytb + ztb*ztb); rtbi=1/rtb;
    rrta = ( xta*sva.vx + yta*sva.vy + zta*sva.vz ) * rtai;
    rrtb = ( xtb*svb.vx + ytb*svb.vy + ztb*svb.vz ) * rtbi;

    if (predictReference == PREDICT_REF_SITE) {
      // calc distances/rates to site A and site B
      // If single site, use primary site A else if two-site, use secondary site B
      xsa=sva.x-siteA.x; ysa=sva.y-siteA.y; zsa=sva.z-siteA.z;
      if (siteB == null) { xsb=svb.x-siteA.x; ysb=svb.y-siteA.y; zsb=svb.z-siteA.z; } // Single site
      else               { xsb=svb.x-siteB.x; ysb=svb.y-siteB.y; zsb=svb.z-siteB.z; } // Dual site

      rsa = Math.sqrt(xsa*xsa + ysa*ysa + zsa*zsa);
      rsai=1/rsa;
      rsb = Math.sqrt(xsb*xsb + ysb*ysb + zsb*zsb);
      rsbi=1/rsb;
      rrsa = ( xsa*sva.vx + ysa*sva.vy + zsa*sva.vz ) * rsai;
      rrsb = ( xsb*svb.vx + ysb*svb.vy + zsb*svb.vz ) * rsbi;
    } else {
      // Prediction reference is on the vehicle.
      rsa  = 0;
      rsb  = 0;
      rrsa = 0;
      rrsb = 0;
    }
    // calculate tdoa/fdoa
    cci = 1/SPEED_OF_LIGHT;

    pre.tdoa = cci*( rtb - rta + rsb - rsa );
    pre.fdoa = -cci*( flinkt*(rrtb-rrta) + flinkb*rrsb - flinka*rrsa );

    // calc angle of arrival (sine of)
    rti = 1/pos.r;
    // aoaa/aoab = angle of arrival to platforms A and B above local horizon
    // Math for angle of arrival above local horizon (WARNING: work in progress)
    // Let PS be the vector from pos (p) to SV
    // Let PC be the vector from pos (p) to 0,0,0
    // By law of cosines: PS*PC = ||PS||||PC||cos(a+90)
    //
    //                 PS*PC
    //  cos(a+90) = ------------ = sin(-a)
    //              ||PS||||PC||
    //
    //  where rtai=1/PS and rti=1/PC
    //  and (pos.x*xta + pos.y*yta + pos.z*zta) = dot product of PS and PC
    //
    //                        ****
    //                    *          *
    //                  /             *
    //                 p               *
    //              . /* .              *
    //           .  a/*90  .             *
    //        .     /*       .           *
    //    SV.........*........ c         *
    //               *                   *
    //                *                 *
    //                 *               *
    //                  *             *
    //                    *         *
    //                        **
    //
    //  opposite side earth
    //
    //                 PS*PC
    //  cos(90-a) = ------------ = sin(a)
    //              ||PS||||PC||
    //
    //  where rtai=1/PS and rti=1/PC
    //  and (pos.x*xta + pos.y*yta + pos.z*zta) = dot product of PS and PC
    //
    //                        ****
    //                      *       *
    //                   *            *
    //                 *               *
    //                *                 *
    //                *                  *
    //               *                   *
    //    SV.........*........ c         *
    //             . *           .       *
    //                *     .      .    *
    //                 *           . . */
    //                  *           a p
    //                    *          */
    //                        ***
    //
    pre.aoaa = (pos.x*xta + pos.y*yta + pos.z*zta) * rtai * rti;
    pre.aoab = (pos.x*xtb + pos.y*ytb + pos.z*ztb) * rtbi * rti;

    //System.out.println("Pre "+pre.tdoa+" "+pre.fdoa+" "+pre.aoaa+" "+pre.aoab);
  }

  /** Set the prediction reference.  Current options are (see {@link #predictReferenceList}
    @param val The prediction reference
  */
  public void setPredictReference (String val) {
    setPredictReference(Parser.find(predictReferenceList,val,0,0,Parser.E_WARNING));  // warning added for Bug 760
  }
  /** Set the prediction reference.  Current options are (see {@link #predictReferenceList}
      @return The prediction reference
   */
  public String getPredictReference () {
    return Parser.get(predictReferenceList,predictReference);
  }
  /* To be used internally to set the prediction reference.  Users should use string values. */
  private void setPredictReference (int val) {
    predictReference = val;
  }

  /** Get the readout for this class
    @param mp Position from which to form readout string
  */
  public String getReadOut (Position mp) {
    Position rpos = mp.cloneOf();
    Predict rpre = new Predict();
    rpos.geo2car();
    predict(rpos,rpre);
    String tmp = name;
    tmp += " ( Lat="+MP.formNumber(rpos.lat,0,0);
    tmp +=   " Lon="+MP.formNumber(rpos.lon,0,0);
    tmp +=   " CEP="+MP.formNumber(cep,0,0);
    tmp +=   " Tdoa="+MP.formNumber(rpre.tdoa,0,0);
    tmp +=   " Fdoa="+MP.formNumber(rpre.fdoa,0,0);
    tmp += " )";
    return tmp;
  }

  /** Class to hold predicted tdoa, fdoa, angle-of-arrivals and cep */
  public class Predict {
    double tdoa,fdoa,aoaa,aoab,cep=0;

    /** default constructor */
    public Predict () {}
  }

  /** Get the time. <br><br>
      Note: Since NeXtMidas 3.1.1 this time can be either a reference time (default)
      or a J1950 time, depending on what was passed in to {@link LayerGeo#setTime(double)}
      by the user.
   */
  public double getTime () { return time; }

  /** Set the time. <br><br>
      Note: Since NeXtMidas 3.1.1, if a user passes in a time greater than
      (86400*100) or 100 days, it is assumed that the time is a J1950 time and the value
      is passed to {@link LayerGeo#setTimeCode(double)}. This was done to be consistent with
      X-Midas XGEOPLOT logic.
   */
  public void setTime (double value) {
    // the check below was added in 3.1.1 for Bug 2503 to try to help users
    // who pass in J1950 times instead of reference times
    if (value > (86400*100)) {
      setTimeCode(value);
      return;
    }
    time = value;
    sva.setTime(time);
    svb.setTime(time);
    refresh();
  }

  /** Sets the timecode in J1950 time. This calls {@link StateVector#setTimeCode(double)}
      for both StateVectors and sets {@link Layer#time} to the given value.<br><br>
      <b>Note:</b> This sets {@link Layer#time} to the time given (J1950 time), NOT
      a file-relative offset time.

      @param value The value to set the time to
      @return true if the timecode is within the range of both of the current state vector files, false
              if not. This can be used to identify cases where the file is no longer valid
              and must be changed.
      @since NeXtMidas 3.1.1
   */
  public boolean setTimeCode (double value) {
    // This method added for Bug 2503 to support setting the time based on actual times, not offsets
    boolean stat = ( sva.setTimeCode(value) && svb.setTimeCode(value));
    time = value;  // seconds since 1950 !!!!!  NOT AN OFFSET   !!!!!
    refresh();
    return stat;
  }

  /** Get the Layer Time
   *  @return the Layer Time as a in seconds since 1950 */
  public double getTimeCode () { return getTime(); }

  /** Get the A state vector
   *  @return state vector A */
  public StateVector getSVA () { return sva; }
  /** Set the A state vector
   *  @param value state vector A */
  public void setSVA (StateVector value) { sva=value; refresh(); }

  /** Get the B state vector
   *  @return state vector B */
  public StateVector getSVB () { return svb; }
  /** Set the B state vector
   *  @param value state vector B */
  public void setSVB (StateVector value) { svb=value; refresh(); }

  /** Get the target position
   *  @return the target Position */
  public Position getPos () { return pos; }
  /** Set the target position
   *  @param value the target Position */
  public void setPos (Position value) { pos=value; refresh(); }
  /** Set the target position by file name
      @param name File name from which to set target position
  */
  public void setPos (String name) { setPos(new Position(ref,name)); }

  /** Set the pointing position of vehicle A
   *  @param value the pointing position of vehicle A */
  public void setPointPosA(Position value) { pointPosA=value; refresh(); }
  /** Set the pointing position of vehicle A
   *  @return the pointing position of vehicle A */
  public Position getPointPosA() { return pointPosA; }

  /** Set the pointing position of vehicle B
   *  @param value the pointing position of vehicle B */
  public void setPointPosB(Position value) { pointPosB=value; refresh(); }
  /** Get the pointing position of vehicle B
   *  @return the pointing position of vehicle B */
  public Position getPointPosB() { return pointPosB; }

  /** Draw the cone to the target */
  public void drawConeToTarget()   { drawConeToTarget = true; refresh(); }
  /** Draw the cone to the pointing position */
  public void drawConeToPointPos() { drawConeToTarget = false; refresh(); }

  /* references to 'site' without letter is for single site and legacy */
  /** Get the site A position
   *  @return site A Position */
  public Position getSite () { return siteA; }
  /** Set the site A position
   *  @param value site A Position */
  public void setSite (Position value) { siteA=value; refresh(); }
  /** Set the site A position by file name
   *  @param name file name of site A position */
  public void setSite (String name) { setSite(new Position(ref,name)); }

  /* references to 'siteA' instead of 'site' separate sites for A and B */
  /** Get the site A position
   *  @return the site A position */
  public Position getSiteA () { return siteA; }
  /** Set the site A position
   *  @param value the site A Position */
  public void setSiteA (Position value) { siteA=value; refresh(); }
  /** Set the site A position by file name
   *  @param name file name for the site A position */
  public void setSiteA (String name) { setSiteA(new Position(ref,name)); }

  /** Get the site B position
   *  @return site B Position */
  public Position getSiteB () { return siteB; }
  /** Set the site B position
   *  @param value site B Position */
  public void setSiteB (Position value) { siteB=value; refresh(); }
  /** Set the site B position by file name
   *  @param name file name for the site B position */
  public void setSiteB (String name) { setSiteB(new Position(ref,name)); }

  /** Set the SVA state vector by name
   *  @param name the name for state vector SVA */
  public void setSVA (String name) { setSVA(new StateVector(ref,name)); }
  /** Set the SVB state vector by name
   *  @param name the name for state vector SVB */
  public void setSVB (String name) { setSVB(new StateVector(ref,name)); }

  /** Set the name to be used as a label for the SVA position.
      @param name The label name */
  public void setSVALabel(String name) { svaLabel = name; refresh(); }
  /** Get label for the SVA position.
   *  @return the label for the SVA position */
  public String getSVALabel() { return svaLabel; }
  /** Set the name to be used as a label for the SVB position.
   *  @param name the name to be used as a label for the SVB position */
  public void setSVBLabel(String name) { svbLabel = name; refresh(); }
  /** Get the label for the SVB position.
   *  @return the label for the SVB position */
  public String getSVBLabel() { return svbLabel; }

  /** Set the name to be used as a label for the target position.
   * @param name the name to be used as a label for the target position. */
  public void setTargetLabel(String name) { targetLabel = name; refresh(); }
  /** Get the label for the target position.
   *  @return the label for the target position */
  public String getTargetLabel() { return targetLabel; }

  /** Set the name to be used as a label for the site position.
   *  @param name the name to be used as a label for the site position. */
  public void setSiteLabel(String name) { siteLabelA = name; refresh(); }
  /** Get the label for the site position.
   *  @return the label for the site position */
  public String getSiteLabel() { return siteLabelA; }

  /** Set the name to be used as a label for the site position.
   *  @param name the name to be used as a label for the site position. */
  public void setSiteLabelA(String name) { siteLabelA = name; refresh(); }
  /** Get the label for the site position.
   *  @return the label for the site position */
  public String getSiteLabelA() { return siteLabelA; }

  /** Set the name to be used as a label for the site B position.
   *  @param name the name to be used as a label for the site B position */
  public void setSiteLabelB(String name) { siteLabelB = name; refresh(); }
  /** Get the label for the site B position.
   *  @return the label for site position B */
  public String getSiteLabelB() { return siteLabelB; }

  /** Get the flags string
   *  @return the flags string */
  public String getFlags() { return Parser.mask2s(flagsList,flags); }
  /** Set the flags mask by mask value
   *  @param value flags as a mask value */
  public void setFlags (int value) { flags=value; refresh(); }
  /** Set the flags with a mask string
   *  @param value flags as a mask string */
  public void setFlags (String value) { setFlags(Parser.mask(flagsList,value,flags)); }

  /** Get the site mode string
   *  @return site mode string */
  public String getSiteMode() { return Parser.get(sitemodeList,sitemode); }
  /** Set the site mode by mask value
   *  @param value sit mode as mask value */
  public void setSiteMode (int value) { sitemode=value; refresh(); }
  /** Set the site mode by mask string
   *  @param value site mode as mask string
   */
  public void setSiteMode (String value) { setSiteMode(Parser.find(sitemodeList,value,sitemode,0,Parser.E_WARNING)); }  // warning added for Bug 760

  /** Get the state vector mode string
   *  @return the state vector mode string */
  public String getSVecMode() { return Parser.get(svecmodeList,svecmode); }
  /** Set the state vector mode by mask value
   *  @param value mask value */
  public void setSVecMode (int value) { svecmode=value; refresh(); }
  /** Set the state vector mode by mask string
   *  @param value mask string */
  public void setSVecMode (String value) { setSVecMode(Parser.find(svecmodeList,value,svecmode,0,Parser.E_WARNING)); }  // warning added for Bug 760


  /** Get the TDOA mode string
   *  @return the TDOA mode string */
  public String getTdoaMode() { return Parser.get(tdoamodeList,tdoamode); }
  /** Set the TDOA mode by mask value
   *  @param value the TDOA mode as a mask string, a | separated list of the options. */
  public void setTdoaMode (int value) { tdoamode=value; refresh(); }
  /** Set the TDOA mode as a mask string, a | separated list of the options.
   *  @param value the TDOA mode by mask string, a | separated list of the options.
   */
  public void setTdoaMode (String value) { setTdoaMode(Parser.find(tdoamodeList,value,tdoamode,0,Parser.E_WARNING)); } // warning added for Bug 760


  /** Get the FDOA mode string
   *  @return the FDOA mode string */
  public String getFdoaMode() { return Parser.get(fdoamodeList,fdoamode); }
  /** Set the FDOA mode by mask value
   *  @param value  the FDOA mode as a mask string, a | separated list of the options. */
  public void setFdoaMode (int value) { fdoamode=value; refresh(); }
  /** Set the FDOA mode as a mask string, a | separated list of the options.
   *  @param value  the FDOA mode by mask string, a | separated list of the options. */
  public void setFdoaMode (String value) { setFdoaMode(Parser.find(fdoamodeList,value,fdoamode,0,Parser.E_WARNING)); } // warning added for Bug 760

  /** Get the time difference of arrival
   *  @return the time difference of arrival */
  public double getTdoa() { return tdoa; }
  /** Set the time difference of arrival
   *  @param value the time difference of arrival */
  public void setTdoa (double value) { tdoa=value; refresh(); }


  /** Get the Delta time difference of arrival
   *  @return the Delta time difference of arrival */
  public double getDTdoa() { return dtdoa; }
  /** Set the Delta time difference of arrival A negative number indicates the number of TDOA lines to
      be drawn in CONTOUR mode and the delta is ignored.
      @param value the Delta time difference of arrival
  */
  public void setDTdoa (double value) { dtdoa=value; refresh(); }

  /** Get the frequency difference of arrival
   *  @return the frequency difference of arrival */
  public double getFdoa() { return fdoa; }
  /** Set the frequency difference of arrival
   *  @param value the frequency difference of arrival */
  public void setFdoa (double value) { fdoa=value; refresh(); }

  /** Get the Delta frequency difference of arrival
   *  @return the Delta frequency difference of arrival */
  public double getDFdoa() { return dfdoa; }
  /** Set the Delta frequency difference of arrival.  A negative number indicates the number of FDOA lines to
      be drawn in CONTOUR mode and the delta is ignored.
      @param value the Delta frequency difference of arrival
  */
  public void setDFdoa (double value) { dfdoa=value; refresh(); }

  /** Get the circular error probability or CEP
   *  @return the circular error probability */
  public double getCEP() { return cep; }
  /** Set the CEP
   *  @param value the circular error probability */
  public void setCEP (double value) { cep=value; refresh(); }

  /** Get the transmit link frequency
   *  @return the transmit link frequency */
  public double getFLinkT() { return flinkt; }
  /** Set the transmit link frequency
   *  @param value the transmit link frequency */
  public void setFLinkT (double value) { flinkt=value; refresh(); }

  /** Get the link frequency A
   *  @return the link frequency A */
  public double getFLinkA() { return flinka; }
  /** Set the link frequency A
   *  @param value the link frequency A */
  public void setFLinkA (double value) { flinka=value; refresh(); }

  /** Get the link frequency B
   *  @return the link frequency B */
  public double getFLinkB() { return flinkb; }
  /** Set the link frequency B
   *  @param value the link frequency B */
  public void setFLinkB (double value) { flinkb=value; refresh(); }

  /** Get the number of conic circles to see from SV to ground
   *  @return the number of conic circles to see from SV to ground */
  public int getConic() { return conic; }
  /** Set the number of conic circles to see from SV to ground
   *  @param value the number of conic circles to see from SV to ground */
  public void setConic (int value) { conic = value; refresh(); }

  /** Get the number of conic divisions to see from SV to ground
   *  @return the number of conic divisions to see from SV to ground */
  public int getConeDiv() { return cdiv; }
  /** Set the number of conic divisions to see from SV to ground
   *  @param value the number of conic divisions to see from SV to ground */
  public void setConeDiv (int value) { cdiv = value; refresh(); }

  /** Get the cone angle for A
      @return cone angle A
      @deprecated Since NeXtMidas 2.5.2: Use getConeAngleA or getConeAngleB.
  */
  @Deprecated public double getConeAngle() { return cangleA; }
  /** Set the cone angle for both A and B.
      @param value cone angle for both A and B
     @deprecated Since NeXtMidas 2.5.2: Use setConeAngleA or setConeAngleB.
  */
  @Deprecated public void setConeAngle (double value) {
    Shell.deprecate("setConeAngle since NeXtMidas 2.5.2: Use setConeAngleA or setConeAngleB");
    cangleA=cangleB=value; refresh();
  }

  /** Get the cone angle A
   *  @return cone angle A */
  public double getConeAngleA () { return cangleA; }
  /** Set the cone angle A
   *  @param value cone angle A */
  public void setConeAngleA (double value) { cangleA = value; refresh(); }
  /** Get the cone angle B
   *  @return cone angle B */
  public double getConeAngleB () { return cangleB; }
  /** Set the cone angle B
   *  @param value cone angle B */
  public void setConeAngleB (double value) { cangleB = value; refresh(); }

  /** Get the cone angle range
   *  @return the cone angle range */
  public double getConeRange() { return crange; }
  /** Set the cone angle range
   *  @param value the cone angle range */
  public void setConeRange (double value) { crange = value; refresh(); }

  /** Get the Altitude on which the horizon line should be based
   *  @return the Altitude on which the horizon line should be based */
  @InternalUseOnly
  public double getHorizonAlt () { return horizonAlt; }
  /** Set the Altitude on which the horizon line should be based (min=0)
   *  @param altitude the Altitude on which the horizon line should be based */
  @InternalUseOnly
  public void setHorizonAlt (double altitude) {
    horizonAlt = Math.max(altitude, 0);
    refresh();
  }

  /** Set the color of footprints, horizon lines and more.  Any omitted
      color keys will not be affected. Valid keys are:
      <pre>
        SITE        - For the site color
        TARGET      - For the target color
        FOOTPRINTA  - For the color of footprint corresponding with A
        FOOTPRINTB  - For the color of footprint corresponding with B
        HORIZONA    - For the color of horizon line corresponding with A
        HORIZONB    - For the color of horizon line corresponding with B
        SVA         - For the color of SVA point and label
        SVB         - For the color of SVB point and label
        TDOA        - For the color of the TDOA lines
        FDOA        - For the color of the FDOA lines
      </pre>
    @param colTab Table of colors
  */
  public void setColorTable (Table colTab) {
    for (Table.Iterator ti = colTab.iterator(); ti.getNext();) {
      String key = ti.getKey();
      Object obj = ti.getValue();
      Color color = null;
      try {
        if      (obj instanceof Color) { color = (Color)obj;                   }
        else if (obj instanceof Data ) { color = new Color(Convert.o2l(obj));  }
        else                           { color = MColor.getColor((String)obj); }

        if      (key.equals("SITE"))       { colorSiteA      = color; }
        else if (key.equals("SITEA"))      { colorSiteA      = color; }
        else if (key.equals("SITEB"))      { colorSiteB      = color; }
        else if (key.equals("TARGET"))     { colorTarget     = color; }
        else if (key.equals("FOOTPRINTA")) { colorFootPrintA = color; }
        else if (key.equals("HORIZONA"))   { colorHorizonA   = color; }
        else if (key.equals("HORIZONALTA"))  { colorHznAtAltA  = color; }
        else if (key.equals("SVA"))        { colorSVA        = color; }
        else if (key.equals("FOOTPRINTB")) { colorFootPrintB = color; }
        else if (key.equals("HORIZONB"))   { colorHorizonB   = color; }
        else if (key.equals("HORIZONALTB"))  { colorHznAtAltB  = color; }
        else if (key.equals("SVB"))        { colorSVB        = color; }
        else if (key.equals("TDOA"))       { colorTDOA       = color; }
        else if (key.equals("FDOA"))       { colorFDOA       = color; }
        else { warning(" "+this+" setColorTable: Invalid color key "+key); }
      } catch (Exception e) {
        warning("Could not convert "+obj+" to a color"); // ignore stack trace
      }
    }
    refresh();
  }
  /** Get all the current color settings as a table of Strings.  See {@link #setColorTable(Table)}
      for the list of keys in the table.
      @return the current color settings as a table of Strings
  */
  public Table getColorTable () {
    return getColorTable(false);
  }
  /** Get all the current color settings as a table.  See {@link #setColorTable(Table)}
      for the list of keys in the table.
      @param doColorObj Get the colors as objects
      @return color settings as a Table
  */
  public Table getColorTable (boolean doColorObj) {
    Table colTab = new Table();
    if (doColorObj) {
      colTab.put("SITE",       colorSiteA);
      colTab.put("SITEA",      colorSiteA);
      colTab.put("SITEB",      colorSiteB);
      colTab.put("TARGET",     colorTarget);
      colTab.put("FOOTPRINTA", colorFootPrintA);
      colTab.put("FOOTPRINTB", colorFootPrintB);
      colTab.put("HORIZONA",   colorHorizonA);
      colTab.put("HORIZONB",   colorHorizonB);
      colTab.put("HORIZONALTA",  colorHznAtAltA);
      colTab.put("HORIZONALTB",  colorHznAtAltB);
      colTab.put("SVA",        colorSVA);
      colTab.put("SVB",        colorSVB);
      colTab.put("TDOA",       colorTDOA);
      colTab.put("FDOA",       colorFDOA);
    } else {
      colTab.put("SITE",       MColor.toString(colorSiteA));
      colTab.put("SITEA",      MColor.toString(colorSiteA));
      colTab.put("SITEB",      MColor.toString(colorSiteB));
      colTab.put("TARGET",     MColor.toString(colorTarget));
      colTab.put("FOOTPRINTA", MColor.toString(colorFootPrintA));
      colTab.put("FOOTPRINTB", MColor.toString(colorFootPrintB));
      colTab.put("HORIZONA",   MColor.toString(colorHorizonA));
      colTab.put("HORIZONB",   MColor.toString(colorHorizonB));
      colTab.put("HORIZONALTA",  MColor.toString(colorHznAtAltA));
      colTab.put("HORIZONALTB",  MColor.toString(colorHznAtAltB));
      colTab.put("SVA",        MColor.toString(colorSVA));
      colTab.put("SVB",        MColor.toString(colorSVB));
      colTab.put("TDOA",       MColor.toString(colorTDOA));
      colTab.put("FDOA",       MColor.toString(colorFDOA));
    }
    return colTab;
  }

  /** The color for all things drawn related to A. (see {@link #setColorA(Color)})
      Note: This does not include experimental Horizon At Altitude
      @param color Color name
  */
  public void setColorA (String color) {
    setColorA(MColor.getColor(color));
  }

  /** The color for all things drawn related to A. This includes the horizon lines,
      A position and label. Both footprint line colors are set if cones are pointed
      at the target.  Otherwise, only footprint A color is set.
      Note: This does not include experimental Horizon At Altitude
      @param color Color object
      @see #drawConeToPointPos()
      @see #drawConeToTarget()
  */
  public void setColorA (Color color) {
    colorFootPrintA = color;
    colorHorizonA   = color;
    colorSVA        = color;
    refresh();
  }

  /** Set the color for all things drawn related to B. (see {@link #setColorB(Color)})
      Note: This does not include experimental Horizon At Altitude
      @param color Color name
  */
  public void setColorB (String color) {
    setColorB(MColor.getColor(color));
  }

  /** The color for all things drawn related to B. This includes the horizon lines,
      B position and label. The footprint line color is changed only if drawing cones
      to pointing position is set.
      Note: This does not include experimental Horizon At Altitude
      @param color Color object
      @see #drawConeToPointPos()
      @see #drawConeToTarget()
  */
  public void setColorB (Color color) {
    colorFootPrintB = color;
    colorHorizonB   = color;
    colorSVB        = color;
    refresh();
  }

  // ----- below is new since NeXtMidas 2.7.3 -----
  /** Get the grid size in the X direction
   *  @return the grid size in the X direction */
  public int getGridSizeX () { return frame; }
  /** Set the grid size in the X direction
   *  @param value the grid size in the X direction */
  public void setGridSizeX (int value) {
    if (value != frame) setGridSize(value, size);
  }
  /** Same as setGridSizeX(..) */
  public void setFrame (int value) { setGridSizeX(value); }

  /** Get the grid size in the Y direction
   *  @return the grid size in the Y direction */
  public int getGridSizeY () { return size; }
  /** Set the grid size in the Y direction
   *  @param value grid size in the Y direction */
  public void setGridSizeY (int value) {
    if (value != size) setGridSize(frame, value);
  }
  /** Same as setGridSizeY(..) */
  public void setSize (int value) { setGridSizeY(value); }

  /** Set the grid size in the X and Y direction to specified value
   *  @param val grid size (both X and Y direction) */
  public void setGridSize (int val) { setGridSize(val, val); }
  /** Set the grid size in the X and Y direction
   *  @param xGridSize grid size in the X direction
   *  @param yGridSize grid size in the Y direction
   */
  public void setGridSize (int xGridSize, int yGridSize) {
    // validate input values (should be greater than 2
    if (xGridSize < 2) throw new IllegalArgumentException("Grid Size X must be greater than 1.");
    if (yGridSize < 2) throw new IllegalArgumentException("Grid Size Y must be greater than 1.");
    int prev = frame*size; // previous number of points
    int npts = xGridSize*yGridSize;
    boolean changed = false;

    if (xGridSize != frame) { frame = xGridSize; changed = true; }
    if (yGridSize != size)  { size  = yGridSize; changed = true; }
    if (changed) {
      if (prev != npts ) {
        // only need to reallocate if npts if different then previous
        tdoabuf = new double[npts];
        fdoabuf = new double[npts];
        aoaabuf = new double[npts];
        aoabbuf = new double[npts];
        // cepbuf  = new float[npts]; /** TBD Unused */
      }
      refresh();
    }
  }

  /** Set if using Traditional Jeff (and Andrew) ray intersect sphere code
   *  for horizon at altitude calculation. The alternate is a more visual
   *  math approach
   *  @param useTraditional true for traditional, false for visual based
   */
  @InternalUseOnly
  public static void useHorizonAltTraditionalCode (boolean useTraditional) {
    horizonAltTraditionalCode = useTraditional;
  }

  /** Get if using Traditional Jeff (and Andrew) ray intersect sphere code
   *  for horizon at altitude calculation. The alternate is a more visual
   *  math approach
   *  @return true if using traditional approach
   */
  @InternalUseOnly
  public static boolean isHorizonAltTraditionalCode () {
    return horizonAltTraditionalCode;
  }

}
