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.geom.EarthPosition;
011    import edu.nrao.sss.geom.SphericalPosition;
012    import edu.nrao.sss.measure.Angle;
013    import edu.nrao.sss.measure.Distance;
014    import edu.nrao.sss.measure.Latitude;
015    import edu.nrao.sss.measure.LocalSiderealTime;
016    import edu.nrao.sss.measure.Longitude;
017    import edu.nrao.sss.measure.TimeDuration;
018    import edu.nrao.sss.measure.TimeInterval;
019    import edu.nrao.sss.util.SourceNotFoundException;
020    
021    /**
022     * Position information for a solar system body.
023     * <p>
024     * <b>Version Info:</b>
025     * <table style="margin-left:2em">
026     *   <tr><td>$Revision: 1256 $</td></tr>
027     *   <tr><td>$Date: 2008-04-29 15:55:50 -0600 (Tue, 29 Apr 2008) $</td></tr>
028     *   <tr><td>$Author: dharland $</td></tr>
029     * </table></p>
030     * 
031     * @author David M. Harland
032     * @since 2007-04-18
033     */
034    @XmlRootElement
035    public class SolarSystemBodyPosition
036      implements SkyPosition
037    {
038      private static final String        DEFAULT_NAME    = "";
039      private static final SourceLocator DEFAULT_LOCATOR = new SkyBotResolver();
040      
041      private static final BigDecimal INITIAL_REFRESH_HOURS = new BigDecimal("0.5");
042    
043      @XmlElement private String bodyName;
044      
045      private TimeDuration  refreshRate;
046      private TimeInterval  validTime;
047      private SourceLocator locator;
048      
049      //The SkyPosition methods are delegated to this object 
050      private SkyPosition skyPos;
051      
052      /** Here for JAXB. */
053      SolarSystemBodyPosition()  { this("AUTO-GENERATED"); }
054      
055      /** Creates a new instance. */
056      public SolarSystemBodyPosition(String nameOfBody)
057      {
058        initialize();
059        
060        bodyName = (nameOfBody == null) ? DEFAULT_NAME : nameOfBody;
061        
062        refreshRate = new TimeDuration(INITIAL_REFRESH_HOURS);
063        validTime   = new TimeInterval();
064      }
065      
066      private void initialize()
067      {
068        //bodyName intentionally NOT set
069        locator  = DEFAULT_LOCATOR;
070        skyPos   = null;
071      }
072      
073      /**
074       * Resets the internals of this position, except for its body name.
075       */
076      public void reset()
077      {
078        initialize();
079        
080        refreshRate.reset();
081        validTime.reset();
082      }
083      
084      //============================================================================
085      // 
086      //============================================================================
087      
088      /**
089       * Returns the name of the solar system body this position represents.
090       * @return the name of the solar system body this position represents.
091       */
092      public String getBodyName()  { return bodyName; }
093      
094      /**
095       * Sets the locator that this position will use for determining the location
096       * of the object whose name is given by {@link #getBodyName()}.
097       * If {@code newLocator} is <i>null</i>, this method does nothing.
098       *  
099       * @param newLocator the locator to use for getting position information
100       *                   for this body.
101       */
102      public void setLocator(SourceLocator newLocator)
103      {
104        if (newLocator != null)
105          locator = newLocator;
106      }
107      
108      /**
109       * Sets the refresh rate for this position to {@code newRate}.
110       * If {@code newRate} is <i>null</i>, the refresh rate will be set to
111       * zero.
112       * <p>
113       * The refresh rate refers to the amount of time that must
114       * pass before this position will request new information from its
115       * {@link #setLocator(SourceLocator) source locator}.  Each time the source
116       * locator is successfully queried, the position and time of the query are
117       * cached.  If a subsequent request is made in the interval
118       * <tt>[queryTime...queryTime + newRate]</tt> (or in the interval
119       * <tt>[queryTime - newRate...queryTime]</tt>, the reported position is the
120       * cached position.  Clients can ensure that cached values are never used by
121       * setting the refresh rate to zero, which is the default state for this
122       * object.</p> 
123       * 
124       * @param newRate the refresh rate for locating this body's position.
125       */
126      public void setRefreshRate(TimeDuration newRate)
127      {
128        if (newRate != null)
129          refreshRate.set(newRate);
130        else
131          refreshRate.reset();
132      }
133      
134      /**
135       * Returns a copy of this position's refresh rate
136       * (see {@link #setRefreshRate(TimeDuration)} for a description).
137       * 
138       * @return a copy of this position's refresh rate.
139       */
140      @XmlTransient
141      public TimeDuration getRefreshRate()
142      {
143        return refreshRate.clone();
144      }
145      
146      /* (non-Javadoc)
147       * @see edu.nrao.sss.astronomy.SkyPosition#isMoving()
148       */
149      public boolean isMoving()  { return true; }
150      
151      //============================================================================
152      // 
153      //============================================================================
154      
155      private SkyPosition getNonNullPosition(Date time)
156      {
157        if ((skyPos == null) || !validTime.contains(time))
158          refreshPosition(time);
159        
160        return skyPos == null ? new SimpleSkyPosition() : skyPos;
161      }
162      
163      private void refreshPosition(Date time)
164      {
165        try
166        {
167          //Get the latest position
168          skyPos = locator.findPosition(bodyName);
169          
170          //Reset the valid time range
171          TimeDuration doubleRate = refreshRate.clone();
172          doubleRate.multiplyBy("2.0");
173          validTime = doubleRate.toIntervalCenteredOn(time);
174        }
175        catch (SourceNotFoundException ex)  //TODO log?
176        {
177          skyPos = null;  //Forces another lookup attempt
178        }
179      }
180      
181      //============================================================================
182      // INTERFACE SkyPosition
183      //============================================================================
184      
185      //TODO document methods for how they behave for invalid times
186      
187      public SkyPositionType getType() {return SkyPositionType.INTERNAL_EPHEMERIS;}
188    
189      public CelestialCoordinateSystem getCoordinateSystem()
190      {
191        return getNonNullPosition(new Date()).getCoordinateSystem();
192      }
193      
194      public Epoch getEpoch()
195      {
196        return getNonNullPosition(new Date()).getEpoch();
197      }
198      
199      public String getOriginOfInformation()
200      {
201        return getNonNullPosition(new Date()).getOriginOfInformation();
202      }
203    
204      //----------------------------------------------------------------------------
205      // Current-time position
206      //----------------------------------------------------------------------------
207    
208      /**
209       * Returns the current longitude of this position.
210       * <p>
211       * The returned value is guaranteed to be non-null.
212       * Note that the object returned is not held internally by this position.
213       * This means that any changes made to the returned object by clients
214       * will <i>not</i> be reflected in this position.</p>
215       * 
216       * @return the current longitude of this position.
217       */
218      public Longitude getLongitude()
219      {
220        return getLongitude(new Date());
221      }
222    
223      /**
224       * Returns the current latitude of this position.
225       * <p>
226       * The returned value is guaranteed to be non-null.
227       * Note that the object returned is not held internally by this position.
228       * This means that any changes made to the returned object by clients
229       * will <i>not</i> be reflected in this position.</p>
230       * 
231       * @return the current latitude of this position.
232       */
233      public Latitude getLatitude()
234      {
235        return getLatitude(new Date());
236      }
237    
238      /**
239       * Returns the current distance of this position.
240       * <p>
241       * The returned value is guaranteed to be non-null.
242       * Note that the object returned is not held internally by this position.
243       * This means that any changes made to the returned object by clients
244       * will <i>not</i> be reflected in this position.</p>
245       * 
246       * @return the current distance of this position.
247       */
248      public Distance getDistance()
249      {
250        return getDistance(new Date());
251      }
252    
253      /**
254       * Returns the longitude of this position at the given point in time.
255       * <p>
256       * The returned value is guaranteed to be non-null.
257       * Note that the object returned is not held internally by this position.
258       * This means that any changes made to the returned object by clients
259       * will <i>not</i> be reflected in this position.</p>
260       * 
261       * @param time the point in time for which the longitude is sought.
262       * 
263       * @return the longitude of this position at the given point in time.
264       */
265      public Longitude getLongitude(Date time)
266      {
267        return getNonNullPosition(time).getLongitude(time);
268      }
269    
270      /**
271       * Returns the latitude of this position at the given point in time.
272       * <p>
273       * The returned value is guaranteed to be non-null.
274       * Note that the object returned is not held internally by this position.
275       * This means that any changes made to the returned object by clients
276       * will <i>not</i> be reflected in this position.</p>
277       * 
278       * @param time the point in time for which the latitude is sought.
279       * 
280       * @return the latitude of this position at the given point in time.
281       */
282      public Latitude getLatitude(Date time)
283      {
284        return getNonNullPosition(time).getLatitude(time);
285      }
286    
287      /**
288       * Returns the distance of this position at the given point in time.
289       * <p>
290       * The returned value is guaranteed to be non-null.
291       * Note that the object returned is not held internally by this position.
292       * This means that any changes made to the returned object by clients
293       * will <i>not</i> be reflected in this position.</p>
294       * 
295       * @param time the point in time for which the distance is sought.
296       * 
297       * @return the distance of this position at the given point in time.
298       */
299      public Distance getDistance(Date time)
300      {
301        return getNonNullPosition(time).getDistance(time);
302      }
303        
304      /**
305       * Returns the uncertainty in the longitude of this position.
306       * <p>
307       * The returned value is guaranteed to be non-null.
308       * Note that the object returned is not held internally by this position.
309       * This means that any changes made to the returned object by clients
310       * will <i>not</i> be reflected in this position.</p>
311       * 
312       * @return the uncertainty in the longitude of this position.
313       */
314      public Longitude getLongitudeUncertainty()
315      {
316        return getNonNullPosition(new Date()).getLongitudeUncertainty();
317      }
318    
319      /**
320       * Returns the uncertainty in the latitude of this position.
321       * <p>
322       * The returned value is guaranteed to be non-null.
323       * Note that the object returned is not held internally by this position.
324       * This means that any changes made to the returned object by clients
325       * will <i>not</i> be reflected in this position.</p>
326       * 
327       * @return the uncertainty in the latitude of this position.
328       */
329      public Latitude getLatitudeUncertainty()
330      {
331        return getNonNullPosition(new Date()).getLatitudeUncertainty();
332      }
333    
334      /**
335       * Returns the uncertainty in the distance of this position.
336       * <p>
337       * The returned value is guaranteed to be non-null.
338       * Note that the object returned is not held internally by this position.
339       * This means that any changes made to the returned object by clients
340       * will <i>not</i> be reflected in this position.</p>
341       * 
342       * @return the uncertainty in the distance of this position.
343       */
344      public Distance getDistanceUncertainty()
345      {
346        return getNonNullPosition(new Date()).getDistanceUncertainty();
347      }
348      
349      /* (non-Javadoc)
350       * @see SphericalPosition#getAngularSeparation(SphericalPosition)
351       */
352      public Angle getAngularSeparation(SphericalPosition other)
353      {
354        return getAngularSeparation(other, new Date());
355      }
356      
357      /* (non-Javadoc)
358       * @see SphericalPosition#getAngularSeparation(SphericalPosition, Date)
359       */
360      public Angle getAngularSeparation(SphericalPosition other, Date time)
361      {
362        return getNonNullPosition(time).getAngularSeparation(other, time);
363      }
364    
365      //============================================================================
366      // 
367      //============================================================================
368      
369      /* (non-Javadoc)
370       * @see SkyPosition#toPosition(CelestialCoordinateSystem, Epoch)
371       */
372      public SkyPosition toPosition(CelestialCoordinateSystem system,
373                                    Epoch                     epoch,
374                                    EarthPosition             observer,
375                                    LocalSiderealTime         lst)
376        throws CoordinateConversionException
377      {
378        return toPosition(system, epoch, observer, lst,AbsSkyPos.DEFAULT_CONVERTER); 
379      }
380      
381      /* (non-Javadoc)
382       * @see SkyPosition#toPosition(CelestialCoordinateSystem, Epoch,
383       *                             CelestialCoordinateConverter)
384       */
385      public SkyPosition toPosition(CelestialCoordinateSystem    system,
386                                    Epoch                        epoch,
387                                    EarthPosition                observer,
388                                    LocalSiderealTime            lst,
389                                    CelestialCoordinateConverter converter)
390        throws CoordinateConversionException
391      {
392        return converter.createFrom(this, system, epoch, observer, lst);
393      }
394    
395      //============================================================================
396      // 
397      //============================================================================
398      
399      /**
400       *  Returns a copy of this body.
401       *  <p>
402       *  The object returned is almost a deep-copy of this position.
403       *  The source locator used by the returned copy, however, is a reference to
404       *  the same locator used by this position.</p>
405       *  <p>
406       *  If anything goes wrong during the cloning procedure,
407       *  a {@code RuntimeException} will be thrown.</p>
408       */
409      @Override
410      public SolarSystemBodyPosition clone()
411      {
412        SolarSystemBodyPosition clone = null;
413        
414        try
415        {
416          //This line takes care of the primitive & immutable fields properly
417          clone = (SolarSystemBodyPosition)super.clone();
418    
419          clone.refreshRate = this.refreshRate.clone();
420          clone.validTime   = this.validTime.clone();
421          
422          if (this.skyPos != null)
423            clone.skyPos = this.skyPos.clone();
424          
425          //Intentionally NOT cloned
426          clone.locator = DEFAULT_LOCATOR;
427        }
428        catch (Exception ex)
429        {
430          throw new RuntimeException(ex);
431        }
432    
433        return clone;
434      }
435    
436      /** Returns <i>true</i> if {@code o} is equal to this body. */
437      @Override
438      public boolean equals(Object o)
439      {
440        //Quick exit if o is this
441        if (o == this)
442          return true;
443        
444        //Quick exit if o is null
445        if (o == null)
446          return false;
447        
448        //Quick exit if classes are different
449        if (!o.getClass().equals(this.getClass()))
450          return false;
451    
452        SolarSystemBodyPosition other = (SolarSystemBodyPosition)o;
453    
454        //The following properties are intentionally absent:
455        //  refreshRate, validTime, locator, skyPos
456    
457        return this.bodyName.equalsIgnoreCase(other.bodyName);
458      }
459    
460      /** Returns a hash code value for this body. */
461      @Override
462      public int hashCode()
463      {
464        //Taken from the Effective Java book by Joshua Bloch.
465        //The constants 17 & 37 are arbitrary & carry no meaning.
466        int result = 17;
467        
468        //You MUST keep this method in synch with equals()
469        
470        result = 37 * result + bodyName.hashCode();
471        
472        return result;
473      }
474    }