001    package edu.nrao.sss.model.source;
002    
003    import java.math.BigDecimal;
004    import java.util.ArrayList;
005    import java.util.Date;
006    import java.util.HashMap;
007    import java.util.List;
008    import java.util.Map;
009    import java.util.SortedSet;
010    import java.util.TreeSet;
011    
012    import javax.xml.bind.annotation.XmlAttribute;
013    import javax.xml.bind.annotation.XmlElement;
014    import javax.xml.bind.annotation.XmlElementRef;
015    import javax.xml.bind.annotation.XmlElementRefs;
016    import javax.xml.bind.annotation.XmlElementWrapper;
017    import javax.xml.bind.annotation.XmlType;
018    
019    import edu.nrao.sss.astronomy.CoordinateConversionException;
020    import edu.nrao.sss.astronomy.SimpleSkyPosition;
021    import edu.nrao.sss.astronomy.SkyPosition;
022    import edu.nrao.sss.astronomy.StokesParameter;
023    import edu.nrao.sss.astronomy.VelocityConvention;
024    import edu.nrao.sss.astronomy.VelocityFrame;
025    import edu.nrao.sss.geom.EarthPosition;
026    import edu.nrao.sss.measure.Frequency;
027    import edu.nrao.sss.measure.LinearVelocity;
028    import edu.nrao.sss.util.Identifiable;
029    
030    /**
031     * A portion of an extended astronomical source.
032     * <p>
033     * <b>Version Info:</b>
034     * <table style="margin-left:2em">
035     *   <tr><td>$Revision: 1709 $</td></tr>
036     *   <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr>
037     *   <tr><td>$Author: dharland $</td></tr>
038     * </table></p>
039     *  
040     * @author David M. Harland
041     * @since 2006-06-07
042     */
043    
044    @XmlType(propOrder= {"name", "position", "velocities", "brightnesses"})
045    
046    public class Subsource
047      implements Identifiable, Cloneable, Comparable<Subsource>
048    {
049      private static final String NO_NAME = "None";
050    
051      //IDENTIFICATION
052      @XmlAttribute
053      private long   id;
054      private String name;
055    
056      //OTHER ATTRIBUTES
057      private SkyPosition            position;
058      private List<SourceVelocity>   velocities;
059      private List<SourceBrightness> brightnesses;
060      
061      /** Creates a new unnamed instance. */
062      public Subsource()
063      {
064        velocities   = new ArrayList<SourceVelocity>();
065        brightnesses = new ArrayList<SourceBrightness>();
066        
067        initialize();
068      }
069      
070      /** Creates a new instance with the given name. */
071      public Subsource(String name)
072      {
073        this();
074        setName(name);
075      }
076      
077      /** Initializes the instance variables of this class.  */
078      private void initialize()
079      {
080        id   = Identifiable.UNIDENTIFIED;
081        name = NO_NAME;
082    
083        initializePosition();
084      }
085      
086      /** Resets position info. */
087      private void initializePosition()
088      {
089        position = new SimpleSkyPosition();
090      }
091      
092      /**
093       *  Resets this subsource to its initial state.
094       *  A reset subsource has the same state as a new subsource. 
095       */
096      public void reset()
097      {
098        velocities.clear();
099        brightnesses.clear();
100    
101        initialize();
102      }
103      
104      /**
105       * Resets this subsource's position information so that it looks like that
106       * of a newly created subsource.  This means that the position type will
107       * be <i>polynomial</i> and that the table of polynomial positions will be
108       * empty. 
109       */
110      public void resetPosition()  { initializePosition(); }
111    
112      /**
113       * Returns <i>true</i> if the position of this subsource at time T
114       * could be different than its position at time U &ne; T.
115       * <p>
116       * The determination of motion will be made with respect to the
117       * coordinate system in which the position of this subsource is expressed.
118       * For example, a position that is expressed in the equatorial system,
119       * and that is holding steady at its position in that system, will said
120       * to be not moving, even though in other coordinate systems it may be
121       * moving quite rapidly.</p>
122       * <p>
123       * This is a convenience method that is equivalent to calling
124       * <tt>getPosition().isMoving()</tt>.</p>
125       * 
126       * @return <i>true</i> if this subsource is moving.
127       */
128      public boolean isMoving()  { return position.isMoving(); }
129      
130      //============================================================================
131      // IDENTIFICATION
132      //============================================================================
133      
134      /* (non-Javadoc)
135       * @see edu.nrao.sss.model.util.Identifiable#getId()
136       */
137      public Long getId()
138      {
139        return id;
140      }
141      
142      @SuppressWarnings("unused")
143      private void setId(Long id)
144      {
145        this.id = id;
146      }
147    
148      /**
149       * Resets this subsource's ID, and the IDs of all its components,
150       * to a value that represents the unidentified state. 
151       */
152      void clearId()
153      {
154        this.id = Identifiable.UNIDENTIFIED;
155        
156        for (SourceBrightness sb : brightnesses)
157          sb.clearId();
158      }
159      
160      /**
161       * Sets the name of this subsource.
162       * <p>
163       * If {@code newName} is <i>null</i> or the empty string
164       * (<tt>""</tt>), the request to change the name will be
165       * denied and the current name will remain in place.</p>
166       * 
167       * @param newName the new name for this subsource.
168       */
169      public void setName(String newName)
170      {
171        if (newName != name && newName.length() > 0)
172          name = newName;
173      }
174      
175      /**
176       * Returns the name of this subsource.
177       * @return the name of this subsource.
178       */
179      public String getName()
180      {
181        return name;
182      }
183    
184      //============================================================================
185      // BRIGHTNESS
186      //============================================================================
187      
188      /**
189       * Adds {@code newBrightness} to this subsource.
190       * If {@code newBrightness} is <i>null</i>, no action is taken.
191       * 
192       * @param newBrightness the new brightness to be added to this subsource.
193       */
194      public void addBrightness(SourceBrightness newBrightness)
195      {
196        if (newBrightness != null)
197          brightnesses.add(newBrightness);
198      }
199      
200      /**
201       * Removes {@code oldBrightness} from this subsource.
202       * <p>
203       * If this subsource is not holding {@code oldBrightness}, this method
204       * does nothing.</p> 
205       * 
206       * @param oldBrightness the brightness to be removed from this subsource.
207       */
208      public void removeBrightness(SourceBrightness oldBrightness)
209      {
210        if (oldBrightness != null)
211          brightnesses.remove(oldBrightness);
212      }
213      
214      /**
215       * Removes all brightness entries from this subsource. 
216       */
217      public void removeAllBrightnesses()
218      {
219        brightnesses.clear();
220      }
221      
222      /**
223       * Replaces this subsource's collection of brightnesses with
224       * {@code replacementList}.
225       * <p>
226       * Note that this subsource will hold a reference to {@code replacementList}
227       * (unless it is <i>null</i>); it will not store a copy.  This means
228       * that any changes a client makes to {@code replacementList} <i>after</i>
229       * calling this method will be reflected in this subsource.</p>
230       * 
231       * @param replacementList a replacement list of brightnesses for this
232       *                        subsource.  If {@code replacementList} is
233       *                        <i>null</i>, it will be interpreted as an empty
234       *                        list.
235       */
236      public void setBrightnesses(List<SourceBrightness> replacementList)
237      {
238        brightnesses = (replacementList == null) ? new ArrayList<SourceBrightness>()
239                                                 : replacementList;
240      }
241      
242      /**
243       * Returns this subsource's collection of brightnesses.
244       * <p>
245       * Note that returned list is the one actually held by this subsource,
246       * not a clone thereof.  That means that any changes that a client
247       * makes to the list will affect this subsource.</p>
248       * 
249       * @return this subsource's collection of brightnesses.  The value returned
250       *         is guaranteed to be non-null, though it may be an empty list.
251       */
252      @XmlElementWrapper
253      @XmlElementRefs
254      (
255        {
256          @XmlElementRef(type=SourceBrightness.class),
257          @XmlElementRef(type=PointBrightness.class),
258          @XmlElementRef(type=GaussianBrightness.class),
259          @XmlElementRef(type=DiskBrightness.class),
260          @XmlElementRef(type=CleanFileBrightness.class),
261          @XmlElementRef(type=FitsFileBrightness.class),
262          @XmlElementRef(type=UnknownBrightness.class)
263        }
264      )
265      public List<SourceBrightness> getBrightnesses()
266      {
267        return brightnesses;
268      }
269    
270      /**
271       * Returns a set of this subsource's brightnesses that match the given
272       * criteria.  A match is made when a source brightness contains
273       * {@code frequency} as a valid frequency and has the some polarization
274       * as {@code polarization}.
275       *  
276       * @param frequency a valid frequency for a source brightness.  The
277       *                  special value of <i>null</i> is a signal to select
278       *                  a brightness without respect to its valid frequency
279       *                  range.
280       *                   
281       * @param polarization the polarization that all brightnesses in the returned
282       *                     set will have.  The special value of <i>null</i is a
283       *                     signal to select a brightness without respect to its
284       *                     polarization.
285       *                      
286       * @return the set of this subsource's brightnesses that match the given
287       *         parameters.  The return value is guaranteed to be non-null.
288       *         The returned set may be empty, though.
289       */
290      public SortedSet<SourceBrightness>
291        getBrightnesses(Frequency frequency, StokesParameter polarization)
292      {
293        SortedSet<SourceBrightness> result = new TreeSet<SourceBrightness>();
294        
295        SourceBrightnessFilter filter = new SourceBrightnessFilter();
296        filter.setFrequency(frequency);
297        filter.setPolarization(polarization);
298        
299        for (SourceBrightness sb : getBrightnesses())
300        {
301          if (filter.allows(sb))
302          {
303            result.add(sb);
304          }
305        }
306    
307        return result;
308      }
309      
310      /**
311       * Returns a map where the key is a {@link StokesParameter} and
312       * the value is a source brightness.  There will be zero or one brightness
313       * for each polarization.
314       * <p>
315       * Calling this method once is similar to calling
316       * {@link #getBrightness(Frequency, Date, StokesParameter)} for
317       * every polarization parameter.</p>
318       * 
319       * @param frequency a valid frequency for the source brightnesses in the
320       *                  returned map.  The use of a <i>null</i> value is not
321       *                  recommended here.
322       * 
323       * @param time the time for which the source brightnesses is requested.  The
324       *             returned brightness will have a valid time interval that
325       *             contains this time. 
326       * 
327       * @return a map of polarization / brightness pairs.
328       */
329      public Map<StokesParameter, SourceBrightness>
330        getBrightnesses(Frequency frequency, Date time)
331      {
332        Map<StokesParameter, SourceBrightness> result =
333          new HashMap<StokesParameter, SourceBrightness>();
334        
335        //This is not the most efficient way to do this, but the number of
336        //polarizations is not large and the code is clearer than some
337        //alternatives.
338        for (StokesParameter polar : StokesParameter.values())
339        {
340          SourceBrightness sb = getBrightness(frequency, time, polar);
341    
342          if (sb != null)
343            result.put(polar, sb);
344        }
345    
346        return result;
347      }
348      
349      /**
350       * Returns the source brightness that matches the given parameters, if any.
351       * <p>
352       * The search for a matching brightness is done by first filtering on
353       * {@code frequency} and {@code polarization}.
354       * (See {@link #getBrightnesses(Frequency, StokesParameter)} for
355       * details.)  At this point there should be only zero or one matching
356       * brightness<sup>1</sup>.  However, if there are multiple brightnesses,
357       * the first one found that has a valid time range that contains {@code time}
358       * will be returned.  If there are no matches, <i>null</i> is returned.</p>
359       * <p>
360       * <sup>1</sup>Ideally this class would <i>prevent</i> clients from adding
361       * new brightnesses that have the same polarization and frequency, and
362       * a coincident or overlapping valid time range, as one already held by
363       * this subsource.  At this time, this class does not have such a
364       * mechanism.</p>
365       *  
366       * @param frequency a valid frequency for a source brightness.  The use
367       *                  of a <i>null</i> value is not recommended here.
368       *                  
369       * @param time the time for which a source brightness is requested.  The
370       *             returned brightness will have a valid time interval that
371       *             contains this time. 
372       * 
373       * @param polarization the polarization that all brightnesses in the returned
374       *                     set will have.  The use of a <i>null</i> value is not
375       *                     recommended here.
376       *                     
377       * @return the source brightness that matches the given parameters, or
378       *         <i>null</i> if there is no match.
379       */
380      public SourceBrightness getBrightness(Frequency frequency, Date time,
381                                            StokesParameter polarization)
382      {
383        SourceBrightness result = null;
384        
385        for (SourceBrightness sb : getBrightnesses(frequency, polarization))
386        {
387          if (sb.getValidTime().contains(time))
388          {
389            result = sb;
390            break;
391          }
392        }
393        
394        return result;
395      }
396      
397      //============================================================================
398      // VELOCITY
399      //============================================================================
400      
401      /**
402       * Adds {@code newVelocity} to this subsource.
403       * If {@code newVelocity} is <i>null</i>, no action is taken.
404       * 
405       * @param newVelocity the new velocity to be added to this subsource.
406       */
407      public void addVelocity(SourceVelocity newVelocity)
408      {
409        if (newVelocity != null)
410          velocities.add(newVelocity);
411      }
412      
413      /**
414       * Removes {@code oldVelocity} from this subsource.
415       * <p>
416       * If this subsource is not holding {@code oldVelocity}, this method
417       * does nothing.</p> 
418       * 
419       * @param oldVelocity the velocity to be removed from this subsource.
420       */
421      public void removeVelocity(SourceVelocity oldVelocity)
422      {
423        if (oldVelocity != null)
424          velocities.remove(oldVelocity);
425      }
426      
427      /**
428       * Removes all velocity entries from this subsource. 
429       */
430      public void removeAllVelocities()
431      {
432        velocities.clear();
433      }
434      
435      /**
436       * Replaces this subsource's collection of velocities with
437       * {@code replacementList}.
438       * <p>
439       * Note that this subsource will hold a reference to {@code replacementList}
440       * (unless it is <i>null</i>); it will not store a copy.  This means
441       * that any changes a client makes to {@code replacementList} <i>after</i>
442       * calling this method will be reflected in this subsource.</p>
443       * 
444       * @param replacementList a replacement list of velocities for this subsource.
445       *                        If {@code replacementList} is <i>null</i>, it will
446       *                        be interpreted as an empty list.
447       */
448      public void setVelocities(List<SourceVelocity> replacementList)
449      {
450        velocities = (replacementList == null) ? new ArrayList<SourceVelocity>()
451                                               : replacementList;
452      }
453      
454      /**
455       * Returns this subsource's collection of velocities.
456       * <p>
457       * Note that returned list is the one actually held by this subsource,
458       * not a clone thereof.  That means that any changes that a client
459       * makes to the list will affect this subsource.</p>
460       * 
461       * @return this subsource's collection of velocities.  The value returned
462       *         is guaranteed to be non-null, though it may be an empty list.
463       */
464      @XmlElementWrapper
465      @XmlElement(name="velocity")
466      public List<SourceVelocity> getVelocities()
467      {
468        return velocities;
469      }
470      
471      /**
472       * Returns the source velocity for the given frequency, if any.
473       * <p>
474       * There should be only zero or one matching
475       * velocity for any single frequency<sup>1</sup>.
476       * However, if there are multiple velocities,
477       * the first one found that has a valid frequency range that contains
478       * {@code frequency}  will be returned.
479       * If there are no matches, <i>null</i> is returned.</p>
480       * <p>
481       * <sup>1</sup>Ideally this class would <i>prevent</i> clients from adding
482       * new velocities that have the frequency ranges that overlap with one
483       * already held by this subsource.  At this time, this class does not have
484       * such a mechanism.</p>
485       *  
486       * @param frequency a valid frequency for a source velocity.
487       *                     
488       * @return the source velocity that contains {@code frequency},
489       *         or <i>null</i> if this subsource holds no such velocity.
490       */
491      public SourceVelocity getVelocity(Frequency frequency)
492      {
493        SourceVelocity result = null;
494        
495        for (SourceVelocity sv : velocities)
496        {
497          if (sv.getValidFrequency().contains(frequency))
498          {
499            result = sv;
500            break;
501          }
502        }
503        
504        return result;
505      }
506    
507      /**
508       * Returns the velocity of this subsource toward or away from the
509       * observer at the given point in time.
510       * 
511       * @param observer
512       *   a position on earth.  The radial velocity of this subsource is
513       *   calculated relative to this observer.
514       *   
515       * @param dateTime
516       *   the point in time at which the velocity is calculated.
517       *   
518       * @return
519       *   the radial velocity of this subsource toward or away from a point
520       *   on earth.
521       *   
522       * @throws CoordinateConversionException
523       *   if the position of this subsource cannot be converted to
524       *   an equatorial RA / Dec position.
525       *   
526       * @since 2008-07-29
527       */
528      public LinearVelocity calcVelocityRelativeTo(EarthPosition observer,
529                                                   Date          dateTime)
530        throws CoordinateConversionException
531      {
532        //TODO Need to resolve problem w/ fetching the "correct" velocity.
533        //     For now just taking 1st one we see.
534        SourceVelocity sv = velocities.isEmpty() ? null : velocities.get(0);
535        
536        VelocityFrame refFrame = (sv == null) ? VelocityFrame.GEOCENTRIC
537                                              : sv.getRestFrame();
538        LinearVelocity observerVelocity =
539          observer.calcVelocityRelativeTo(getPosition(), dateTime, refFrame);
540        
541        LinearVelocity sourceVelocity = (sv == null) ? new LinearVelocity()
542                                                     : sv.getRadialVelocity();
543        return sourceVelocity.add(observerVelocity);
544      }
545      
546      /**
547       * Returns an observed frequency based on a rest frequency and the
548       * relative motion between this source and an earth-bound observer.
549       * 
550       * @param restFreq
551       *   the rest frequency for which an observed frequency is requested.
552       *    
553       * @param observer
554       *   a position on earth.  The relative radial velocity between this
555       *   observer and this source determines the amount by which the
556       *   {@code restFreq} is shifted into an observed frequency.
557       *   
558       * @param dateTime
559       *   the time for which the velocity calculation is performed.
560       *   
561       * @return
562       *   an observed frequency based on {@code restFreq} and the relative
563       *   motion between this source and the {@code observer}.
564       *   
565       * @throws CoordinateConversionException
566       *   if the position of this subsource cannot be converted to
567       *   an equatorial RA / Dec position.
568       *   
569       * @since 2008-07-29
570       */
571      public Frequency calcShiftedFrequency(Frequency     restFreq,
572                                            EarthPosition observer,
573                                            Date          dateTime)
574        throws CoordinateConversionException
575      {
576        LinearVelocity totalVelocity = calcVelocityRelativeTo(observer, dateTime);
577     
578        VelocityConvention convention = VelocityConvention.RADIO;
579        
580        //TODO Need to resolve problem w/ fetching the "correct" velocity.
581        //     For now just taking 1st one we see.
582        if (!velocities.isEmpty())
583          convention = velocities.get(0).getConvention();
584        
585        BigDecimal shiftFactor = convention.getFrequencyShiftFactor(totalVelocity);
586        
587        return restFreq.clone().multiplyBy(shiftFactor);
588      }
589      
590      //============================================================================
591      // POSITION
592      //============================================================================
593      
594      /**
595       * Sets the position of this subsource.
596       * <p>
597       * If {@code newPosition} is <i>null</i>, this method does nothing.
598       * Otherwise this subsource will hold a reference to {@code newPosition}.</p>
599       *  
600       * @param newPosition a new position for this subsource.
601       */
602      public void setPosition(SkyPosition newPosition)
603      {
604        if (newPosition != null)
605          position = newPosition;
606      }
607      
608      /**
609       * Returns the position of this subsource.
610       * <p>
611       * The value returned is guaranteed to be non-null.  It is also a reference
612       * to the position actually held by this subsource, so any changes made to
613       * it by clients will be reflected in this object.</p>
614       * 
615       * @return the position of this subsource.
616       */
617      @XmlElementRefs
618      (
619        {
620          @XmlElementRef(type=edu.nrao.sss.astronomy.SimpleSkyPosition.class),
621          @XmlElementRef(type=edu.nrao.sss.astronomy.EphemerisTable.class),
622          @XmlElementRef(type=edu.nrao.sss.astronomy.Orbit.class),
623          @XmlElementRef(type=edu.nrao.sss.astronomy.PolynomialPosition.class),
624          @XmlElementRef(type=edu.nrao.sss.astronomy.PolynomialPositionTable.class),
625          @XmlElementRef(type=edu.nrao.sss.astronomy.SolarSystemBodyPosition.class)
626        }
627      )
628      public SkyPosition getPosition()
629      {
630        return position;
631      }
632      
633      //============================================================================
634      // 
635      //============================================================================
636      
637      /**
638       *  Returns a copy of this subsource.
639       *  <p>
640       *  If anything goes wrong during the cloning procedure,
641       *  a {@code RuntimeException} will be thrown.</p>
642       */
643      @Override
644      public Subsource clone()
645      {
646        Subsource clone = null;
647    
648        try
649        {
650          //This line takes care of the primitive fields properly
651          clone = (Subsource)super.clone();
652          
653          //We do NOT want the clone to have the same ID as the original.
654          //The ID is here for the persistence layer; it is in charge of
655          //setting IDs.  To help it, we put the clone's ID in the uninitialized
656          //state.
657          clone.id = Identifiable.UNIDENTIFIED;
658    
659          clone.position = this.position.clone();
660          
661          clone.velocities = new ArrayList<SourceVelocity>();
662          for (SourceVelocity sv : this.velocities)
663            clone.addVelocity(sv.clone());
664          
665          clone.brightnesses = new ArrayList<SourceBrightness>();
666          for (SourceBrightness sb : this.brightnesses)
667            clone.addBrightness(sb.clone());
668        }
669        catch (Exception ex)
670        {
671          throw new RuntimeException(ex);
672        }
673        
674        return clone;
675      }
676      
677      /** Returns <i>true</i> if {@code o} is equal to this subsource. */
678      @Override
679      public boolean equals(Object o)
680      {
681        //Quick exit if o is this
682        if (o == this)
683          return true;
684        
685        //Quick exit if o is null
686        if (o == null)
687          return false;
688        
689        //Quick exit if classes are different
690        if (!o.getClass().equals(this.getClass()))
691          return false;
692        
693        Subsource other = (Subsource)o;
694        
695        //NOTE: The absence of the id property here is intentional.
696    
697        //Separated the tests for easier debugging
698        if (!other.position.equals(this.position))
699          return false;
700    
701        if (!other.velocities.equals(this.velocities))
702          return false;
703        
704        if (!other.brightnesses.equals(this.brightnesses))
705          return false;
706        
707        return true;
708      }
709    
710      /** Returns a hash code value for this subsource. */
711      @Override
712      public int hashCode()
713      {
714        //Taken from the Effective Java book by Joshua Bloch.
715        //The constants 17 & 37 are arbitrary & carry no meaning.
716        int result = 17;
717        
718        //NOTE: The absence of the id property here is intentional.
719        
720        result = 37 * result + position.hashCode();
721        result = 37 * result + velocities.hashCode();
722        result = 37 * result + brightnesses.hashCode();
723        
724        return result;
725      }
726      
727      /**
728       * Compares this subsource to {@code other} for order.
729       * <p>
730       * Note that this method <i>is not consistent with equals</i>.
731       * Only the name attribute is used for ordering.
732       * There is a special name, <i>CENTER</i>, that is deemend to
733       * precede all other names.  Otherwise, alphabetical ordering
734       * is used.</p>
735       * 
736       * @param other the subsource to which this one is compared.
737       * 
738       * @return a negative integer, zero, or a positive integer as this subsource
739       *         is less than, equal to, or greater than the other subsource.
740       */
741      public int compareTo(Subsource other)
742      {
743        String thisName  = this.getName();
744        String otherName = other.getName();
745        
746        //Quick exit if exactly one of these subsources is the special one
747        if (!thisName.equalsIgnoreCase(otherName))
748        {
749          if (thisName.equalsIgnoreCase(Source.CENTER_POSITION_NAME))
750            return -1;
751    
752          if (otherName.equalsIgnoreCase(Source.CENTER_POSITION_NAME))
753            return +1;
754        }
755        
756        //Either both or neither subsource has the special name.
757        return thisName.compareTo(otherName);
758      }
759    }