001    package edu.nrao.sss.model.project.scan;
002    
003    import javax.xml.bind.annotation.XmlTransient;
004    
005    import edu.nrao.sss.measure.Angle;
006    import edu.nrao.sss.measure.TimeDuration;
007    
008    /**
009     * A position used by a {@link PointingScan}.
010     * <p>
011     * This position may be used as either an offset or an absolute position
012     * by its containing client.  Likewise, the interpretation
013     * of the position angles (as declinations, altitudes, or latitudes,
014     * for example) is left to the clients of this class.</p>
015     * <p>
016     * <b>CVS Info:</b>
017     * <table style="margin-left:2em">
018     *   <tr><td>$Revision: 1494 $</td></tr>
019     *   <tr><td>$Date: 2008-08-14 13:51:17 -0600 (Thu, 14 Aug 2008) $</td></tr>
020     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
021     * </table></p>
022     *  
023     * @author David M. Harland
024     * @since 2006-07-10
025     */
026    public class PointingPosition
027      implements Cloneable
028    {
029      private static final ScanTimeType DEFAULT_TYPE =
030        ScanTimeType.ON_SOURCE_SIDEREAL;
031      
032      private Angle        longitude;
033      private Angle        latitude;
034      private TimeDuration timeAtPosition;
035      private ScanTimeType durationType;
036    
037      /** Creates a new instance. */
038      public PointingPosition()
039      {
040        longitude      = new Angle();
041        latitude       = new Angle();
042        timeAtPosition = new TimeDuration();
043        durationType   = DEFAULT_TYPE;
044      }
045    
046      //============================================================================
047      // PERSISTED PROPERTIES
048      //============================================================================
049    
050      /**
051       * Sets the latitudinal angle of this position.
052       * <p>
053       * If {@code latitude} is <i>null</i>, it will be treated as
054       * an non-null latitude of size zero.</p>
055       * 
056       * @param latitude the latitudinal angle of this position.
057       */
058      public void setLatitude(Angle latitude)
059      {
060        this.latitude = (latitude == null) ? new Angle() : latitude;
061      }
062    
063      /**
064       * Returns the latitudinal angle of this position.
065       * <p>
066       * The returned value is guaranteed to be non-null.  It is also the 
067       * angle that is held internally by this position, so any changes
068       * made to the returned angle will be reflected in this object.</p>
069       *  
070       * @return the latitudinal angle of this position.
071       */
072      public Angle getLatitude()
073      {
074        return latitude;
075      }
076    
077      /**
078       * Sets the longitudinal angle of this position.
079       * <p>
080       * If {@code longitude} is <i>null</i>, it will be treated as
081       * an non-null longitude of size zero.</p>
082       * 
083       * @param longitude the longitudinal angle of this position.
084       */
085      public void setLongitude(Angle longitude)
086      {
087        this.longitude = (longitude == null) ? new Angle() : longitude;
088      }
089    
090      /**
091       * Returns the longitudinal angle of this position.
092       * <p>
093       * The returned value is guaranteed to be non-null.  It is also the 
094       * angle that is held internally by this position, so any changes
095       * made to the returned angle will be reflected in this object.</p>
096       *  
097       * @return the longitudinal angle of this position.
098       */
099      public Angle getLongitude()
100      {
101        return longitude;
102      }
103    
104      /**
105       * Sets the amount of time that the containing scan should spend at this
106       * position.
107       * <p>
108       * If {@code duration} is <i>null</i>, it will be treated as
109       * an non-null duration of size zero.</p>
110       * 
111       * @param duration the amount of time that should be spent at this position.
112       */
113      public void setTimeAtPosition(TimeDuration duration)
114      {
115        timeAtPosition = (duration == null) ? new TimeDuration() : duration;
116      }
117    
118      /**
119       * Returns the amount of time that the containing scan should spend at this
120       * position.
121       * <p>
122       * The returned value is guaranteed to be non-null.  It is also the 
123       * duration that is held internally by this offset, so any changes
124       * made to the returned duration will be reflected in this object.</p>
125       *  
126       * @return the amount of time that should be spent at this position.
127       */
128      public TimeDuration getTimeAtPosition()
129      {
130        return timeAtPosition;
131      }
132    
133      /**
134       * Sets a new duration type for this position.
135       * 
136       * @param newType
137       *   the new duration type for this position.
138       *   If this value is <i>null</i>, the default duration type of
139       *   <tt>ON_SOURCE_SIDEREAL</tt> will be used.
140       *   This value must have its <tt>isDuration()</tt> method
141       *   return <i>true</i>.  If it does not, an
142       *   <tt>IllegalArgumentException</tt> is thrown.
143       *   
144       * @throws IllegalArgumentException
145       *   if {@code newType} is not a duration type.
146       */
147      public void setDurationType(ScanTimeType newType)
148      {
149        if (newType == null)
150          newType = DEFAULT_TYPE;
151        
152        if (!newType.isDuration())
153          throw new IllegalArgumentException("newType '" + newType +
154            "' must be a duration type.");
155        
156        durationType = newType;
157      }
158      
159      /**
160       * Returns the type of duration used by this position.
161       * This method helps clients interpret the value returned by
162       * {@link #getTimeAtPosition()}.
163       * <p>
164       * The returned type will be non-null and its <tt>isDuration()</tt> method
165       * will always return <i>true</i>.</p>
166       * 
167       * @return
168       *   the type of duration used by this position.
169       */
170      public ScanTimeType getDurationType()
171      {
172        return durationType;
173      }
174      
175      //============================================================================
176      // CONVENIENCE METHODS
177      //============================================================================
178      
179      /**
180       * A convenience method for setting the time spent at this position.
181       * This position will <i>not</i> hold a reference to <tt>newSpec</tt>.
182       * Instead, this method will use its
183       * {@link ScanTimeSpecification#getTimeType() getTimeType()} and 
184       * {@link ScanTimeSpecification#getDuration() getDuration()} methods
185       * to set the time at position and duration type of this object. 
186       * 
187       * @param newSpec
188       *   the provider of the time at position and duration type values for
189       *   this position.  A value of <i>null</i> will result in a
190       *   {@code NullPointerException}.
191       * 
192       * @throws IllegalArgumentException
193       *   if the time type held by {@code newSpec} is not a duration type.
194       */
195      @XmlTransient
196      public void setTimeSpec(ScanTimeSpecification newSpec)
197      {
198        setDurationType(newSpec.getTimeType());
199        setTimeAtPosition(newSpec.getDuration());
200      }
201      
202      /**
203       * A convenience method for fetching the time spent at this position.
204       * The returned object is guaranteed to be non-null.
205       * It is not referenced internally by this position, so any changes made
206       * to it by clients will not affect this object.
207       * 
208       * @return
209       *   the time at offset and duration type of this offset.
210       */
211      public ScanTimeSpecification getTimeSpec()
212      {
213        ScanTimeSpecification spec = new ScanTimeSpecification();
214        
215        spec.set(durationType, timeAtPosition);
216        
217        return spec;
218      }
219    
220      //============================================================================
221      // 
222      //============================================================================
223    
224      /**
225       * Returns a text representation of this pointing position.
226       * The returned string is of the form <i>longitude,latitude,timeAtPosition</i>.
227       */
228      @Override
229      public String toString()
230      {
231        StringBuilder buff = new StringBuilder();
232        
233        buff.append(longitude);
234        buff.append(',').append(latitude);
235        buff.append(',').append(timeAtPosition);
236        buff.append(',').append(durationType);
237    
238        return buff.toString();
239      }
240    
241      /**
242       *  Returns a position that is a copy of this one.
243       *  <p>
244       *  If anything goes wrong during the cloning procedure,
245       *  a {@code RuntimeException} will be thrown.</p>
246       */
247      @Override
248      public PointingPosition clone()
249      {
250        PointingPosition clone = null;
251    
252        try
253        {
254          clone = (PointingPosition)super.clone();
255          
256          clone.latitude       = this.latitude.clone();
257          clone.longitude      = this.longitude.clone();
258          clone.timeAtPosition = this.timeAtPosition.clone();
259        }
260        catch (Exception ex)
261        {
262          throw new RuntimeException(ex);
263        }
264        
265        return clone;
266      }
267      
268      /** Returns <i>true</i> if {@code o} is equal to this position. */
269      @Override
270      public boolean equals(Object o)
271      {
272        //Quick exit if o is null
273        if (o == null)
274          return false;
275        
276        //Quick exit if o is this object
277        if (o == this)
278          return true;
279        
280        //Quick exit if classes are different
281        if (!o.getClass().equals(this.getClass()))
282          return false;
283        
284        //A safe cast if we got this far
285        PointingPosition other = (PointingPosition)o;
286        
287        //Compare attributes
288        return
289          this.durationType.equals(other.durationType) &&
290          this.latitude.equals(other.latitude) &&
291          this.longitude.equals(other.longitude) &&
292          this.timeAtPosition.equals(other.timeAtPosition);
293      }
294    
295      /** Returns a hash code value for this position. */
296      @Override
297      public int hashCode()
298      {
299        //Taken from the Effective Java book by Joshua Bloch.
300        //The constants 17 & 37 are arbitrary & carry no meaning.
301        int result = 17;
302        
303        result = 37 * result + latitude.hashCode();
304        result = 37 * result + longitude.hashCode();
305        result = 37 * result + durationType.hashCode();
306        result = 37 * result + timeAtPosition.hashCode();
307        
308        return result;
309      }
310    }