001    package edu.nrao.sss.model.project.scan;
002    
003    import javax.xml.bind.annotation.XmlElement;
004    import javax.xml.bind.annotation.XmlElementRef;
005    import javax.xml.bind.annotation.XmlElementRefs;
006    import javax.xml.bind.annotation.XmlType;
007    
008    import edu.nrao.sss.astronomy.SkyPosition;
009    import edu.nrao.sss.astronomy.VelocityConvention;
010    import edu.nrao.sss.astronomy.VelocityFrame;
011    import edu.nrao.sss.measure.LinearVelocity;
012    import edu.nrao.sss.measure.LinearVelocityUnits;
013    
014    /**
015     * A grouping of a sky position, radial velocity, and rest frame for
016     * specifying Doppler tracking information for a scan.
017     * <p>
018     * <b>Version Info:</b>
019     * <table style="margin-left:2em">
020     *   <tr><td>$Revision: 2102 $</td></tr>
021     *   <tr><td>$Date: 2009-03-12 08:49:53 -0600 (Thu, 12 Mar 2009) $</td></tr>
022     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
023     * </table></p>
024     * 
025     * @author David M. Harland
026     * @since 2008-09-10
027     */
028    @XmlType(propOrder = {"position", "xmlVelocity"})
029    
030    public class ScanDopplerSpecs
031      implements Cloneable
032    {
033      private SkyPosition        dtPosition;
034      private LinearVelocity     dtVelocity;
035      private VelocityFrame      dtRestFrame;
036      private VelocityConvention dtVelConv;
037      
038      /**
039       * Creates a new specification with a <i>null</i> position and velocity.
040       */
041      public ScanDopplerSpecs()
042      {
043        setPosition(null);
044        setVelocity(null, null, null);
045      }
046      
047      /**
048       * Creates a new specification with the given properties.
049       * 
050       * @param sourcePosition
051       *   See {@link #setPosition(SkyPosition)}.
052       *   
053       * @param sourceVelocity
054       *   See {@link #setVelocity(LinearVelocity, VelocityFrame, VelocityConvention)}.
055       *   
056       * @param restFrame
057       *   See {@link #setVelocity(LinearVelocity, VelocityFrame, VelocityConvention)}.
058       *   
059       * @param velocityConvention
060       *   See {@link #setVelocity(LinearVelocity, VelocityFrame, VelocityConvention)}.
061       */
062      public ScanDopplerSpecs(SkyPosition        sourcePosition,
063                              LinearVelocity     sourceVelocity,
064                              VelocityFrame      restFrame,
065                              VelocityConvention velocityConvention)
066      {
067        setPosition(sourcePosition);
068        setVelocity(sourceVelocity, restFrame, velocityConvention);
069      }
070      
071      /**
072       * Sets the sky position to use when Doppler tracking.
073       * 
074       * @param newPosition
075       *   a position to use for Doppler tracking calculations.
076       *   A value of <i>null</i> may be used as a signal to the scan
077       *   to use the position of its source.
078       */
079      public final void setPosition(SkyPosition newPosition)
080      {
081        dtPosition = newPosition;
082      }
083      
084      /**
085       * Sets the source velocity to use when Doppler tracking.
086       * 
087       * @param newVelocity
088       *   a source velocity to use for Doppler tracking calculations.
089       *   Note that this is only the radial velocity of a source relative
090       *   to a frame of rest.  It does not account for the motion of Earth.
091       *   A value of <i>null</i> may be used as a signal to the scan
092       *   to use the velocity of its source.
093       *   
094       * @param newFrame
095       *   the rest frame against which {@code newVelocity} is measured.
096       *   
097       * @param newConvention
098       *   the convention used to determine {@code newVelocity}.
099       *   
100       * @throws IllegalArgumentException
101       *   if exactly one of the parameters is <i>null</i>.
102       */
103      public final void setVelocity(LinearVelocity     newVelocity,
104                                    VelocityFrame      newFrame,
105                                    VelocityConvention newConvention)
106      {
107        //Must either be all null or all non-null
108        if ((newVelocity == null && newFrame == null && newConvention == null) ||
109            (newVelocity != null && newFrame != null && newConvention != null))
110        {
111          dtVelocity  = newVelocity;
112          dtRestFrame = newFrame;
113          dtVelConv   = newConvention;
114        }
115        else
116        {
117          throw new IllegalArgumentException(
118            "The velocity, frame, and convention must either all be null or all be non-null.");
119        }
120      }
121      
122      /**
123       * The sky position to use for Doppler tracking.
124       * The returned value may be <i>null</i>, which the scan will take
125       * as a signal to use the position of its source.
126       * 
127       * @return the sky position to use for Doppler tracking.
128       */
129      @XmlElementRefs
130      (
131        {
132          @XmlElementRef(type=edu.nrao.sss.astronomy.SimpleSkyPosition.class),
133          @XmlElementRef(type=edu.nrao.sss.astronomy.EphemerisTable.class),
134          @XmlElementRef(type=edu.nrao.sss.astronomy.Orbit.class),
135          @XmlElementRef(type=edu.nrao.sss.astronomy.PolynomialPosition.class),
136          @XmlElementRef(type=edu.nrao.sss.astronomy.PolynomialPositionTable.class),
137          @XmlElementRef(type=edu.nrao.sss.astronomy.SolarSystemBodyPosition.class)
138        }
139      )
140      public SkyPosition getPosition()  { return dtPosition; }
141      
142      /**
143       * The source velocity to use for Doppler tracking.
144       * The returned value may be <i>null</i>, which the scan will take
145       * as a signal to use the velocity of its source.
146       * <p>
147       * Note that the returned velocity is only that of the source with
148       * respect to its {@link #getRestFrame() rest frame}.  It does
149       * <i>not</i> include the motion of Earth.</p>
150       * <p>
151       * If the returned value is <i>null</i>, the returned value of
152       * {@link #getRestFrame()} and {@link #getVelocityConvention()}
153       * will also be <i>null</i>.</p>
154       * 
155       * @return the source velocity to use for Doppler tracking.
156       */
157      public LinearVelocity getVelocity()  { return dtVelocity; }
158      
159      /**
160       * The velocity convention to use for the Doppler tracking velocity.
161       * The returned value may be <i>null</i>, which the scan will take
162       * as a signal to use the convention of its source's velocity.
163       * <p>
164       * If the returned value is <i>null</i>, the returned value of
165       * {@link #getVelocity()} and {@link #getRestFrame()}
166       * will also be <i>null</i>.</p>
167       * 
168       * @return the velocity convention to use for Doppler tracking.
169       */
170      public VelocityConvention getVelocityConvention()  { return dtVelConv; }
171      
172      /**
173       * The rest frame to use for the Doppler tracking velocity.
174       * The returned value may be <i>null</i>, which the scan will take
175       * as a signal to use the frame of its source's velocity.
176       * <p>
177       * If the returned value is <i>null</i>, the returned value of
178       * {@link #getVelocity()} and {@link #getVelocityConvention()}
179       * will also be <i>null</i>.</p>
180       * 
181       * @return the rest frame to use for the Doppler tracking velocity.
182       */
183      public VelocityFrame getRestFrame()  { return dtRestFrame; }
184    
185      //----------------------------------------------------------------------------
186      // Special JAXB code for velocity in order to keep both null or both non-null
187      //----------------------------------------------------------------------------
188      
189      private static class DopplerVelocity
190      {
191        @XmlElement LinearVelocity     radialVelocity;
192        @XmlElement VelocityFrame      restFrame;
193        @XmlElement VelocityConvention velocityConvention;
194        
195        DopplerVelocity() { }
196        
197        DopplerVelocity(LinearVelocity v, VelocityFrame f, VelocityConvention c)
198        {
199          radialVelocity     = v;
200          restFrame          = f;
201          velocityConvention = c;
202        }
203      }
204      
205      @XmlElement(name="velocity")
206      @SuppressWarnings("unused")
207      private DopplerVelocity getXmlVelocity()
208      {
209        return
210          (dtVelocity == null) ? null : new DopplerVelocity(dtVelocity, dtRestFrame,
211                                                                        dtVelConv);
212      }
213      
214      @SuppressWarnings("unused")
215      private void setXmlVelocity(DopplerVelocity dv)
216      {
217        dtVelocity  = dv.radialVelocity;
218        dtRestFrame = dv.restFrame;
219        dtVelConv   = dv.velocityConvention;
220        
221        //Velocity convention was not originally specified.  When we added it
222        //we made it an optional element in the xml for backward compatibility.
223        //This means the xml could bring us legitimate velocity info w/ a null
224        //convention.  We detect that here & override.
225        if (dv.velocityConvention == null && dtVelocity != null)
226        {
227          LinearVelocityUnits units = dtVelocity.getUnits();
228            
229          if (units.equals(LinearVelocityUnits.Z))
230            dtVelConv = VelocityConvention.REDSHIFT;
231          else
232            dtVelConv = VelocityConvention.RADIO;
233        }
234      }
235      
236      //============================================================================
237      // 
238      //============================================================================
239      
240      @Override
241      /**
242       *  Returns a copy of this specification.
243       *  <p>
244       *  If anything goes wrong during the cloning procedure,
245       *  a {@code RuntimeException} will be thrown.</p>
246       */
247      public ScanDopplerSpecs clone()
248      {
249        ScanDopplerSpecs clone = null;
250        try
251        {
252          //This line takes care of the primitive & immutable fields properly
253          clone = (ScanDopplerSpecs)super.clone();
254          
255          clone.dtPosition = (this.dtPosition == null) ? null : this.dtPosition.clone();
256          clone.dtVelocity = (this.dtVelocity == null) ? null : this.dtVelocity.clone();
257        }
258        catch (Exception ex)
259        {
260          throw new RuntimeException(ex);
261        }
262        
263        return clone;
264      }
265      
266      @Override
267      public boolean equals(Object o)
268      {
269        //Quick exit if o is this
270        if (o == this)
271          return true;
272        
273        //Quick exit if o is null
274        if (o == null)
275          return false;
276        
277        //Quick exit if classes are different
278        if (!o.getClass().equals(this.getClass()))
279          return false;
280        
281        ScanDopplerSpecs other = (ScanDopplerSpecs)o;
282        
283        return
284          objectsAreEqual(this.dtRestFrame, other.dtRestFrame) &&
285          objectsAreEqual(this.dtVelocity,  other.dtVelocity ) &&
286          objectsAreEqual(this.dtVelConv,   other.dtVelConv  ) &&
287          objectsAreEqual(this.dtPosition,  other.dtPosition );
288      }
289      
290      private boolean objectsAreEqual(Object thisOne, Object thatOne)
291      {
292        return (thisOne == null) ? (thatOne == null) : thisOne.equals(thatOne);
293      }
294    
295      @Override
296      public int hashCode()
297      {
298        //Taken from the Effective Java book by Joshua Bloch.
299        //The constants 17 & 37 are arbitrary & carry no meaning.
300        int result = 17;
301    
302        if (dtPosition != null)
303          result = 37 * result + dtPosition.hashCode();
304        
305        if (dtVelocity != null)
306          result = 37 * result + dtVelocity.hashCode();
307        
308        if (dtVelConv != null)
309          result = 37 * result + dtVelConv.hashCode();
310        
311        if (dtRestFrame != null)
312          result = 37 * result + dtRestFrame.hashCode();
313        
314        return result;
315      }
316    }