001    package edu.nrao.sss.model.resource.evla;
002    
003    import static edu.nrao.sss.astronomy.CelestialCoordinateSystem.HORIZONTAL;
004    import static edu.nrao.sss.astronomy.CelestialCoordinateSystem.EQUATORIAL;
005    import static edu.nrao.sss.measure.ArcUnits.DEGREE;
006    import static edu.nrao.sss.measure.ArcUnits.SECOND;
007    import static edu.nrao.sss.measure.ArcUnits.ARC_SECOND;
008    
009    import javax.xml.bind.annotation.XmlElement;
010    import javax.xml.bind.annotation.XmlTransient;
011    
012    import edu.nrao.sss.astronomy.CelestialCoordinateSystem;
013    import edu.nrao.sss.astronomy.Epoch;
014    import edu.nrao.sss.astronomy.SimpleSkyPosition;
015    import edu.nrao.sss.astronomy.SkyPosition;
016    import edu.nrao.sss.measure.Angle;
017    import edu.nrao.sss.measure.ArcUnits;
018    import edu.nrao.sss.measure.Latitude;
019    import edu.nrao.sss.measure.Longitude;
020    
021    /**
022     * The position to which the EVLA antennas should point.
023     * <p>
024     * The main reason for the existence of this class is to be able to
025     * allow azimuth values that go beyond the normal range of 0 - 360
026     * and elevation values that go beyond the normal range of -90 - +90.</p>
027     * <p>
028     * <b>Version Info:</b>
029     * <table style="margin-left:2em">
030     *   <tr><td>$Revision$</td></tr>
031     *   <tr><td>$Date$</td></tr>
032     *   <tr><td>$Author$ (last person to modify)</td></tr>
033     * </table></p>
034     * 
035     * @author David M. Harland
036     * @since 2009-02-18
037     */
038    //TODO to make more general, should have superclass and/or interface "TelescopePointingPosition"
039    public class EvlaPointingPosition
040      implements Cloneable
041    {
042      @XmlElement private Angle latitude;
043      @XmlElement private Angle longitude;
044      
045      private CelestialCoordinateSystem coordSys;
046      
047      /**
048       * Creates a new AZ / EL position where both dimensions are at their midpoints.
049       */
050      public EvlaPointingPosition()
051      {
052        coordSys  = CelestialCoordinateSystem.HORIZONTAL;
053        longitude = EvlaTelescopeMotionSimulator.getAzimuthDefault();
054        latitude  = EvlaTelescopeMotionSimulator.getElevationDefault();
055      }
056      
057      //============================================================================
058      // SIMPLE GETTERS AND SETTERS
059      //============================================================================
060      
061      /**
062       * Returns the latitude of this position.
063       * This value is guaranteed not to be <i>null</i>.
064       * It is also a copy of the one held internally by this position.
065       * 
066       * @return the latitude of this position.
067       */
068      @XmlTransient //accessing variable directly
069      public Angle getLatitude()  { return latitude.clone(); }
070    
071      /**
072       * Sets the latitude of this position.
073       * 
074       * @param newLatitude
075       *   the new latitude of this position.
076       *   
077       * @throws IllegalArgumentException
078       *   if the parameter is null or out of bounds.
079       *   
080       * @see #getLatitudeMaximum()
081       * @see #getLatitudeMinimum()
082       */
083      public void setLatitude(Angle newLatitude)
084      {
085        if (newLatitude == null)
086          throw new IllegalArgumentException("You may not set a NULL latitude.");
087    
088        if (!latitudeIsValid(newLatitude))
089          throw new IllegalArgumentException("The " + getCoordinateSystem().getNameOfLatitude() + " of " + newLatitude +
090            " is not valid.  It must be in the range " +
091            getLatitudeMinimum() + " to " +
092            getLatitudeMaximum() + ", inclusive.");
093          
094        latitude = newLatitude.clone();
095      }
096    
097      /**
098       * Returns the longitude of this position.
099       * This value is guaranteed not to be <i>null</i>.
100       * It is also a copy of the one held internally by this position.
101       * 
102       * @return the longitude of this position.
103       */
104      @XmlTransient //accessing variable directly
105      public Angle getLongitude()  { return longitude.clone(); }
106    
107      /**
108       * Sets the longitude of this position.
109       * 
110       * @param newLongitude
111       *   the new longitude of this position.
112       *   
113       * @throws IllegalArgumentException
114       *   if the parameter is null or out of bounds.
115       *   
116       * @see #getLongitudeMaximum()
117       * @see #getLongitudeMinimum()
118       */
119      public void setLongitude(Angle newLongitude)
120      {
121        if (newLongitude == null)
122          throw new IllegalArgumentException("You may not set a NULL longitude.");
123    
124        if (!longitudeIsValid(newLongitude))
125          throw new IllegalArgumentException("The " + getCoordinateSystem().getNameOfLongitude() + " of " + newLongitude +
126            " is not valid.  It must be in the range " +
127            getLongitudeMinimum() + " to " +
128            getLongitudeMaximum() + ", inclusive.");
129          
130        longitude = newLongitude.clone();
131      }
132    
133      /**
134       * Returns the coordinate system used by this position.
135       * @return the coordinate system used by this position.
136       */
137      public CelestialCoordinateSystem getCoordinateSystem()
138      {
139        return coordSys;
140      }
141    
142      /**
143       * Sets the coordinate system used by this position.
144       * <p>
145       * It is possible that current the latitude and/or longitude of this
146       * position will not be compatible with the new system.
147       * Note that this method will <i>not</i> automatically
148       * correct the positions, nor will it complain about the
149       * new system.  Clients who are concerned about this possibility
150       * should call the {@link #latitudeIsValid()} and
151       * {@link #longitudeIsValid()} methods.
152       * 
153       * @param newSystem
154       *   a new coordinate system for this position.
155       */
156      public void setCoordinateSystem(CelestialCoordinateSystem newSystem)
157      {
158        if (newSystem == null)
159          throw new IllegalArgumentException("You may not set a NULL coordinate system.");
160    
161        if (!coordSys.equals(newSystem))
162        {
163          boolean convert = coordSys.equals(EQUATORIAL);
164          
165          coordSys = newSystem;
166          
167          if (convert)
168          {
169            latitude.convertTo(DEGREE);
170            longitude.convertTo(DEGREE);
171          }
172        }
173      }
174      
175      //============================================================================
176      // TEXT BASED
177      //============================================================================
178      
179      //These were added to aid the OPT display.  DMH 2009-02-27
180      
181      @XmlTransient
182      public String getLatitudeText()
183      {
184        String answer;
185        
186        //Kludgy code here.    
187        if (coordSys.equals(EQUATORIAL))
188        {
189          answer = latitude.toStringDms();
190        }
191        else if (coordSys.equals(HORIZONTAL))
192        {
193          answer = latitude.convertTo(DEGREE).toString();
194        }
195        else
196        {
197          ArcUnits units = latitude.getUnits();
198    
199          if (units.equals(ARC_SECOND) || units.equals(SECOND))
200            answer = latitude.toStringDms();
201          else
202            answer = latitude.toString();
203        }
204    
205        return answer;
206      }
207      
208      @XmlTransient
209      public String getLongitudeText()
210      {
211        String answer;
212        
213        //Kludgy code here.    
214        if (coordSys.equals(EQUATORIAL))
215        {
216          answer = longitude.toStringHms();
217        }
218        else if (coordSys.equals(HORIZONTAL))
219        {
220          answer = longitude.convertTo(DEGREE).toString();
221        }
222        else
223        {
224          ArcUnits units = longitude.getUnits();
225    
226          if (units.equals(ARC_SECOND) || units.equals(SECOND))
227            answer = longitude.toStringHms();
228          else
229            answer = longitude.toString();
230        }
231    
232        return answer;
233      }
234      
235      private static final Latitude LAT_PARSER = new Latitude();
236      
237      public void setLatitude(String text)
238      {
239        if (coordSys.equals(HORIZONTAL))
240        {
241          try
242          {
243            //Will throw exception for hms, dms, colon-delim.
244            //We do NOT, though, start w/ Latitude's parser because we want
245            //to allow non-normalized degree values.
246            latitude.set(text);
247          }
248          catch (IllegalArgumentException ex)
249          {
250            LAT_PARSER.set(text);
251            latitude = LAT_PARSER.toAngle().convertTo(DEGREE);
252          }
253        }
254        else
255        {
256          LAT_PARSER.set(text);
257          latitude = LAT_PARSER.toAngle();
258        }
259      }
260    
261      private static final Longitude LON_PARSER = new Longitude();
262      
263      public void setLongitude(String text)
264      {
265        if (coordSys.equals(HORIZONTAL))
266        {
267          try
268          {
269            //Will throw exception for hms, dms, colon-delim.
270            //We do NOT, though, start w/ Longitude's parser because we want
271            //to allow non-normalized degree values.
272            longitude.set(text);
273          }
274          catch (IllegalArgumentException ex)
275          {
276            LON_PARSER.set(text);
277            longitude = LON_PARSER.toAngle().convertTo(DEGREE);
278          }
279        }
280        else
281        {
282          LON_PARSER.set(text);
283          longitude = LON_PARSER.toAngle();
284        }
285      }
286      
287      //============================================================================
288      // VALIDATION
289      //============================================================================
290      
291      private boolean latitudeIsValid(Angle a)
292      {
293        return a.compareTo(getLatMin()) >= 0 && a.compareTo(getLatMax()) <= 0;
294      }
295    
296      private boolean longitudeIsValid(Angle a)
297      {
298        return a.compareTo(getLonMin()) >= 0 && a.compareTo(getLonMax()) <= 0;
299      }
300      
301      /**
302       * Returns <i>true</i> if this position's latitude value is valid with
303       * respect to its coordinate system.
304       */
305      public boolean latitudeIsValid()
306      {
307        return latitudeIsValid(latitude);
308      }
309      
310      /**
311       * Returns <i>true</i> if this position's longitude value is valid with
312       * respect to its coordinate system.
313       */
314      public boolean longitudeIsValid()
315      {
316        return longitudeIsValid(longitude);
317      }
318      
319      //============================================================================
320      // MINIMUMS & MAXIMUMS
321      //============================================================================
322    
323      private static final Angle LAT_MIN = new Angle("-90.0");
324      private static final Angle LAT_MAX = new Angle("+90.0");
325    
326      private static final Angle EL_MIN = EvlaTelescopeMotionSimulator.getElevationMinimum();
327      private static final Angle EL_MAX = EvlaTelescopeMotionSimulator.getElevationMaximum();
328    
329      private static final Angle LON_MIN = new Angle("0.0");
330      private static final Angle LON_MAX = new Angle("360.0");
331    
332      private static final Angle AZ_MIN = EvlaTelescopeMotionSimulator.getAzimuthMinimum();
333      private static final Angle AZ_MAX = EvlaTelescopeMotionSimulator.getAzimuthMaximum();
334    
335      private Angle getLatMax()
336      {
337        return coordSys.equals(HORIZONTAL) ? EL_MAX : LAT_MAX;
338      }
339    
340      private Angle getLatMin()
341      {
342        return coordSys.equals(HORIZONTAL) ? EL_MIN : LAT_MIN;
343      }
344    
345      private Angle getLonMax()
346      {
347        return coordSys.equals(HORIZONTAL) ? AZ_MAX : LON_MAX;
348      }
349    
350      private Angle getLonMin()
351      {
352        return coordSys.equals(HORIZONTAL) ? AZ_MIN : LON_MIN;
353      }
354      
355      /**
356       * Returns the maximum latitude for this pointing position, given
357       * the chosen {@link #getCoordinateSystem() coordinate system}.
358       */
359      public Angle getLatitudeMaximum()  { return getLatMax().clone(); }
360      
361      /**
362       * Returns the minimum latitude for this pointing position, given
363       * the chosen {@link #getCoordinateSystem() coordinate system}.
364       */
365      public Angle getLatitudeMinimum()  { return getLatMin().clone(); }
366    
367      /**
368       * Returns the maximum longitude for this pointing position, given
369       * the chosen {@link #getCoordinateSystem() coordinate system}.
370       */
371      public Angle getLongitudeMaximum()  { return getLonMax().clone(); }
372      
373      /**
374       * Returns the minimum longitude for this pointing position, given
375       * the chosen {@link #getCoordinateSystem() coordinate system}.
376       */
377      public Angle getLongitudeMinimum()  { return getLonMin().clone(); }
378      
379      //============================================================================
380      // MISC
381      //============================================================================
382    
383      /**
384       * Converts this pointing position to a sky position.
385       * The latitude and longitude values of this class will be
386       * normalized according to the rules of the 
387       * {@link Latitude} and {@link Longitude} classes.
388       * Also, equatorial (RA, Dec) coordinates are <i>assumed</i>
389       * to be J2000.
390       * 
391       * @return
392       *   the sky position that is equivalent to this pointing position.
393       */
394      public SkyPosition toSkyPosition()
395      {
396        SimpleSkyPosition skyPos = new SimpleSkyPosition(coordSys, Epoch.J2000);
397        
398        skyPos.setLatitude (new Latitude (latitude ));
399        skyPos.setLongitude(new Longitude(longitude));
400        
401        return skyPos;
402      }
403      
404      /**
405       * Returns a copy of this position.
406       * <p>
407       * If anything goes wrong during the cloning procedure,
408       * a {@code RuntimeException} will be thrown.</p>
409       */
410      @Override
411      public EvlaPointingPosition clone()
412      {
413        EvlaPointingPosition clone = null;
414       
415        try
416        {
417          //This line takes care of the primitive & immutable fields properly
418          clone = (EvlaPointingPosition)super.clone();
419          
420          clone.latitude  = this.latitude.clone();
421          clone.longitude = this.longitude.clone();
422        }
423        catch (Exception ex)
424        {
425          throw new RuntimeException(ex);
426        }
427    
428        return clone;
429      }
430      
431      /**
432       * Returns <i>true</i> if <tt>o</tt> is equal to this position.
433       */
434      @Override
435      public boolean equals(Object o)
436      {
437        //Quick exit if o is this
438        if (o == this)
439          return true;
440        
441        //Quick exit if o is null
442        if (o == null)
443          return false;
444       
445        //Quick exit if classes are different
446        if (!o.getClass().equals(this.getClass()))
447          return false;
448       
449        //A safe cast if we got this far
450        EvlaPointingPosition other = (EvlaPointingPosition)o;
451        
452        return
453          other.coordSys.equals(this.coordSys) &&
454          other.latitude.equals(this.latitude) &&
455          other.longitude.equals(this.longitude);
456      }
457    
458      /** Returns a hash code value for this position. */
459      @Override
460      public int hashCode()
461      {
462        //Taken from the Effective Java book by Joshua Bloch.
463        //The constants 17 & 37 are arbitrary & carry no meaning.
464        int result = 17;
465        
466        //You MUST keep this method in sync w/ the equals method
467        
468        result = 37 * result + coordSys.hashCode();
469        result = 37 * result + latitude.hashCode();
470        result = 37 * result + longitude.hashCode();
471        
472        return result;
473      }
474    }