001 package edu.nrao.sss.astronomy; 002 003 import java.util.ArrayList; 004 import java.util.Collections; 005 import java.util.Comparator; 006 import java.util.Date; 007 import java.util.List; 008 009 import javax.xml.bind.annotation.XmlElement; 010 import javax.xml.bind.annotation.XmlRootElement; 011 import javax.xml.bind.annotation.XmlTransient; 012 013 import edu.nrao.sss.geom.EarthPosition; 014 import edu.nrao.sss.geom.SphericalPosition; 015 import edu.nrao.sss.measure.Angle; 016 import edu.nrao.sss.measure.Distance; 017 import edu.nrao.sss.measure.Latitude; 018 import edu.nrao.sss.measure.LocalSiderealTime; 019 import edu.nrao.sss.measure.Longitude; 020 021 /** 022 * A table of {@link PolynomialPosition polynomial positions}. 023 * <p> 024 * This table is sorted according to the natural order of 025 * {@code PolynomialPosition}, which will result in positions with the 026 * earliest valid time intervals preceding those with later intervals. 027 * This table is kept in sorted order at all times.</p> 028 * <p> 029 * <b>Version Info:</b> 030 * <table style="margin-left:2em"> 031 * <tr><td>$Revision: 1156 $</td></tr> 032 * <tr><td>$Date: 2008-03-12 11:43:13 -0600 (Wed, 12 Mar 2008) $</td></tr> 033 * <tr><td>$Author: dharland $</td></tr> 034 * </table></p> 035 * 036 * @author David M. Harland 037 * @since 2007-04-17 038 */ 039 @XmlRootElement 040 public class PolynomialPositionTable 041 implements SkyPosition 042 { 043 @XmlElement(name="polynomialPosition") 044 private List<PolynomialPosition> entries; 045 046 /** Creates a new instance. */ 047 public PolynomialPositionTable() 048 { 049 initialize(); 050 051 entries = new ArrayList<PolynomialPosition>(); 052 } 053 054 private void initialize() 055 { 056 //id = Identifiable.UNIDENTIFIED; 057 } 058 059 /** 060 * Clears all entries from this table. 061 * A reset table is equivalent to a new table created via the no-argument 062 * constructor. 063 */ 064 public void reset() 065 { 066 initialize(); 067 068 entries.clear(); 069 } 070 071 /* (non-Javadoc) 072 * @see edu.nrao.sss.astronomy.SkyPosition#isMoving() 073 */ 074 public boolean isMoving() 075 { 076 int entryCount = entries.size(); 077 078 if (entryCount == 0) return false; 079 else if (entryCount > 1) return true; 080 else /*entryCount == 1*/ return entries.get(0).isMoving(); 081 } 082 083 //============================================================================ 084 // 085 //============================================================================ 086 087 /** 088 * Sets the coordinate system of all entries in this table. 089 * If {@code newSystem} is <i>null</i>, this method does nothing. 090 * @param newSystem the new coordinate system for all entries in this table. 091 */ 092 @XmlTransient 093 public void setCoordinateSystem(CelestialCoordinateSystem newSystem) 094 { 095 if (newSystem != null) 096 for (PolynomialPosition entry : entries) 097 entry.setCoordinateSystem(newSystem); 098 } 099 100 /** 101 * Sets the epoch of all entries in this table. 102 * If {@code newEpoch} is <i>null</i>, this method does nothing. 103 * @param newEpoch the new epoch for all entries in this table. 104 */ 105 @XmlTransient 106 public void setEpoch(Epoch newEpoch) 107 { 108 if (newEpoch != null) 109 for (PolynomialPosition entry : entries) 110 entry.setEpoch(newEpoch); 111 } 112 113 /** 114 * Sets the origin of information of all entries in this table. 115 * If {@code newOrigin} is <i>null</i>, this method does nothing. 116 * @param newOrigin the new origin of information for all entries 117 * in this table. 118 */ 119 @XmlTransient 120 public void setOriginOfInformation(String newOrigin) 121 { 122 if (newOrigin != null) 123 for (PolynomialPosition entry : entries) 124 entry.setOriginOfInformation(newOrigin); 125 } 126 127 //============================================================================ 128 // ADDING, REMOVING, FETCHING, & REPLACING ENTRIES 129 //============================================================================ 130 131 //OLD Philosophies: 132 // 133 // 1. Store and return clones. (We return actual objects only if those 134 // objects were removed from this table.) 135 // 136 // 2. Sort ASAP -- as soon entries is changed. 137 138 139 //NEW Philosophies: 140 // 141 // 1. Store and retrieve actual references. 142 // 143 // 2. Don't bother keeping internally sorted list 144 145 146 /** 147 * Adds {@code newPosition} to this table. 148 * <p> 149 * If {@code newPosition} is <i>null</i>, this method does nothing. 150 * Otherwise this table will hold a reference to {@code newPosition}. 151 * This means that any changes made to {@code newPosition} after 152 * a call to this method will be reflected in this object.</p> 153 * 154 * @param newPosition a new position to be added to this table. 155 * @return <i>true</i> if {@code newPosition} was added to this table. 156 */ 157 public boolean add(PolynomialPosition newPosition) 158 { 159 return (newPosition == null) ? false : entries.add(newPosition); 160 } 161 162 /** 163 * Removes the first occurrence of the unwanted position from this table. 164 * @param unwantedPosition a position to be removed from this table. 165 * @return <i>true</i> if the unwanted position was removed. 166 */ 167 public boolean remove(PolynomialPosition unwantedPosition) 168 { 169 return (unwantedPosition == null) ? false 170 : entries.remove(unwantedPosition); 171 } 172 173 /** 174 * Removes the {@code index}<sup>th</sup> position from this table. 175 * @param index to index of the position to be removed. 176 * @return the position that had been at {@code index}. 177 */ 178 public PolynomialPosition remove(int index) 179 { 180 return entries.remove(index); 181 } 182 183 /** 184 * Replaces the position currently at {@code index} with 185 * {@code replacement}. 186 * <p> 187 * If {@code newPosition} is <i>null</i>, this method does nothing. 188 * Otherwise this table will hold a reference to {@code newPosition}. 189 * This means that any changes made to {@code newPosition} after 190 * a call to this method will be reflected in this object.</p> 191 * 192 * @param index the index of the position to be replaced. 193 * @param replacement the new position for the {@code index}<sup>th</sup> 194 * slot in this table. 195 * @return the position that had been at {@code index}. 196 */ 197 public PolynomialPosition set(int index, PolynomialPosition replacement) 198 { 199 PolynomialPosition priorEntry; 200 201 if (replacement != null) 202 { 203 priorEntry = entries.get(index); 204 entries.set(index, replacement); 205 } 206 else 207 { 208 priorEntry = null; 209 } 210 211 return priorEntry; 212 } 213 214 /** 215 * Returns the position at {@code index}. 216 * <p> 217 * Note that the returned position is the one actually held by this table. 218 * This means that any changes made to it after 219 * a call to this method will be reflected in this object.</p> 220 * 221 * @param index the index of the desired position. 222 * @return a reference to the position at {@code index}. 223 */ 224 public PolynomialPosition get(int index) 225 { 226 return entries.get(index); 227 } 228 229 /** 230 * Returns a position for which {@code time} is a valid time. 231 * <p> 232 * If this table has no such position, <i>null</i> is returned. 233 * If this table has <i>more</i> than one such position, the one held 234 * at the index of lowest value will be returned. Just which position 235 * that is depends on whether, and how, this table was most recently 236 * sorted by its clients.</p> 237 * <p> 238 * Note that the returned position is the one actually held by this table. 239 * This means that any changes made to it after 240 * a call to this method will be reflected in this object.</p> 241 * 242 * @param time the time for which a position is desired. The returned 243 * position, unless it is <i>null</i> will have a valid time 244 * interval that contains this value. 245 * 246 * @return the a position for which {@code time} is a valid time, or 247 * <i>null</i> if this table has no such position. 248 */ 249 public PolynomialPosition get(Date time) 250 { 251 for (PolynomialPosition entry : entries) 252 if (entry.isValidFor(time)) 253 return entry; 254 255 return null; 256 } 257 258 /** 259 * Returns a new list that contains references to all the entries in this 260 * table. The entries are ordered in the list in the same way they are 261 * ordered in this table. 262 * 263 * @return a new list of this table's entries. 264 */ 265 public List<PolynomialPosition> getEntries() 266 { 267 return new ArrayList<PolynomialPosition>(entries); 268 } 269 270 //============================================================================ 271 // 272 //============================================================================ 273 274 /** 275 * Returns the number of entries in this table. 276 * @return the size of this table. 277 */ 278 public int size() 279 { 280 return entries.size(); 281 } 282 283 /** 284 * Returns <i>true</i> if this table has one or more positions for which 285 * {@code time} is a valid time. 286 * 287 * @param time the time for which a valid position is sought. 288 * 289 * @return <i>true</i> if this table has one or more positions for which 290 * {@code time} is a valid time. 291 */ 292 public boolean isValidFor(Date time) 293 { 294 for (PolynomialPosition entry : entries) 295 if (entry.isValidFor(time)) 296 return true; 297 298 return false; 299 } 300 301 /** 302 * Returns <i>true</i> if the valid time intervals of the positions of this 303 * table do not overlap and do not contain gaps. Note that by this definition 304 * an empty table is well formed. 305 * 306 * @return <i>true</i> if the valid time intervals of the positions of this 307 * table are contiguous. 308 */ 309 public boolean isWellFormed() 310 { 311 ArrayList<PolynomialPosition> tempList = 312 new ArrayList<PolynomialPosition>(entries); 313 314 Collections.sort(tempList); 315 316 //This code depends on the PolynomialPosition's "natural" ordering 317 //wherein positions with earlier valid time ranges precede those with later. 318 for (int e=tempList.size()-1; e > 0; e--) 319 { 320 PolynomialPosition later = tempList.get(e); 321 PolynomialPosition earlier = tempList.get(e-1); 322 323 if (!later.getValidTime().isContiguousWith(earlier.getValidTime())) 324 return false; 325 } 326 327 return true; 328 } 329 330 /** 331 * Sorts this table so that its entries are in their natural order. 332 */ 333 public void sort() 334 { 335 Collections.sort(entries); 336 } 337 338 /** 339 * Sorts this table so that its entries are in the order dictated by 340 * {@code comparator}. 341 */ 342 public void sort(Comparator<PolynomialPosition> comparator) 343 { 344 Collections.sort(entries, comparator); 345 } 346 347 //============================================================================ 348 // INTERFACE SkyPosition 349 //============================================================================ 350 351 /** 352 * Returns either the PolynomialPosition whose valid time contains 353 * {@code time} or, if there is no such position, a newly created position. 354 */ 355 private SkyPosition getNonNullPosition(Date time) 356 { 357 SkyPosition position = get(time); 358 359 if (position == null) 360 position = new SimpleSkyPosition(); 361 362 return position; 363 } 364 365 public SkyPositionType getType() { return SkyPositionType.POLYNOMIAL_TABLE; } 366 367 //TODO document methods for how they behave for invalid times 368 369 public CelestialCoordinateSystem getCoordinateSystem() 370 { 371 return getNonNullPosition(new Date()).getCoordinateSystem(); 372 } 373 374 public Epoch getEpoch() 375 { 376 return getNonNullPosition(new Date()).getEpoch(); 377 } 378 379 public String getOriginOfInformation() 380 { 381 return getNonNullPosition(new Date()).getOriginOfInformation(); 382 } 383 384 public Longitude getLongitude() 385 { 386 return getLongitude(new Date()); 387 } 388 389 public Latitude getLatitude() 390 { 391 return getLatitude(new Date()); 392 } 393 394 public Distance getDistance() 395 { 396 return getDistance(new Date()); 397 } 398 399 public Longitude getLongitude(Date time) 400 { 401 return getNonNullPosition(time).getLongitude(time); 402 } 403 404 public Latitude getLatitude(Date time) 405 { 406 return getNonNullPosition(time).getLatitude(time); 407 } 408 409 public Distance getDistance(Date time) 410 { 411 return getNonNullPosition(time).getDistance(time); 412 } 413 414 public Longitude getLongitudeUncertainty() 415 { 416 return getNonNullPosition(new Date()).getLongitudeUncertainty(); 417 } 418 419 public Latitude getLatitudeUncertainty() 420 { 421 return getNonNullPosition(new Date()).getLatitudeUncertainty(); 422 } 423 424 public Distance getDistanceUncertainty() 425 { 426 return getNonNullPosition(new Date()).getDistanceUncertainty(); 427 } 428 429 /* (non-Javadoc) 430 * @see SphericalPosition#getAngularSeparation(SphericalPosition) 431 */ 432 public Angle getAngularSeparation(SphericalPosition other) 433 { 434 return getAngularSeparation(other, new Date()); 435 } 436 437 /* (non-Javadoc) 438 * @see SphericalPosition#getAngularSeparation(SphericalPosition, Date) 439 */ 440 public Angle getAngularSeparation(SphericalPosition other, Date time) 441 { 442 return getNonNullPosition(time).getAngularSeparation(other, time); 443 } 444 445 //============================================================================ 446 // 447 //============================================================================ 448 449 /* (non-Javadoc) 450 * @see SkyPosition#toPosition(CelestialCoordinateSystem, Epoch) 451 */ 452 public SkyPosition toPosition(CelestialCoordinateSystem system, 453 Epoch epoch, 454 EarthPosition observer, 455 LocalSiderealTime lst) 456 throws CoordinateConversionException 457 { 458 return toPosition(system, epoch, observer, lst,AbsSkyPos.DEFAULT_CONVERTER); 459 } 460 461 /* (non-Javadoc) 462 * @see SkyPosition#toPosition(CelestialCoordinateSystem, Epoch, 463 * CelestialCoordinateConverter) 464 */ 465 public SkyPosition toPosition(CelestialCoordinateSystem system, 466 Epoch epoch, 467 EarthPosition observer, 468 LocalSiderealTime lst, 469 CelestialCoordinateConverter converter) 470 throws CoordinateConversionException 471 { 472 return converter.createFrom(this, system, epoch, observer, lst); 473 } 474 475 //============================================================================ 476 // 477 //============================================================================ 478 479 /** 480 * Returns a copy of this table. 481 * <p> 482 * If anything goes wrong during the cloning procedure, 483 * a {@code RuntimeException} will be thrown.</p> 484 */ 485 @Override 486 public PolynomialPositionTable clone() 487 { 488 PolynomialPositionTable clone = null; 489 490 try 491 { 492 //This line takes care of the primitive & immutable fields properly 493 clone = (PolynomialPositionTable)super.clone(); 494 495 //Clone gets its own list, populated with clones from this table's list 496 clone.entries = new ArrayList<PolynomialPosition>(); 497 for (PolynomialPosition thisEntry : this.entries) 498 clone.entries.add(thisEntry.clone()); 499 } 500 catch (Exception ex) 501 { 502 throw new RuntimeException(ex); 503 } 504 505 return clone; 506 } 507 508 /** Returns <i>true</i> if {@code o} is equal to this table. */ 509 @Override 510 public boolean equals(Object o) 511 { 512 //Quick exit if o is this 513 if (o == this) 514 return true; 515 516 //Quick exit if o is null 517 if (o == null) 518 return false; 519 520 //Quick exit if classes are different 521 if (!o.getClass().equals(this.getClass())) 522 return false; 523 524 PolynomialPositionTable other = (PolynomialPositionTable)o; 525 526 return this.entries.equals(other.entries); 527 } 528 529 /** Returns a hash code value for this table. */ 530 @Override 531 public int hashCode() 532 { 533 //Taken from the Effective Java book by Joshua Bloch. 534 //The constants 17 & 37 are arbitrary & carry no meaning. 535 int result = 17; 536 537 //You MUST keep this method in synch with equals() 538 539 result = 37 * result + entries.hashCode(); 540 541 return result; 542 } 543 }