001    package edu.nrao.sss.astronomy;
002    
003    import java.math.BigDecimal;
004    import java.util.Date;
005    
006    import javax.xml.bind.annotation.XmlElement;
007    import javax.xml.bind.annotation.XmlRootElement;
008    import javax.xml.bind.annotation.XmlTransient;
009    
010    import edu.nrao.sss.math.MathUtil;
011    import edu.nrao.sss.math.Polynomial;
012    import edu.nrao.sss.measure.AngularVelocityUnits;
013    import edu.nrao.sss.measure.Distance;
014    import edu.nrao.sss.measure.DistanceUnits;
015    import edu.nrao.sss.measure.Latitude;
016    import edu.nrao.sss.measure.Longitude;
017    import edu.nrao.sss.measure.LinearVelocityUnits;
018    import edu.nrao.sss.measure.TimeDuration;
019    import edu.nrao.sss.measure.TimeInterval;
020    import edu.nrao.sss.measure.TimeUnits;
021    
022    /**
023     * A position of an astronomical source that is described by a collection
024     * of polynomial equations.
025     * <p>
026     * The viewpoint of this class is that the position of a source may be described
027     * by its location (right ascension, declination, and distance) at a particular
028     * reference time and a set of equations that describe the motions of the source
029     * away from that location.</p>
030     * <p>
031     * <b>Version Info:</b>
032     * <table style="margin-left:2em">
033     *   <tr><td>$Revision: 1707 $</td></tr>
034     *   <tr><td>$Date: 2008-11-14 10:23:59 -0700 (Fri, 14 Nov 2008) $</td></tr>
035     *   <tr><td>$Author: dharland $</td></tr>
036     * </table></p>
037     * 
038     * @author David M. Harland
039     * @since 2006-03-24
040     */
041    @XmlRootElement
042    public class PolynomialPosition
043      extends AbsSkyPos
044      implements Comparable<PolynomialPosition>
045    {
046      private static final long DEFAULT_TIME = 0L;
047      
048      //Reference properties
049      private Date         referenceTime;
050      private TimeInterval validTime;
051      
052      //Position at time zero
053      private Longitude longitudeAtTimeZero;
054      private Latitude  latitudeAtTimeZero;
055      private Distance  distanceAtTimeZero;
056      
057      //Position movements
058      private Polynomial           longitudeMotion;
059      private Polynomial           latitudeMotion;
060      private Polynomial           radialMotion;
061      private AngularVelocityUnits longitudeVelocityUnits;
062      private AngularVelocityUnits latitudeVelocityUnits;
063      private LinearVelocityUnits  radialVelocityUnits;
064      
065      //Uncertainties
066      private Longitude longitudeUncertainty;
067      private Latitude  latitudeUncertainty;
068      private Distance  distanceUncertainty;
069      
070      /** Creates a new instance. */
071      public PolynomialPosition()
072      {
073        super();
074        
075        initialize();
076        
077        validTime     = new TimeInterval();
078        referenceTime = new Date(DEFAULT_TIME);
079    
080        longitudeAtTimeZero = new Longitude();
081        latitudeAtTimeZero  = new Latitude();
082        distanceAtTimeZero  = new Distance(MathUtil.POSITIVE_INFINITY);
083        
084        longitudeMotion = new Polynomial();
085        latitudeMotion  = new Polynomial();
086        radialMotion    = new Polynomial();
087    
088        longitudeUncertainty = new Longitude();
089        latitudeUncertainty  = new Latitude();
090        distanceUncertainty  = new Distance();
091      }
092      
093      private void initialize()
094      {
095        longitudeVelocityUnits = AngularVelocityUnits.getDefault();
096        latitudeVelocityUnits  = AngularVelocityUnits.getDefault();
097        radialVelocityUnits    = LinearVelocityUnits.KILOMETERS_PER_SECOND;
098      }
099      
100      /**
101       *  Resets this position to its initial state.
102       *  A reset position has the same state as one created
103       *  via the {@link #PolynomialPosition()} constructor. 
104       */
105      public void reset()
106      {
107        super.reset();
108        
109        initialize();
110        
111        validTime.reset();
112        referenceTime.setTime(DEFAULT_TIME);
113    
114        longitudeAtTimeZero.reset();
115        latitudeAtTimeZero.reset();
116        distanceAtTimeZero.set(MathUtil.POSITIVE_INFINITY, DistanceUnits.KILOMETER);
117    
118        longitudeMotion.clear();
119        latitudeMotion.clear();
120        radialMotion.clear();
121        
122        longitudeUncertainty.reset();
123        latitudeUncertainty.reset();
124        distanceUncertainty.reset();
125      }
126    
127      /* (non-Javadoc)
128       * @see edu.nrao.sss.astronomy.SkyPosition#isMoving()
129       */
130      public boolean isMoving()
131      {
132        //Return true if any of the motion variables have values
133        return longitudeMotion.getNumberOfTerms() > 0 ||
134                latitudeMotion.getNumberOfTerms() > 0 ||
135                  radialMotion.getNumberOfTerms() > 0;
136      }
137      
138      //===========================================================================
139      // REFERENCE PROPERTIES
140      //===========================================================================
141      
142      /* (non-Javadoc)
143       * @see SkyPosition#getType()
144       */
145      public SkyPositionType getType()  { return SkyPositionType.POLYNOMIAL; }
146      
147      /**
148       * Sets the time at which the delta-t terms in the motion polynomials
149       * evaluate to zero.
150       * <p>
151       * The motion polynomials are returned by the methods
152       * {@link #getLatitudeMotion()},
153       * {@link #getLongitudeMotion()}, and
154       * {@link #getRadialMotion()}.
155       * At the reference time t<sub>0</sub>, these equations should
156       * evaluate to zero.</p>
157       * <p>
158       * If {@code referenceTime} is <i>null</i>, an
159       * {@code IllegalArgumentException} will be thrown.</p>
160       *  
161       * @param referenceTime t<sub>0</sub> for the motion terms.
162       */
163      public void setReferenceTime(Date referenceTime)
164      {
165        if (referenceTime == null)
166          throw new IllegalArgumentException("null is not a legal argument");
167        
168        this.referenceTime = referenceTime;
169      }
170    
171      /**
172       * Returns the time at which the delta-t terms in the motion polynomials
173       * evaluate to zero.
174       * See {@link #setReferenceTime(Date)} for more information.
175       * 
176       * @return t<sub>0</sub> for the motion terms.
177       */
178      public Date getReferenceTime()
179      {
180        return referenceTime;
181      }
182      
183      /**
184       * Sets the interval of time for which this position is valid.
185       * <p>
186       * If {@code interval} is <i>null</i>, it will be treated as
187       * an non-null interval of zero length.</p>
188       * 
189       * @param interval the interval of time for which this position is valid.
190       */
191      public void setValidTime(TimeInterval interval)
192      {
193        if (interval == null)
194          validTime.reset();
195        else
196          validTime = interval;
197      }
198      
199      /**
200       * Returns the interval of time for which this position is valid.
201       * 
202       * @return the interval of time for which this position is valid.
203       */
204      public TimeInterval getValidTime()
205      {
206        return validTime;
207      }
208      
209      /**
210       * Returns <i>true</i> if this position is valid for
211       * the given point in time.
212       * 
213       * @param time the point in time to be checked.
214       */
215      public boolean isValidFor(Date time)
216      {
217        return validTime.contains(time);
218      }
219    
220      //===========================================================================
221      // MOTION POLYNOMIALS
222      //===========================================================================
223      
224      /**
225       * Returns the polynomial, in delta-t, for calculating change in the
226       * longitude of this position since the reference time.
227       * <p>
228       * The independent variable should be <b>delta-t</b>, with time measured in
229       * units determined by the value sent to
230       * {@link #setLongitudeVelocityUnits(AngularVelocityUnits)}.</p>
231       * <p>
232       * The polynomial returned is the one held by this source position,
233       * so any changes made to it will be reflected here.
234       * Note that this means that client objects need never use the {@code set}
235       * method.  Instead, they may just use this method and manipulate the
236       * polynomial.</p>
237       *  
238       * @return the equation for calculating the change in the 
239       *         longitude of this position since the reference time.
240       */
241      public Polynomial getLongitudeMotion()
242      {
243        return longitudeMotion;
244      }
245    
246      /**
247       * Sets this position's longitude motion equation to {@code longitudeEqn}.
248       * <p>
249       * See {@link #getLongitudeMotion()} for an explanation of the
250       * independent variable and its coefficients.</p>
251       * <p>
252       * This method will save a reference to {@code longitudeEqn}; it will
253       * not make and store a clone.  This means that any changes made
254       * to {@code longitudeEqn} after calling this method will be reflected
255       * in this position.
256       * Note that it is not necessary to use this {@code set} method; clients
257       * may choose to interact only with the {@code get} method and manipulate
258       * the returned polynomial.</p>
259       * <p>
260       * If {@code longitudeEqn} is <i>null</i>, a new empty polynomial will be
261       * created and used for this motion.</p>
262       * 
263       * @param longitudeEqn the longitude motion equation for this position.
264       */
265      public void setLongitudeMotion(Polynomial longitudeEqn)
266      {
267        longitudeMotion = (longitudeEqn == null) ? new Polynomial()
268                                                 : longitudeEqn;
269      }
270    
271      /**
272       * Returns the polynomial, in delta-t, for calculating change in the
273       * latitude of this position since the reference time.
274       * <p>
275       * The independent variable should be <b>delta-t</b>, with time measured in
276       * units determined by the value sent to
277       * {@link #setLatitudeVelocityUnits(AngularVelocityUnits)}.</p>
278       * <p>
279       * The polynomial returned is the one held by this source position,
280       * so any changes made to it will be reflected here.
281       * Note that this means that client objects need never use the {@code set}
282       * method.  Instead, they may just use this method and manipulate the
283       * polynomial.</p>
284       *  
285       * @return the equation for calculating the change in the 
286       *         latitude of this position since the reference time.
287       */
288      public Polynomial getLatitudeMotion()
289      {
290        return latitudeMotion;
291      }
292    
293      /**
294       * Sets this position's latitude motion equation to {@code latitudeEqn}.
295       * <p>
296       * See {@link #getLatitudeMotion()} for an explanation of the
297       * independent variable and its coefficients.</p>
298       * <p>
299       * This method will save a reference to {@code latitudeEqn}; it will
300       * not make and store a clone.  This means that any changes made
301       * to {@code latitudeEqn} after calling this method will be reflected
302       * in this position.
303       * Note that it is not necessary to use this {@code set} method; clients
304       * may choose to interact only with the {@code get} method and manipulate
305       * the returned polynomial.</p>
306       * <p>
307       * If {@code latitudeEqn} is <i>null</i>, a new empty polynomial will be
308       * created and used for this motion.</p>
309       * 
310       * @param latitudeEqn the latitude motion equation for this position.
311       */
312      public void setLatitudeMotion(Polynomial latitudeEqn)
313      {
314        latitudeMotion = (latitudeEqn == null) ? new Polynomial()
315                                               : latitudeEqn;
316      }
317    
318      /**
319       * Returns the polynomial, in delta-t, for calculating change in the
320       * distance of this position since the reference time.
321       * <p>
322       * The independent variable should be <b>delta-t</b>, with time measured in
323       * units determined by the value sent to
324       * {@link #setRadialVelocityUnits(LinearVelocityUnits)}.</p>
325       * <p>
326       * The polynomial returned is the one held by this source position,
327       * so any changes made to it will be reflected here.
328       * Note that this means that client objects need never use the {@code set}
329       * method.  Instead, they may just use this method and manipulate the
330       * polynomial.</p>
331       *  
332       * @return the equation for calculating the change in the 
333       *         distance of this position since the reference time.
334       */
335      public Polynomial getRadialMotion()
336      {
337        return radialMotion;
338      }
339      
340      /**
341       * Sets this position's distance motion equation to {@code distanceEquation}.
342       * <p>
343       * See {@link #getRadialMotion()} for an explanation of the
344       * independent variable and its coefficients.</p>
345       * <p>
346       * This method will save a reference to {@code distanceEquation}; it will
347       * not make and store a clone.  This means that any changes made
348       * to {@code distanceEquation} after calling this method will be reflected
349       * in this position.
350       * Note that it is not necessary to use this {@code set} method; clients
351       * may choose to interact only with the {@code get} method and manipulate
352       * the returned polynomial.</p>
353       * <p>
354       * If {@code distanceEquation} is <i>null</i>, a new empty polynomial will be
355       * created and used for this motion.</p>
356       * 
357       * @param distanceEquation the distance motion equation for this position.
358       */
359      public void setRadialMotion(Polynomial distanceEquation)
360      {
361        this.radialMotion = (distanceEquation == null) ? new Polynomial()
362                                                       : distanceEquation;
363      }
364    
365      //===========================================================================
366      // UNITS OF VELOCITY
367      //===========================================================================
368    
369      /**
370       * Sets the units that will be used to interpret the
371       * longitude motion polynomial.
372       * The default value of this property is
373       * {@code AngularVelocityUnits.MILLI_ARC_SECONDS_PER_YEAR}.
374       * <p>
375       * This property will be used by the 
376       * {@link #getLongitude(Date) getLongitude} methods.
377       * Those methods will assume that all terms
378       * will be based on the logical extension of these units.</p>
379       * 
380       * @param units the units to use when interpreting the longitude
381       *              motion polynomial.  If {@code units} is <i>null</i>,
382       *              this method will do nothing.
383       */
384      public void setLongitudeVelocityUnits(AngularVelocityUnits units)
385      {
386        if (units != null)
387          longitudeVelocityUnits = units;
388      }
389    
390      /**
391       * Sets the units that will be used to interpret the
392       * declination motion polynomial.
393       * The default value of this property is
394       * {@code AngularVelocityUnits.MILLI_ARC_SECONDS_PER_YEAR}.
395       * <p>
396       * This property will be used by the 
397       * {@link #getLatitude(Date) getLatitude} methods.
398       * Those methods will assume that all terms
399       * will be based on the logical extension of these units.</p>
400       * 
401       * @param units the units to use when interpreting the declination
402       *              motion polynomial.  If {@code units} is <i>null</i>,
403       *              this method will do nothing.
404       */
405      public void setLatitudeVelocityUnits(AngularVelocityUnits units)
406      {
407        if (units != null)
408          latitudeVelocityUnits = units;
409      }
410    
411      /**
412       * Sets the units that will be used to interpret the
413       * distance motion polynomial.
414       * The default value of this property is
415       * {@code LinearVelocityUnits.KILOMETERS_PER_SECOND}.
416       * <p>
417       * This property will be used by the 
418       * {@link #getDistance(Date) getDistance} methods.
419       * Those methods will assume that all terms
420       * will be based on the logical extension of these units.</p>
421       * 
422       * @param units the units to use when interpreting the distance
423       *              motion polynomial.  If {@code units} is <i>null</i>,
424       *              it will be replaced by a non-null default type.
425       */
426      public void setRadialVelocityUnits(LinearVelocityUnits units)
427      {
428        if (units == null)
429          units = LinearVelocityUnits.getDefault();
430        
431        radialVelocityUnits = units;
432      }
433    
434      /**
435       * Returns the units that will be used to interpret the
436       * the longitude motion polynomial.
437       * 
438       * @see #setLongitudeVelocityUnits(AngularVelocityUnits)
439       */
440      @XmlTransient
441      public AngularVelocityUnits getLongitudeVelocityUnits()
442      {
443        return longitudeVelocityUnits;
444      }
445    
446      /**
447       * Returns the units that will be used to interpret the
448       * the latitude motion polynomial.
449       * 
450       * @see #setLatitudeVelocityUnits(AngularVelocityUnits)
451       */
452      @XmlTransient
453      public AngularVelocityUnits getLatitudeVelocityUnits()
454      {
455        return latitudeVelocityUnits;
456      }
457    
458      /**
459       * Returns the units that will be used to interpret the
460       * the distance motion polynomial.
461       * 
462       * @see #setRadialVelocityUnits(LinearVelocityUnits)
463       */
464      @XmlTransient
465      public LinearVelocityUnits getRadialVelocityUnits()
466      {
467        return radialVelocityUnits;
468      }
469    
470      //We'll use the shorter symbol for persistent storage
471      @XmlElement(name="longitudeVelocityUnits")
472      @SuppressWarnings("unused")
473      private String getLonVSymbol()  { return getLongitudeVelocityUnits().getSymbol(); }
474      @SuppressWarnings("unused")
475      private void setLonVSymbol(String u)
476      {
477        setLongitudeVelocityUnits(AngularVelocityUnits.fromString(u));
478      }
479      @XmlElement(name="latitudeVelocityUnits")
480      @SuppressWarnings("unused")
481      private String getLatVSymbol()  { return getLatitudeVelocityUnits().getSymbol(); }
482      @SuppressWarnings("unused")
483      private void setLatVSymbol(String u)
484      {
485        setLatitudeVelocityUnits(AngularVelocityUnits.fromString(u));
486      }
487      @XmlElement(name="radialVelocityUnits")
488      @SuppressWarnings("unused")
489      private String getRadialVSymbol()  { return getRadialVelocityUnits().getSymbol(); }
490      @SuppressWarnings("unused")
491      private void setRadialVSymbol(String u)
492      {
493        setRadialVelocityUnits(LinearVelocityUnits.fromString(u));
494      }
495    
496      //===========================================================================
497      // POSITION AT TIME ZERO
498      //===========================================================================
499    
500      /**
501       * Sets the longitude of this position as of the reference time.
502       * <p>
503       * This method will save a reference to {@code longitude}; it will
504       * not make and store a clone.  This means that any changes made
505       * to {@code longitude} after calling this method will be reflected
506       * in this position.</p>
507       * 
508       * @param longitude the longitude of this position as of the reference time.
509       *                  If this value is <i>null</i> it will be treated
510       *                  as a new longitude built via the no-argument
511       *                  constructor.
512       */
513      public void setLongitudeAtTimeZero(Longitude longitude)
514      {
515        if (longitude == null)
516          longitude = new Longitude();
517        
518        longitudeAtTimeZero = longitude;
519      }
520    
521      /**
522       * Returns the longitude of this position as of the reference time.
523       * 
524       * @return the longitude of this position as of the reference time.
525       *         This value is guaranteed to be non-<i>null</i>.
526       */
527      public Longitude getLongitudeAtTimeZero()
528      {
529        return longitudeAtTimeZero;
530      }
531    
532      /**
533       * Sets the latitude of this position as of the reference time.
534       * <p>
535       * This method will save a reference to {@code latitude}; it will
536       * not make and store a clone.  This means that any changes made
537       * to {@code latitude} after calling this method will be reflected
538       * in this position.</p>
539       * 
540       * @param latitude the latitude of this position as of the reference time.
541       *                 If this value is <i>null</i> it will be treated
542       *                 as a new latitude built via the no-argument
543       *                 constructor.
544       */
545      public void setLatitudeAtTimeZero(Latitude latitude)
546      {
547        latitudeAtTimeZero = latitude;
548      }
549    
550      /**
551       * Returns the latitude of this position as of the reference time.
552       * 
553       * @return the latitude of this position as of the reference time.
554       *         This value is guaranteed to be non-<i>null</i>.
555       */
556      public Latitude getLatitudeAtTimeZero()
557      {
558        return latitudeAtTimeZero;
559      }
560    
561      /**
562       * Sets the distance of this position as of the reference time.
563       * <p>
564       * This method will save a reference to {@code distance}; it will
565       * not make and store a clone.  This means that any changes made
566       * to {@code distance} after calling this method will be reflected
567       * in this position.</p>
568       * 
569       * @param distance the distance of this position as of the reference time.
570       *                 If this value is <i>null</i> it will be treated
571       *                 as a new distance built via the no-argument
572       *                 constructor.
573       */
574      public void setDistanceAtTimeZero(Distance distance)
575      {
576        distanceAtTimeZero = distance;
577      }
578    
579      /**
580       * Returns the distance of this position as of the reference time.
581       * 
582       * @return the distance of this position as of the reference time.
583       *         This value is guaranteed to be non-<i>null</i>.
584       */
585      public Distance getDistanceAtTimeZero()
586      {
587        return distanceAtTimeZero;
588      }
589    
590      //===========================================================================
591      // EVALUATING THE POSITION AT TIME T
592      //===========================================================================
593    
594      /**
595       * Returns the longitude of this position at the given point in time.
596       * <p>
597       * The returned value is guaranteed to be non-null.
598       * Note that the object returned is not held internally by this position.
599       * This means that any changes made to the returned object by clients
600       * will <i>not</i> be reflected in this position.</p>
601       * 
602       * @param time the point in time for which the longitude is sought.
603       * 
604       * @return the longitude of this position at the given point in time.
605       */
606      public Longitude getLongitude(Date time)
607      {
608        //TODO: What should happen if time is not valid?
609        
610        //Create a new RA from the value at time zero
611        Longitude answer = longitudeAtTimeZero.clone();
612        
613        //Get delta-T in appropriate units
614        double deltaTime =
615          getDeltaTime(time, longitudeVelocityUnits.getTimeUnits()).doubleValue();
616        
617        //Add the movement in RA since time zero
618        BigDecimal movement =
619          BigDecimal.valueOf(longitudeMotion.calculateFor(deltaTime));
620        
621        answer.add(movement, longitudeVelocityUnits.getArcUnits());
622    
623        return answer; 
624      }
625      
626      /**
627       * Returns the latitude of this position at the given point in time.
628       * <p>
629       * The returned value is guaranteed to be non-null.
630       * Note that the object returned is not held internally by this position.
631       * This means that any changes made to the returned object by clients
632       * will <i>not</i> be reflected in this position.</p>
633       * 
634       * @param time the point in time for which the latitude is sought.
635       * 
636       * @return the latitude of this position at the given point in time.
637       */
638      public Latitude getLatitude(Date time)
639      {
640        //TODO: What should happen if time is not valid?
641    
642        //Create a new declination from the value at time zero
643        Latitude answer = latitudeAtTimeZero.clone();
644      
645        //Get delta-T in appropriate units
646        double deltaTime =
647          getDeltaTime(time, latitudeVelocityUnits.getTimeUnits()).doubleValue();
648        
649        //Add the movement in declination since time zero
650        BigDecimal movement =
651          BigDecimal.valueOf(latitudeMotion.calculateFor(deltaTime));
652        
653        answer.add(movement, latitudeVelocityUnits.getArcUnits());
654    
655        return answer; 
656      }
657    
658      /**
659       * Returns the distance of this position at the given point in time.
660       * <p>
661       * The returned value is guaranteed to be non-null.
662       * Note that the object returned is not held internally by this position.
663       * This means that any changes made to the returned object by clients
664       * will <i>not</i> be reflected in this position.</p>
665       * 
666       * @param time the point in time for which the distance is sought.
667       * 
668       * @return the distance of this position at the given point in time.
669       */
670      public Distance getDistance(Date time)
671      {
672        //TODO: What should happen if time is not valid?
673        
674        //Get delta-T in appropriate units
675        BigDecimal deltaTime =
676          getDeltaTime(time, radialVelocityUnits.getTimeUnits());
677        
678        //Create a new distance from the motion equation
679        double dist = radialMotion.calculateFor(deltaTime.doubleValue());
680        Distance answer =
681          new Distance(BigDecimal.valueOf(dist),
682                       radialVelocityUnits.getDistanceUnits());
683        
684        //Add the distance from time zero
685        answer.add(distanceAtTimeZero);
686    
687        return answer; 
688      }
689      
690      /**
691       * Returns the difference between {@code time} and this position's
692       * reference time in {@code units}.
693       */
694      private BigDecimal getDeltaTime(Date time, TimeUnits units)
695      {
696        long milliseconds = time.getTime() - referenceTime.getTime();
697        
698        boolean negative = (milliseconds < 0.0);
699        
700        if (negative)
701          milliseconds = -milliseconds;
702        
703        TimeDuration td = new TimeDuration(new BigDecimal(milliseconds),
704                                           TimeUnits.MILLISECOND);
705    
706        BigDecimal delta = td.toUnits(units);
707        
708        return negative ? delta.negate() : delta;
709      }
710    
711      //===========================================================================
712      // SPACE: UNCERTAINTIES
713      //===========================================================================
714    
715      /**
716       * Sets the uncertainty level in the longitude of this position.
717       * <p>
718       * Note that, unless {@code uncertainty} is <i>null</i>, this position
719       * will hold a reference to {@code uncertainty}.  That is, this method
720       * will not make a copy of {@code uncertainty}.  This means that any
721       * changes made to {@code uncertainty} will be reflected here.  Note, too,
722       * that this method is not strictly required.  Clients could instead
723       * call {@link #getLongitudeUncertainty} and operate on the returned
724       * object.</p>
725       * 
726       * @param uncertainty the uncertainty level in the longitude of
727       *                    this position. An {@code uncertainty} of <i>null</i>
728       *                    will be treated as an uncertainty of zero.
729       */
730      public void setLongitudeUncertainty(Longitude uncertainty)
731      {
732        if (uncertainty != null)
733          longitudeUncertainty = uncertainty;
734        else
735          longitudeUncertainty.reset();
736      }
737      
738      /**
739       * Sets the uncertainty level in the latitude of this position.
740       * <p>
741       * Note that, unless {@code uncertainty} is <i>null</i>, this position
742       * will hold a reference to {@code uncertainty}.  That is, this method
743       * will not make a copy of {@code uncertainty}.  This means that any
744       * changes made to {@code uncertainty} will be reflected here.  Note, too,
745       * that this method is not strictly required.  Clients could instead
746       * call {@link #getLatitudeUncertainty} and operate on the returned
747       * object.</p>
748       * 
749       * @param uncertainty the uncertainty level in the latitude of
750       *                    this position. An {@code uncertainty} of <i>null</i>
751       *                    will be treated as an uncertainty of zero.
752       */
753      public void setLatitudeUncertainty(Latitude uncertainty)
754      {
755        if (uncertainty != null)
756          latitudeUncertainty = uncertainty;
757        else
758          latitudeUncertainty.reset();
759      }
760      
761      /**
762       * Sets the uncertainty level in the distance of this position.
763       * <p>
764       * Note that, unless {@code uncertainty} is <i>null</i>, this position
765       * will hold a reference to {@code uncertainty}.  That is, this method
766       * will not make a copy of {@code uncertainty}.  This means that any
767       * changes made to {@code uncertainty} will be reflected here.  Note, too,
768       * that this method is not strictly required.  Clients could instead
769       * call {@link #getDistanceUncertainty} and operate on the returned
770       * object.</p>
771       * 
772       * @param uncertainty the uncertainty level in the distance of
773       *                    this position. An {@code uncertainty} of <i>null</i>
774       *                    will be treated as an uncertainty of zero.
775       */
776      public void setDistanceUncertainty(Distance uncertainty)
777      {
778        if (uncertainty != null)
779          distanceUncertainty = uncertainty;
780        else
781          distanceUncertainty.reset();
782      }
783    
784      /**
785       * Returns the uncertainty in the longitude of this position.
786       * <p>
787       * The returned object is guaranteed to be non-null and is also a reference
788       * to the internal uncertainty held by this position.  This means any changes
789       * made by clients to the returned object will be reflected in this
790       * position.</p>
791       * 
792       * @return the uncertainty in the longitude of this position.
793       */
794      @Override
795      public Longitude getLongitudeUncertainty()
796      {
797        return longitudeUncertainty;
798      }
799      
800      /**
801       * Returns the uncertainty in the latitude of this position.
802       * <p>
803       * The returned object is guaranteed to be non-null and is also a reference
804       * to the internal uncertainty held by this position.  This means any changes
805       * made by clients to the returned object will be reflected in this
806       * position.</p>
807       * 
808       * @return the uncertainty in the latitude of this position.
809       */
810      @Override
811      public Latitude getLatitudeUncertainty()
812      {
813        return latitudeUncertainty;
814      }
815      
816      /**
817       * Returns the uncertainty in the distance of this position.
818       * <p>
819       * The returned object is guaranteed to be non-null and is also a reference
820       * to the internal uncertainty held by this position.  This means any changes
821       * made by clients to the returned object will be reflected in this
822       * position.</p>
823       * 
824       * @return the uncertainty in the distance of this position.
825       */
826      @Override
827      public Distance getDistanceUncertainty()
828      {
829        return distanceUncertainty;
830      }
831    
832      //============================================================================
833      // 
834      //============================================================================
835      
836      /**
837       *  Returns a copy of this polynomial position.
838       *  <p>
839       *  If anything goes wrong during the cloning procedure,
840       *  a {@code RuntimeException} will be thrown.</p>
841       */
842      @Override
843      public PolynomialPosition clone()
844      {
845        PolynomialPosition clone = null;
846    
847        try
848        {
849          //This line takes care of the primitive fields properly
850          clone = (PolynomialPosition)super.clone();
851          
852          clone.referenceTime = (Date)this.referenceTime.clone();
853          clone.validTime     = this.validTime.clone();
854          
855          clone.longitudeAtTimeZero = this.longitudeAtTimeZero.clone();
856          clone.latitudeAtTimeZero  = this.latitudeAtTimeZero.clone();
857          clone.distanceAtTimeZero  = this.distanceAtTimeZero.clone();
858          
859          clone.longitudeMotion = this.longitudeMotion.clone();
860          clone.latitudeMotion  = this.latitudeMotion.clone();
861          clone.radialMotion    = this.radialMotion.clone();
862          
863          clone.longitudeUncertainty = this.longitudeUncertainty.clone();
864          clone.latitudeUncertainty  = this.latitudeUncertainty.clone();
865          clone.distanceUncertainty  = this.distanceUncertainty.clone();
866        }
867        catch (Exception ex)
868        {
869          throw new RuntimeException(ex);
870        }
871        
872        return clone;
873      }
874      
875      /** Returns <i>true</i> if {@code o} is equal to this position. */
876      @Override
877      public boolean equals(Object o)
878      {
879        //Quick exit if o is this
880        if (o == this)
881          return true;
882        
883        //Quick exit if super class determines not equal
884        if (!super.equals(o))
885          return false;
886        
887        //Super class determined o is non-null & of same class
888        PolynomialPosition other = (PolynomialPosition)o;
889    
890        return other.referenceTime.equals(this.referenceTime) &&
891               other.validTime.equals    (this.validTime    ) &&
892    
893               other.longitudeAtTimeZero.equals(this.longitudeAtTimeZero) &&
894               other.latitudeAtTimeZero.equals (this.latitudeAtTimeZero ) &&
895               other.distanceAtTimeZero.equals (this.distanceAtTimeZero ) &&
896    
897               other.longitudeMotion.equals(this.longitudeMotion) &&
898               other.latitudeMotion.equals (this.latitudeMotion ) &&
899               other.radialMotion.equals   (this.radialMotion   ) &&
900    
901               other.longitudeVelocityUnits.equals(this.longitudeVelocityUnits) &&
902               other.latitudeVelocityUnits.equals (this.latitudeVelocityUnits ) &&
903               other.radialVelocityUnits.equals   (this.radialVelocityUnits   ) &&
904    
905               other.longitudeUncertainty.equals(this.longitudeUncertainty) &&
906               other.latitudeUncertainty.equals (this.latitudeUncertainty ) &&
907               other.distanceUncertainty.equals (this.distanceUncertainty );
908      }
909    
910      /** Returns a hash code value for this position. */
911      @Override
912      public int hashCode()
913      {
914        //Taken from the Effective Java book by Joshua Bloch.
915        //The constants 17 & 37 are arbitrary & carry no meaning.
916        
917        //You MUST keep this method in synch with equals()
918        
919        int result = super.hashCode();
920    
921        result = 37 * result + referenceTime.hashCode();
922        result = 37 * result + validTime.hashCode();
923    
924        result = 37 * result + longitudeAtTimeZero.hashCode();
925        result = 37 * result + latitudeAtTimeZero.hashCode();
926        result = 37 * result + distanceAtTimeZero.hashCode();
927    
928        result = 37 * result + longitudeMotion.hashCode();
929        result = 37 * result + latitudeMotion.hashCode();
930        result = 37 * result + radialMotion.hashCode();
931    
932        result = 37 * result + longitudeVelocityUnits.hashCode();
933        result = 37 * result + latitudeVelocityUnits.hashCode();
934        result = 37 * result + radialVelocityUnits.hashCode();
935    
936        result = 37 * result + longitudeUncertainty.hashCode();
937        result = 37 * result + latitudeUncertainty.hashCode();
938        result = 37 * result + distanceUncertainty.hashCode();
939        
940        return result;
941      }
942      
943      /**
944       * Compares this position to {@code other} for order.
945       * <p>
946       * One position is deemed to be "less than" the other if it has
947       * a valid time range that is less than that of the other.
948       * In the case that two positions have the same valid time range,
949       * the reference time is used as a tie-breaker.  If these are the
950       * same other attributes are compared until a difference is found.
951       * If none are found, the return value is zero.</p>
952       * 
953       * @param other the time interval to which this one is compared.
954       * 
955       * @return a negative integer, zero, or a positive integer as this interval
956       *         is less than, equal to, or greater than the other interval.
957       */
958      public int compareTo(PolynomialPosition other)
959      {
960        //TODO rethink comparison for all sky positions?
961        
962        //First compare the valid time intervals
963        int answer = this.validTime.compareTo(other.validTime);
964        if (answer != 0)
965          return answer;
966        
967        //Reference time
968        answer = this.referenceTime.compareTo(other.referenceTime);
969        if (answer != 0)
970          return answer;
971        
972        //Epoch
973        answer = this.getEpoch().compareTo(other.getEpoch());
974        if (answer != 0)
975          return answer;
976        
977        //R.A. t=0
978        answer = this.longitudeAtTimeZero.compareTo(other.longitudeAtTimeZero);
979        if (answer != 0)
980          return answer;
981        
982        //Latitude t=0
983        answer = this.latitudeAtTimeZero.compareTo(other.latitudeAtTimeZero);
984        if (answer != 0)
985          return answer;
986        
987        //Distance t=0
988        answer = this.distanceAtTimeZero.compareTo(other.distanceAtTimeZero);
989        if (answer != 0)
990          return answer;
991        
992        //R.A. motion
993        answer = this.longitudeMotion.compareTo(other.longitudeMotion);
994        if (answer != 0)
995          return answer;
996        
997        //Latitude motion
998        answer = this.latitudeMotion.compareTo(other.latitudeMotion);
999        if (answer != 0)
1000          return answer;
1001        
1002        //Distance motion
1003        answer = this.radialMotion.compareTo(other.radialMotion);
1004        if (answer != 0)
1005          return answer;
1006        
1007        //R.A. uncertainty
1008        answer = this.longitudeUncertainty.compareTo(other.longitudeUncertainty);
1009        if (answer != 0)
1010          return answer;
1011        
1012        //Latitude uncertainty
1013        answer = this.latitudeUncertainty.compareTo(other.latitudeUncertainty);
1014        if (answer != 0)
1015          return answer;
1016        
1017        //Distance uncertainty
1018        answer = this.distanceUncertainty.compareTo(other.distanceUncertainty);
1019        if (answer != 0)
1020          return answer;
1021        
1022        //Coordinate system
1023        answer = this.getCoordinateSystem().compareTo(other.getCoordinateSystem());
1024        if (answer != 0)
1025          return answer;
1026       
1027        //No differences found
1028        return 0;
1029      }
1030    }