001    package edu.nrao.sss.astronomy;
002    
003    import java.util.ArrayList;
004    import java.util.Collections;
005    import java.util.Comparator;
006    import java.util.Date;
007    import java.util.List;
008    
009    import javax.xml.bind.annotation.XmlElement;
010    import javax.xml.bind.annotation.XmlRootElement;
011    import javax.xml.bind.annotation.XmlTransient;
012    
013    import edu.nrao.sss.geom.EarthPosition;
014    import edu.nrao.sss.geom.SphericalPosition;
015    import edu.nrao.sss.measure.Angle;
016    import edu.nrao.sss.measure.Distance;
017    import edu.nrao.sss.measure.Latitude;
018    import edu.nrao.sss.measure.LocalSiderealTime;
019    import edu.nrao.sss.measure.Longitude;
020    
021    /**
022     * A table of {@link PolynomialPosition polynomial positions}.
023     * <p>
024     * This table is sorted according to the natural order of
025     * {@code PolynomialPosition}, which will result in positions with the
026     * earliest valid time intervals preceding those with later intervals.
027     * This table is kept in sorted order at all times.</p>
028     * <p>
029     * <b>Version Info:</b>
030     * <table style="margin-left:2em">
031     *   <tr><td>$Revision: 1156 $</td></tr>
032     *   <tr><td>$Date: 2008-03-12 11:43:13 -0600 (Wed, 12 Mar 2008) $</td></tr>
033     *   <tr><td>$Author: dharland $</td></tr>
034     * </table></p>
035     * 
036     * @author David M. Harland
037     * @since 2007-04-17
038     */
039    @XmlRootElement
040    public class PolynomialPositionTable
041      implements SkyPosition
042    {
043      @XmlElement(name="polynomialPosition")
044      private List<PolynomialPosition> entries;
045    
046      /** Creates a new instance. */
047      public PolynomialPositionTable()
048      {
049        initialize();
050        
051        entries = new ArrayList<PolynomialPosition>();
052      }
053    
054      private void initialize()
055      {
056        //id = Identifiable.UNIDENTIFIED;
057      }
058    
059      /**
060       * Clears all entries from this table.
061       * A reset table is equivalent to a new table created via the no-argument
062       * constructor.
063       */
064      public void reset()
065      {
066        initialize();
067        
068        entries.clear();
069      }
070      
071      /* (non-Javadoc)
072       * @see edu.nrao.sss.astronomy.SkyPosition#isMoving()
073       */
074      public boolean isMoving()
075      {
076        int entryCount = entries.size();
077        
078        if      (entryCount == 0)  return false;
079        else if (entryCount  > 1)  return true;
080        else   /*entryCount == 1*/ return entries.get(0).isMoving();  
081      }
082      
083      //============================================================================
084      // 
085      //============================================================================
086      
087      /**
088       * Sets the coordinate system of all entries in this table.
089       * If {@code newSystem} is <i>null</i>, this method does nothing.
090       * @param newSystem the new coordinate system for all entries in this table.
091       */
092      @XmlTransient
093      public void setCoordinateSystem(CelestialCoordinateSystem newSystem)
094      {
095        if (newSystem != null)
096          for (PolynomialPosition entry : entries)
097            entry.setCoordinateSystem(newSystem);
098      }
099      
100      /**
101       * Sets the epoch of all entries in this table.
102       * If {@code newEpoch} is <i>null</i>, this method does nothing.
103       * @param newEpoch the new epoch for all entries in this table.
104       */
105      @XmlTransient
106      public void setEpoch(Epoch newEpoch)
107      {
108        if (newEpoch != null)
109          for (PolynomialPosition entry : entries)
110            entry.setEpoch(newEpoch);
111      }
112      
113      /**
114       * Sets the origin of information of all entries in this table.
115       * If {@code newOrigin} is <i>null</i>, this method does nothing.
116       * @param newOrigin the new origin of information for all entries
117       *                  in this table.
118       */
119      @XmlTransient
120      public void setOriginOfInformation(String newOrigin)
121      {
122        if (newOrigin != null)
123          for (PolynomialPosition entry : entries)
124            entry.setOriginOfInformation(newOrigin);
125      }
126      
127      //============================================================================
128      // ADDING, REMOVING, FETCHING, & REPLACING ENTRIES
129      //============================================================================
130      
131      //OLD Philosophies:
132      //
133      //  1. Store and return clones.  (We return actual objects only if those
134      //     objects were removed from this table.)
135      //
136      //  2. Sort ASAP -- as soon entries is changed.
137      
138      
139      //NEW Philosophies:
140      //
141      //  1. Store and retrieve actual references.
142      //
143      //  2. Don't bother keeping internally sorted list
144      
145      
146      /**
147       * Adds {@code newPosition} to this table.
148       * <p>
149       * If {@code newPosition} is <i>null</i>, this method does nothing.
150       * Otherwise this table will hold a reference to {@code newPosition}.
151       * This means that any changes made to {@code newPosition} after
152       * a call to this method will be reflected in this object.</p>
153       *  
154       * @param newPosition a new position to be added to this table.
155       * @return <i>true</i> if {@code newPosition} was added to this table.
156       */
157      public boolean add(PolynomialPosition newPosition)
158      {
159        return (newPosition == null) ? false : entries.add(newPosition);
160      }
161      
162      /**
163       * Removes the first occurrence of the unwanted position from this table.
164       * @param unwantedPosition a position to be removed from this table.
165       * @return <i>true</i> if the unwanted position was removed.
166       */
167      public boolean remove(PolynomialPosition unwantedPosition)
168      {
169        return (unwantedPosition == null) ? false
170                                          : entries.remove(unwantedPosition);
171      }
172      
173      /**
174       * Removes the {@code index}<sup>th</sup> position from this table.
175       * @param index to index of the position to be removed.
176       * @return the position that had been at {@code index}.
177       */
178      public PolynomialPosition remove(int index)
179      {
180        return entries.remove(index);
181      }
182      
183      /**
184       * Replaces the position currently at {@code index} with
185       * {@code replacement}.
186       * <p>
187       * If {@code newPosition} is <i>null</i>, this method does nothing.
188       * Otherwise this table will hold a reference to {@code newPosition}.
189       * This means that any changes made to {@code newPosition} after
190       * a call to this method will be reflected in this object.</p>
191       * 
192       * @param index the index of the position to be replaced.
193       * @param replacement the new position for the {@code index}<sup>th</sup>
194       *                    slot in this table.
195       * @return the position that had been at {@code index}.
196       */
197      public PolynomialPosition set(int index, PolynomialPosition replacement)
198      {
199        PolynomialPosition priorEntry;
200        
201        if (replacement != null)
202        {
203          priorEntry = entries.get(index);
204          entries.set(index, replacement);
205        }
206        else
207        {
208          priorEntry = null;
209        }
210        
211        return priorEntry;
212      }
213      
214      /**
215       * Returns the position at {@code index}.
216       * <p>
217       * Note that the returned position is the one actually held by this table.
218       * This means that any changes made to it after
219       * a call to this method will be reflected in this object.</p>
220       * 
221       * @param index the index of the desired position.
222       * @return a reference to the position at {@code index}.
223       */
224      public PolynomialPosition get(int index)
225      {
226        return entries.get(index);
227      }
228      
229      /**
230       * Returns a position for which {@code time} is a valid time.
231       * <p>
232       * If this table has no such position, <i>null</i> is returned.
233       * If this table has <i>more</i> than one such position, the one held
234       * at the index of lowest value will be returned.  Just which position
235       * that is depends on whether, and how, this table was most recently
236       * sorted by its clients.</p>
237       * <p>
238       * Note that the returned position is the one actually held by this table.
239       * This means that any changes made to it after
240       * a call to this method will be reflected in this object.</p>
241       * 
242       * @param time the time for which a position is desired.  The returned
243       *             position, unless it is <i>null</i> will have a valid time
244       *             interval that contains this value.
245       *             
246       * @return the a position for which {@code time} is a valid time, or
247       *         <i>null</i> if this table has no such position.
248       */
249      public PolynomialPosition get(Date time)
250      {
251        for (PolynomialPosition entry : entries)
252          if (entry.isValidFor(time))
253            return entry;
254        
255        return null;
256      }
257      
258      /**
259       * Returns a new list that contains references to all the entries in this
260       * table.  The entries are ordered in the list in the same way they are
261       * ordered in this table.
262       * 
263       * @return a new list of this table's entries.
264       */
265      public List<PolynomialPosition> getEntries()
266      {
267        return new ArrayList<PolynomialPosition>(entries);
268      }
269      
270      //============================================================================
271      // 
272      //============================================================================
273      
274      /**
275       * Returns the number of entries in this table.
276       * @return the size of this table.
277       */
278      public int size()
279      {
280        return entries.size();
281      }
282      
283      /**
284       * Returns <i>true</i> if this table has one or more positions for which
285       * {@code time} is a valid time.
286       * 
287       * @param time the time for which a valid position is sought.
288       * 
289       * @return <i>true</i> if this table has one or more positions for which
290       *         {@code time} is a valid time.
291       */
292      public boolean isValidFor(Date time)
293      {
294        for (PolynomialPosition entry : entries)
295          if (entry.isValidFor(time))
296            return true;
297        
298        return false;
299      }
300      
301      /**
302       * Returns <i>true</i> if the valid time intervals of the positions of this
303       * table do not overlap and do not contain gaps.  Note that by this definition
304       * an empty table is well formed.
305       * 
306       * @return <i>true</i> if the valid time intervals of the positions of this
307       *         table are contiguous.
308       */
309      public boolean isWellFormed()
310      {
311        ArrayList<PolynomialPosition> tempList =
312          new ArrayList<PolynomialPosition>(entries);
313        
314        Collections.sort(tempList);
315        
316        //This code depends on the PolynomialPosition's "natural" ordering
317        //wherein positions with earlier valid time ranges precede those with later.
318        for (int e=tempList.size()-1; e > 0; e--)
319        {
320          PolynomialPosition later   = tempList.get(e);
321          PolynomialPosition earlier = tempList.get(e-1);
322          
323          if (!later.getValidTime().isContiguousWith(earlier.getValidTime()))
324            return false;
325        }
326        
327        return true;
328      }
329      
330      /**
331       * Sorts this table so that its entries are in their natural order.
332       */
333      public void sort()
334      {
335        Collections.sort(entries);
336      }
337      
338      /**
339       * Sorts this table so that its entries are in the order dictated by
340       * {@code comparator}.
341       */
342      public void sort(Comparator<PolynomialPosition> comparator)
343      {
344        Collections.sort(entries, comparator);
345      }
346      
347      //============================================================================
348      // INTERFACE SkyPosition
349      //============================================================================
350    
351      /**
352       * Returns either the PolynomialPosition whose valid time contains
353       * {@code time} or, if there is no such position, a newly created position.
354       */
355      private SkyPosition getNonNullPosition(Date time)
356      {
357        SkyPosition position = get(time);
358        
359        if (position == null)
360          position = new SimpleSkyPosition();
361        
362        return position;
363      }
364      
365      public SkyPositionType getType()  { return SkyPositionType.POLYNOMIAL_TABLE; }
366    
367      //TODO document methods for how they behave for invalid times
368      
369      public CelestialCoordinateSystem getCoordinateSystem()
370      {
371        return getNonNullPosition(new Date()).getCoordinateSystem();
372      }
373      
374      public Epoch getEpoch()
375      {
376        return getNonNullPosition(new Date()).getEpoch();
377      }
378      
379      public String getOriginOfInformation()
380      {
381        return getNonNullPosition(new Date()).getOriginOfInformation();
382      }
383    
384      public Longitude getLongitude()
385      {
386        return getLongitude(new Date());
387      }
388    
389      public Latitude getLatitude()
390      {
391        return getLatitude(new Date());
392      }
393    
394      public Distance getDistance()
395      {
396        return getDistance(new Date());
397      }
398    
399      public Longitude getLongitude(Date time)
400      {
401        return getNonNullPosition(time).getLongitude(time);
402      }
403    
404      public Latitude getLatitude(Date time)
405      {
406        return getNonNullPosition(time).getLatitude(time);
407      }
408    
409      public Distance getDistance(Date time)
410      {
411        return getNonNullPosition(time).getDistance(time);
412      }
413        
414      public Longitude getLongitudeUncertainty()
415      {
416        return getNonNullPosition(new Date()).getLongitudeUncertainty();
417      }
418    
419      public Latitude getLatitudeUncertainty()
420      {
421        return getNonNullPosition(new Date()).getLatitudeUncertainty();
422      }
423    
424      public Distance getDistanceUncertainty()
425      {
426        return getNonNullPosition(new Date()).getDistanceUncertainty();
427      }
428      
429      /* (non-Javadoc)
430       * @see SphericalPosition#getAngularSeparation(SphericalPosition)
431       */
432      public Angle getAngularSeparation(SphericalPosition other)
433      {
434        return getAngularSeparation(other, new Date());
435      }
436    
437      /* (non-Javadoc)
438       * @see SphericalPosition#getAngularSeparation(SphericalPosition, Date)
439       */
440      public Angle getAngularSeparation(SphericalPosition other, Date time)
441      {
442        return getNonNullPosition(time).getAngularSeparation(other, time);
443      }
444      
445      //============================================================================
446      // 
447      //============================================================================
448      
449      /* (non-Javadoc)
450       * @see SkyPosition#toPosition(CelestialCoordinateSystem, Epoch)
451       */
452      public SkyPosition toPosition(CelestialCoordinateSystem system,
453                                    Epoch                     epoch,
454                                    EarthPosition             observer,
455                                    LocalSiderealTime         lst)
456        throws CoordinateConversionException
457      {
458        return toPosition(system, epoch, observer, lst,AbsSkyPos.DEFAULT_CONVERTER); 
459      }
460      
461      /* (non-Javadoc)
462       * @see SkyPosition#toPosition(CelestialCoordinateSystem, Epoch,
463       *                             CelestialCoordinateConverter)
464       */
465      public SkyPosition toPosition(CelestialCoordinateSystem    system,
466                                    Epoch                        epoch,
467                                    EarthPosition                observer,
468                                    LocalSiderealTime            lst,
469                                    CelestialCoordinateConverter converter)
470        throws CoordinateConversionException
471      {
472        return converter.createFrom(this, system, epoch, observer, lst);
473      }
474    
475      //============================================================================
476      // 
477      //============================================================================
478      
479      /**
480       *  Returns a copy of this table.
481       *  <p>
482       *  If anything goes wrong during the cloning procedure,
483       *  a {@code RuntimeException} will be thrown.</p>
484       */
485      @Override
486      public PolynomialPositionTable clone()
487      {
488        PolynomialPositionTable clone = null;
489        
490        try
491        {
492          //This line takes care of the primitive & immutable fields properly
493          clone = (PolynomialPositionTable)super.clone();
494    
495          //Clone gets its own list, populated with clones from this table's list
496          clone.entries = new ArrayList<PolynomialPosition>();
497          for (PolynomialPosition thisEntry : this.entries)
498            clone.entries.add(thisEntry.clone());
499        }
500        catch (Exception ex)
501        {
502          throw new RuntimeException(ex);
503        }
504    
505        return clone;
506      }
507    
508      /** Returns <i>true</i> if {@code o} is equal to this table. */
509      @Override
510      public boolean equals(Object o)
511      {
512        //Quick exit if o is this
513        if (o == this)
514          return true;
515        
516        //Quick exit if o is null
517        if (o == null)
518          return false;
519        
520        //Quick exit if classes are different
521        if (!o.getClass().equals(this.getClass()))
522          return false;
523        
524        PolynomialPositionTable other = (PolynomialPositionTable)o;
525        
526        return this.entries.equals(other.entries);
527      }
528    
529      /** Returns a hash code value for this table. */
530      @Override
531      public int hashCode()
532      {
533        //Taken from the Effective Java book by Joshua Bloch.
534        //The constants 17 & 37 are arbitrary & carry no meaning.
535        int result = 17;
536    
537        //You MUST keep this method in synch with equals()
538        
539        result = 37 * result + entries.hashCode();
540        
541        return result;
542      }
543    }