001    package edu.nrao.sss.measure;
002    
003    import java.math.BigDecimal;
004    
005    import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS;
006    
007    import static edu.nrao.sss.measure.DistanceUnits.KILOMETER;
008    import static edu.nrao.sss.measure.DistanceUnits.METER;
009    import static edu.nrao.sss.measure.FrequencyUnits.GIGAHERTZ;
010    import static edu.nrao.sss.measure.FrequencyUnits.KILOHERTZ;
011    import static edu.nrao.sss.measure.FrequencyUnits.HERTZ;
012    import static edu.nrao.sss.measure.LinearVelocityUnits.KILOMETERS_PER_SECOND;
013    
014    /**
015     * A wave with properties of velocity, wavelength, and frequency.
016     * <p>
017     * <b>Version Info:</b>
018     * <table style="margin-left:2em">
019     *   <tr><td>$Revision: 1490 $</td></tr>
020     *   <tr><td>$Date: 2008-08-13 16:38:27 -0600 (Wed, 13 Aug 2008) $</td></tr>
021     *   <tr><td>$Author: dharland $</td></tr>
022     * </table></p>
023     * 
024     * @author David M. Harland
025     * @since 2006-12-05
026     */
027    public class Wave
028    {
029      private LinearVelocity velocity;
030      private Frequency      frequency;
031      private Distance       wavelength;
032      
033      private boolean needToUpdateFrequency;
034      private boolean needToUpdateWavelength;
035    
036      /**
037       * The speed of light in a vacuum, expressed in kilometers per second.
038       * The value used is <tt>299,792.458 km/s</tt>.
039       */
040      public static final BigDecimal LIGHT_SPEED_VACUUM_KM_PER_SEC =
041        new BigDecimal("2.99792458e5");
042    
043      /**
044       * Creates a new wave with velocity equal to that of light in a vacuum. 
045       * The default frequency of this wave is one gigahertz.
046       */
047      public Wave()
048      {
049        this(new LinearVelocity(LIGHT_SPEED_VACUUM_KM_PER_SEC,
050                                KILOMETERS_PER_SECOND));
051      }
052      
053      /**
054       * Creates a new wave with the given velocity.
055       * The default frequency of this wave is one gigahertz.
056       * 
057       * @param speedOfWave the velocity of this wave.
058       * 
059       * @throws IllegalArgumentException if {@code speedOfWave} is <i>null</i>
060       *           or has a non-positive value.
061       */
062      public Wave(LinearVelocity speedOfWave)
063      {
064        validateVelocity(speedOfWave);
065        
066        velocity   = speedOfWave.clone();
067        frequency  = new Frequency("1.0", GIGAHERTZ);
068        wavelength = new Distance(); //a dummy value
069        
070        needToUpdateFrequency  = false;
071        needToUpdateWavelength = true;
072      }
073      
074      //============================================================================
075      // VELOCITY
076      //============================================================================
077      
078      /**
079       * Sets this wave's velocity so that it is equal to {@code newVelocity}.
080       * Changing this wave's velocity will force a corresponding change in its
081       * wavelength while its frequency will be held constant.
082       * 
083       * @param newVelocity a new velocity for this wave.
084       * @return this wave
085       * @throws IllegalArgumentException if {@code newVelocity} is <i>null</i>
086       *           or has a non-positive value.
087       */
088      public Wave setVelocity(LinearVelocity newVelocity)
089      {
090        validateVelocity(newVelocity);
091        
092        velocity = newVelocity.clone();
093        
094        needToUpdateWavelength = true;
095        
096        return this;
097      }
098      
099      /**
100       * Returns a copy of the velocity of this wave.
101       * @return a copy of the velocity of this wave.
102       */
103      public LinearVelocity getVelocity()
104      {
105        //Can't provide reference because clients could alter it
106        return velocity.clone();
107      }
108      
109      /**
110       * Returns the velocity of this wave in the given units.
111       * This method will be faster than calling
112       * {@code getVelocity().toUnits(units)} because {@link #getVelocity()}
113       * makes a copy of this wave's velocity, while this method does not.
114       * 
115       * @param units units of velocity.
116       * @return the velocity of this wave in the given units.
117       */
118      public BigDecimal getVelocityValueIn(LinearVelocityUnits units)
119      {
120        return velocity.toUnits(units);
121      }
122    
123      /** Ensures velocity value is valid. */
124      private void validateVelocity(LinearVelocity v)
125      {
126        if (v == null)
127          throw new IllegalArgumentException("May not use NULL velocity.");
128        
129        if (v.getValue().signum() <= 0)
130          throw new IllegalArgumentException("Velocity must be positive.");
131      }
132    
133      //============================================================================
134      // FREQUENCY
135      //============================================================================
136      
137      /**
138       * Sets this wave's frequency so that it is equal to {@code newFrequency}.
139       * Changing this wave's frequency will result in a corresponding change
140       * in its wavelength while its velocity will be held constant.
141       * 
142       * @param newFrequency a new frequency for this wave.
143       * @return this wave.
144       * @throws IllegalArgumentException if {@code newFrequency} is <i>null</i>
145       *           or has a non-positive value.
146       */
147      public Wave setFrequency(Frequency newFrequency)
148      {
149        validateFrequency(newFrequency);
150        
151        //Can't save reference because clients could alter it
152        frequency = newFrequency.clone();
153        
154        needToUpdateFrequency  = false;
155        needToUpdateWavelength = true;
156        
157        return this;
158      }
159      
160      /**
161       * Returns a copy of this wave's frequency.
162       * @return a copy of this wave's frequency.
163       */
164      public Frequency getFrequency()
165      {
166        if (needToUpdateFrequency)
167          updateFrequency();
168        
169        //Can't provide reference because clients could alter it
170        return frequency.clone();
171      }
172      
173      /**
174       * Returns the frequency of this wave in the given units.
175       * This method will be faster than calling
176       * {@code getFrequency().asUnitsOf(units)} because {@link #getFrequency()}
177       * makes a copy of this wave's frequency, while this method does not.
178       * 
179       * @param units units of frequency.
180       * @return the frequency of this wave in the given units.
181       */
182      public BigDecimal getFrequencyValueIn(FrequencyUnits units)
183      {
184        if (needToUpdateFrequency)
185          updateFrequency();
186        
187        return frequency.toUnits(units);
188      }
189    
190      /** Ensures frequency value is valid. */
191      private void validateFrequency(Frequency f)
192      {
193        if (f == null)
194          throw new IllegalArgumentException("May not use NULL frequency.");
195        
196        if (f.getValue().compareTo(BigDecimal.ZERO) <= 0)
197          throw new IllegalArgumentException("Frequency must be positive.");
198      }
199    
200      /** Updates the frequency based on velocity & wavelength. */
201      private void updateFrequency()
202      {
203        BigDecimal kms = velocity.toUnits(KILOMETERS_PER_SECOND);
204        BigDecimal km  = wavelength.toUnits(KILOMETER);
205        
206        BigDecimal hertz = kms.divide(km, MC_INTERM_CALCS);
207        
208        frequency.set(hertz, HERTZ);
209      }
210    
211      //============================================================================
212      // WAVELENGTH
213      //============================================================================
214    
215      /**
216       * Sets this wave's wavelength so that it is equal to {@code newWavelength}.
217       * Changing this wave's wavelength will result in a corresponding change
218       * in its frequency while its velocity will be held constant.
219       * 
220       * @param newWavelength a new wavelength for this wave.
221       * @return this wave.
222       * @throws IllegalArgumentException if {@code newWavelength} is <i>null</i>
223       *           or has a non-positive length.
224       */
225      public Wave setWavelength(Distance newWavelength)
226      {
227        validateWavelength(newWavelength);
228        
229        //Can't save reference because clients could alter it
230        wavelength = newWavelength.clone();
231        
232        needToUpdateFrequency  = true;
233        needToUpdateWavelength = false;
234        
235        return this;
236      }
237      
238      /**
239       * Returns a copy of this wave's wavelength.
240       * @return a copy of this wave's wavelength.
241       */
242      public Distance getWavelength()
243      {
244        if (needToUpdateWavelength)
245          updateWavelength();
246        
247        //Can't provide reference because clients could alter it
248        return wavelength.clone();
249      }
250      
251      /**
252       * Returns the wavelength of this wave in the given units.
253       * This method will be faster than calling
254       * {@code getWavelength().toUnits(units)} because {@link #getWavelength()}
255       * makes a copy of this wave's wavelength, while this method does not.
256       * 
257       * @param units units of wavelength.
258       * @return the wavelength of this wave in the given units.
259       */
260      public BigDecimal getWavelengthValueIn(DistanceUnits units)
261      {
262        if (needToUpdateWavelength)
263          updateWavelength();
264        
265        return wavelength.toUnits(units);
266      }
267    
268      /** Ensures frequency value is valid. */
269      private void validateWavelength(Distance d)
270      {
271        if (d == null)
272          throw new IllegalArgumentException("May not use NULL wavelength.");
273        
274        if (d.getValue().signum() <= 0)
275          throw new IllegalArgumentException("Wavelength must be positive.");
276      }
277    
278      /** Updates the wavelength based on velocity & frequency. */
279      private void updateWavelength()
280      {
281        BigDecimal kms = velocity.toUnits(KILOMETERS_PER_SECOND);
282        BigDecimal kHz = frequency.toUnits(KILOHERTZ);
283        
284        BigDecimal meters = kms.divide(kHz, MC_INTERM_CALCS);
285        
286        wavelength.set(meters, METER);
287      }
288    
289      //============================================================================
290      // 
291      //============================================================================
292      
293      @Override
294      public String toString()
295      {
296        StringBuilder buff = new StringBuilder();
297        
298        buff.append(getVelocity()).append(", ");
299        buff.append(getFrequency().normalize()).append(", ");
300        buff.append(getWavelength());
301        
302        return buff.toString();
303      }
304    }