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 }