001    package edu.nrao.sss.model.resource;
002    
003    import java.math.BigDecimal;
004    import java.math.RoundingMode;
005    import java.util.SortedMap;
006    import java.util.SortedSet;
007    import java.util.TreeMap;
008    import java.util.TreeSet;
009    
010    import javax.xml.bind.annotation.XmlList;
011    
012    import edu.nrao.sss.astronomy.SpectralLine;
013    import edu.nrao.sss.astronomy.StokesParameter;
014    import edu.nrao.sss.astronomy.VelocityConvention;
015    import edu.nrao.sss.measure.Frequency;
016    import edu.nrao.sss.measure.FrequencyRange;
017    import edu.nrao.sss.measure.LinearVelocity;
018    import edu.nrao.sss.measure.LinearVelocityUnits;
019    import edu.nrao.sss.measure.TimeDuration;
020    import edu.nrao.sss.measure.TimeUnits;
021    import edu.nrao.sss.util.Identifiable;
022    import edu.nrao.sss.util.StringUtil;
023    
024    /**
025     * A specification for observing a spectral transition in a distance object.
026     * Instances of this class are used to help choose and configure instrumentation
027     * that can obtain data that conform to the encapsulated specifications.
028     * <p>
029     * <b>Version Info:</b>
030     * <table style="margin-left:2em">
031     *   <tr><td>$Revision: 1709 $</td></tr>
032     *   <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr>
033     *   <tr><td>$Author: dharland $</td></tr>
034     * </table></p>
035     * 
036     * @author David M. Harland
037     * @since 2007-01-29
038     */
039    public class SpectralLineSpecification
040      implements Cloneable
041    {
042      private static final BigDecimal TWO = new BigDecimal("2.0");
043      
044      //This ID field is here for the persistance layer
045      @SuppressWarnings("unused")
046      private Long id;
047      
048      private SpectralLine   line;
049      private LinearVelocity sourceVelocity;
050      private LinearVelocity velocityBandwidth;
051      private LinearVelocity velocityResolution;
052      private TimeDuration   timeResolution;
053      
054      private SortedSet<StokesParameter> polarizations;
055      private SortedMap<String, String>  additionalSpecifications;
056      
057      /** Creates a new instance. */
058      public SpectralLineSpecification()
059      {
060        id = Identifiable.UNIDENTIFIED;
061      
062        line               = new SpectralLine();
063        sourceVelocity     = new LinearVelocity();
064        velocityBandwidth  = new LinearVelocity();
065        velocityResolution = new LinearVelocity();
066        timeResolution     = new TimeDuration("0.0", TimeUnits.MILLISECOND);
067        
068        polarizations            = new TreeSet<StokesParameter>();
069        additionalSpecifications = new TreeMap<String, String>();
070      }
071    
072      /**
073       * Resets the identifier of this specification
074       * to a value that represents the unidentified state.
075       * <p>
076       * This method is useful for preparing a specification for storage in a
077       * database.
078       * The ID property (as of now, though this may change in the future) is
079       * used by our persistence mechanism to identify objects.  If you are
080       * persisting this object for the first time, you may need to call
081       * this method before performing a save.  This is especially true if
082       * you have created this object from XML, as the XML unmarshalling
083       * brings along the ID property.</p> 
084       */
085      void clearId()
086      {
087        this.id = Identifiable.UNIDENTIFIED;
088      }
089    
090      //============================================================================
091      // DERIVED VALUES
092      //============================================================================
093    
094      /**
095       * Returns a sky frequency specification that corresponds to this spectral
096       * line specification.
097       * 
098       * @return a new sky frequency specification derived from this specification.
099       */
100      public SkyFrequencySpecification toSkyFrequencySpecification()
101      {
102        SkyFrequencySpecification skyFreqSpec = new SkyFrequencySpecification();
103        
104        skyFreqSpec.setTimeResolution(timeResolution.clone());
105    
106        skyFreqSpec.setPolarizationProducts(new TreeSet<StokesParameter>(polarizations));
107    
108        skyFreqSpec.setAdditionalSpecifications(
109                       new TreeMap<String, String>(additionalSpecifications));
110        
111        updateFreqRangeAndResolution(skyFreqSpec);
112        
113        return skyFreqSpec;
114      }
115    
116      /** Calculates the sky frequency range from the other properties. */
117      private
118        void updateFreqRangeAndResolution(SkyFrequencySpecification skyFreqSpec)
119      {
120        //Use the central velocity to decide on using either the radio
121        //or the redshift convention.
122        VelocityConvention convention = 
123          sourceVelocity.getUnits().equals(LinearVelocityUnits.Z) ?
124          VelocityConvention.REDSHIFT : VelocityConvention.RADIO;
125    
126        skyFreqSpec.setFrequencyRange(calcFreqRange(velocityBandwidth, convention));
127        
128        skyFreqSpec.setFrequencyResolution(calcFreqRange(velocityResolution,
129                                                         convention).getWidth());
130      }
131      
132      /** 
133       * Creates a frequency range from the parameters, the source
134       * velocity, and the rest frequency.
135       */
136      private FrequencyRange calcFreqRange(LinearVelocity     bandwidth,
137                                           VelocityConvention convention)
138      {
139        LinearVelocityUnits velocityUnits = convention.getDefaultUnits();
140    
141        BigDecimal halfBandwidth = bandwidth.toUnits(velocityUnits).divide(TWO, RoundingMode.HALF_UP);
142        BigDecimal centralVelocity = sourceVelocity.toUnits(velocityUnits);
143        
144        BigDecimal low  = centralVelocity.subtract(halfBandwidth);
145        BigDecimal high = centralVelocity.add(halfBandwidth);
146    
147        BigDecimal shiftFactor1 = convention.getFrequencyShiftFactor(low);
148        BigDecimal shiftFactor2 = convention.getFrequencyShiftFactor(high);
149        
150        if (shiftFactor1.signum() < 0)
151          shiftFactor1 = BigDecimal.ZERO;
152        
153        if (shiftFactor2.signum() < 0)
154          shiftFactor2 = BigDecimal.ZERO;
155        
156        Frequency restFrequency = line.getFrequency();
157        
158        Frequency frequency1 = restFrequency.clone().multiplyBy(shiftFactor1);
159        Frequency frequency2 = restFrequency.clone().multiplyBy(shiftFactor2);
160        
161        return new FrequencyRange(frequency1, frequency2);
162      }
163      
164      //============================================================================
165      // INDIRECT MANIPULATIONS
166      //============================================================================
167      
168      /**
169       * Alters the velocity values of this specification so that its sky
170       * frequencies equal the endpoints of {@code skyFreqRange}.
171       * Only the velocity range is changed; the rest frequency and resolutions
172       * of this specification are unaltered.  If this specification's rest
173       * frequency is zero, this method does nothing.
174       * 
175       * @param skyFreqRange the range of sky frequencies to which the rest
176       *                     frequency of this specification is constrained.
177       *                     
178       * @return this specification.
179       */
180      //TODO: Test this method
181      public SpectralLineSpecification 
182        adjustVelocityForSkyFrequencyOf(FrequencyRange skyFreqRange)
183      {
184        Frequency restFrequency = line.getFrequency();
185        
186        //Quick exit if no rest frequency
187        if (restFrequency.getValue().compareTo(BigDecimal.ZERO) == 0)
188          return this;
189        
190        VelocityConvention vc =
191          (sourceVelocity.getUnits() == LinearVelocityUnits.Z) ?
192            VelocityConvention.REDSHIFT : VelocityConvention.RADIO;
193        
194        //Convert the frequency shifts to velocities
195        LinearVelocity v1 =
196          vc.getVelocity(skyFreqRange.getLowFrequency().dividedBy(restFrequency));
197        LinearVelocity v2 =
198          vc.getVelocity(skyFreqRange.getHighFrequency().dividedBy(restFrequency));
199    
200        //Worry about both red and blue shifts
201        LinearVelocity vHigh, vLow;
202        
203        if (v1.compareTo(v2) >= 0)  { vHigh = v1; vLow = v2; }
204        else                        { vHigh = v2; vLow = v1; }
205        
206        //Set the new velocity values
207        velocityBandwidth = vHigh.subtract(vLow);
208        sourceVelocity    = vLow.add(velocityBandwidth.clone().divideBy(TWO));
209        
210        return this;
211      }
212      
213      //============================================================================
214      // SIMPLE PROPERTIES
215      //============================================================================
216    
217      /**
218       * Sets the spectral line of this specification.
219       * <p>
220       * This specification will hold a reference to {@code newLine} (unless it is
221       * <i>null</i>), so any changes made to it after this call will be reflected
222       * in this specification.</p>
223       * 
224       * @param newLine the spectral line of this specification.
225       *                If this value is <i>null</i>,
226       *                it will be replaced with {@code new SpectralLine()}.
227       */
228      public void setLine(SpectralLine newLine)
229      {
230        line = (newLine == null) ? new SpectralLine() : newLine;
231      }
232    
233      /**
234       * Returns the spectral line of this specification.
235       * <p>
236       * The returned line is the one held by this specification,
237       * so any changes made to it will be reflected in this object.
238       * This value is guaranteed to be non-null.</p>
239       * 
240       * @return the spectral line of this specification.
241       */
242      public SpectralLine getLine()
243      {
244        return line;
245      }
246    
247      /**
248       * Sets the velocity of the source of this spectral line away from
249       * or toward the observer.
250       * <p>
251       * Unless {@code newVelocity} is <i>null</i>, 
252       * this object will hold a reference to the parameter,
253       * so any changes made to it will be reflected in this object.
254       * A value of <i>null</i> will be interpreted as a velocity
255       * of zero kilometers per second.</p>
256       *
257       * @param newVelocity the velocity of the source of this spectral line away from
258       *                    or toward the observer.
259       */
260      public void setSourceVelocity(LinearVelocity newVelocity)
261      {
262        sourceVelocity = (newVelocity == null) ? new LinearVelocity() : newVelocity;
263      }
264    
265      /**
266       * Returns the velocity of the source of this spectral line.
267       * The velocity returned is the component that is either away from or
268       * toward the observer.
269       * <p>
270       * Note that the returned velocity is the one held by this specification,
271       * so any changes made to it will be reflected in this object.
272       * This value is guaranteed to be non-null.</p>
273       * 
274       * @return the velocity of the source of this spectral line.
275       */
276      public LinearVelocity getSourceVelocity()
277      {
278        return sourceVelocity;
279      }
280    
281      /**
282       * Helps set the minimum and maximum velocities for this observation.
283       * The minimum (maximum) velocity is the
284       * {@link #getSourceVelocity() source velocity}
285       * minus (plus) one-half of {@code newBandwidth}.
286       * If {@code newBandwidth} is <i>null</i>, this method does nothing.
287       * <p>
288       * Unless {@code newBandwidth} is <i>null</i>, 
289       * this object will hold a reference to the parameter,
290       * so any changes made to it will be reflected in this object.
291       * A value of <i>null</i> will be interpreted as a bandwidth
292       * of zero kilometers per second.</p>
293       *
294       * @param newBandwidth the width of the velocity range that is centered on
295       *                     {@link #getSourceVelocity()}.
296       */
297      public void setVelocityBandwidth(LinearVelocity newBandwidth)
298      {
299        velocityBandwidth =
300          (newBandwidth == null) ? new LinearVelocity() : newBandwidth;
301      }
302    
303      /**
304       * Returns the width of the velocity range that is centered on
305       * {@link #getSourceVelocity()}.
306       * <p>
307       * Note that the returned width is the one held by this specification,
308       * so any changes made to it will be reflected in this object.
309       * This value is guaranteed to be non-null.</p>
310       *
311       * @return the width of the velocity range that is centered on
312       *         {@link #getSourceVelocity()}.
313       *         
314       * @see #setVelocityBandwidth(LinearVelocity)
315       */
316      public LinearVelocity getVelocityBandwidth()
317      {
318        return velocityBandwidth;
319      }
320    
321      /**
322       * Sets velocity resolution of this specification.
323       * <p>
324       * Unless {@code newResolution} is <i>null</i>, 
325       * this object will hold a reference to the parameter,
326       * so any changes made to it will be reflected in this object.
327       * A value of <i>null</i> will be interpreted as a resolution
328       * equal to that of this specification's current bandwidth.</p>
329       *
330       * @param newResolution velocity resolution of this specification.
331       */
332      public void setVelocityResolution(LinearVelocity newResolution)
333      {
334        velocityResolution = newResolution == null ? getVelocityBandwidth().clone()
335                                                   : newResolution;
336      }
337    
338      /**
339       * Returns the velocity resolution of this specification.
340       * <p>
341       * Note that the returned resolution is the one held by this specification,
342       * so any changes made to it will be reflected in this object.
343       * This value is guaranteed to be non-null.</p>
344       *
345       * @return the velocity resolution of this specification.
346       */
347      public LinearVelocity getVelocityResolution()
348      {
349        return velocityResolution;
350      }
351    
352      /**
353       * Sets the time resolution of this specification.
354       * <p>
355       * Unless {@code resolution} is <i>null</i>, 
356       * this object will hold a reference to the parameter,
357       * so any changes made to it will be reflected in this object.
358       * A value of <i>null</i> will be interpreted as a duration
359       * of zero milliseconds.</p>
360       * 
361       * @param resolution the time resolution of this specification.
362       */
363      public void setTimeResolution(TimeDuration resolution)
364      {
365        timeResolution =
366          (resolution == null) ? new TimeDuration("0.0", TimeUnits.MILLISECOND)
367                               : resolution;
368      }
369    
370      /**
371       * Returns the time resolution of this specification.
372       * <p>
373       * Note that the returned duration is the one held by this specification,
374       * so any changes made to it will be reflected in this object.
375       * This value is guaranteed to be non-null.</p>
376       * 
377       * @return the time resolution of this specification.
378       */
379      public TimeDuration getTimeResolution()
380      {
381        return timeResolution;
382      }
383    
384      /**
385       * Sets the polarization products requested by this specification.
386       * <p>
387       * Unless {@code newSet} is <i>null</i>, 
388       * this object will hold a reference to the parameter,
389       * so any changes made to it will be reflected in this object.
390       * A value of <i>null</i> will be interpreted as a new empty set.</p>
391       * 
392       * @param newSet the polarizations requested by this specification.
393       *               If this value is <i>null</i>, it will be interpreted
394       *               as an empty set.
395       */
396      public void setPolarizationProducts(SortedSet<StokesParameter> newSet)
397      {
398        polarizations = (newSet == null) ? new TreeSet<StokesParameter>()
399                                         : newSet;
400      }
401      
402      /**
403       * Returns the polarization products requested by this specification.
404       * <p>
405       * Note that the returned set is the one held by this specification,
406       * so any changes made to it will be reflected in this object.
407       * This set is guaranteed to be non-null, but it may be empty.</p>
408       * 
409       * @return the polarization products requested by this specification.
410       */
411      @XmlList
412      public SortedSet<StokesParameter> getPolarizationProducts()
413      {
414        return polarizations;
415      }
416      
417      //============================================================================
418      // 
419      //============================================================================
420    
421      /**
422       * Returns a text representation of this specification.
423       * @return a text representation of this specification.
424       */
425      public String toString()
426      {
427        StringBuilder buff = new StringBuilder();
428        
429        final String EOL = StringUtil.EOL;
430        
431        buff.append("Spectral Line:").append(EOL).append('{').append(EOL);
432        buff.append(line).append('}').append(EOL);
433        buff.append("Source Velocity:     ").append(sourceVelocity).append(EOL);
434        buff.append("Velocity Bandwidth:  ").append(velocityBandwidth).append(EOL);
435        buff.append("Velocity Resolution: ").append(velocityResolution).append(EOL);
436        buff.append("Time Resolution:     ").append(timeResolution).append(EOL);
437        
438        return buff.toString();
439      }
440     
441      /**
442       *  Returns a specification that is a copy of this one.
443       *  <p>
444       *  If anything goes wrong during the cloning procedure,
445       *  a {@code RuntimeException} will be thrown.</p>
446       */
447      @Override
448      public SpectralLineSpecification clone()
449      {
450        SpectralLineSpecification clone = null;
451        
452        try
453        {
454          clone = (SpectralLineSpecification)super.clone();
455    
456          //We do NOT want the clone to have the same ID as the original.
457          //The ID is here for the persistence layer; it is in charge of
458          //setting IDs.  To help it, we put the clone's ID in the uninitialized
459          //state.
460          clone.id = Identifiable.UNIDENTIFIED;
461    
462          clone.line               = this.line.clone();
463          clone.sourceVelocity     = this.sourceVelocity.clone();
464          clone.velocityBandwidth  = this.velocityBandwidth.clone();
465          clone.velocityResolution = this.velocityResolution.clone();
466          clone.timeResolution     = this.timeResolution.clone();
467          
468          //These are collections of immutables, so we need clone only the collection
469          //variables, not the contents therein.
470          clone.polarizations = new TreeSet<StokesParameter>(this.polarizations);
471          
472          clone.additionalSpecifications =
473            new TreeMap<String, String>(this.additionalSpecifications);
474        }
475        catch (Exception ex)
476        {
477          throw new RuntimeException(ex);
478        }
479        
480        return clone;
481      }
482      
483      /** Returns <i>true</i> if {@code o} is equal to this specification. */
484      @Override
485      public boolean equals(Object o)
486      {
487        //Quick exit if o is this
488        if (o == this)
489          return true;
490        
491        //Quick exit if o is null
492        if (o == null)
493          return false;
494        
495        //Quick exit if classes are different
496        if (!o.getClass().equals(this.getClass()))
497          return false;
498        
499        SpectralLineSpecification other = (SpectralLineSpecification)o;
500        
501        //NOTE: absence of ID field is intentional
502    
503        return other.line.equals(this.line) &&
504               other.sourceVelocity.equals(this.sourceVelocity) &&
505               other.velocityBandwidth.equals(this.velocityBandwidth) &&
506               other.velocityResolution.equals(this.velocityResolution) &&
507               other.timeResolution.equals(this.timeResolution) &&
508               other.polarizations.equals(this.polarizations) &&
509               other.additionalSpecifications.equals(this.additionalSpecifications);
510    
511        //Note the comparison of the polarizations Set is OK because
512        //the elements are immutable.
513      }    
514    
515      /** Returns a hash code value for this specification. */
516      @Override
517      public int hashCode()
518      {
519        //Taken from the Effective Java book by Joshua Bloch.
520        //The constants 17 & 37 are arbitrary & carry no meaning.
521        int result = 17;
522        
523        //This method MUST be kept in synch w/ the equals method.
524        
525        result = 37 * result + line.hashCode();
526        result = 37 * result + sourceVelocity.hashCode();
527        result = 37 * result + velocityBandwidth.hashCode();
528        result = 37 * result + velocityResolution.hashCode();
529        result = 37 * result + timeResolution.hashCode();
530        result = 37 * result + polarizations.hashCode();
531        result = 37 * result + additionalSpecifications.hashCode();
532        
533        return result;
534      }
535      
536      //============================================================================
537      // 
538      //============================================================================
539      /*
540      public static void main(String[] args) throws Exception
541      {
542        ResourceBuilder builder = new ResourceBuilder();
543        SpectralLineSpecification spec = builder.makeSpectralLineSpecification();
544        
545        System.out.println(spec);
546      }
547      */
548    }