001 package edu.nrao.sss.astronomy; 002 003 import java.awt.geom.Point2D; 004 import java.io.FileInputStream; 005 import java.io.FileNotFoundException; 006 import java.io.InputStream; 007 import java.io.IOException; 008 import java.io.Reader; 009 import java.io.Writer; 010 import java.math.BigDecimal; 011 import java.util.Date; 012 import java.util.List; 013 import java.util.SortedMap; 014 import java.util.TreeMap; 015 016 import javax.xml.bind.JAXBException; 017 import javax.xml.bind.annotation.XmlElement; 018 import javax.xml.bind.annotation.XmlRootElement; 019 import javax.xml.stream.XMLStreamException; 020 021 import edu.nrao.sss.math.CubicSpline; 022 import edu.nrao.sss.math.Interpolator; 023 import edu.nrao.sss.measure.ArcUnits; 024 import edu.nrao.sss.measure.DistanceUnits; 025 import edu.nrao.sss.measure.Latitude; 026 import edu.nrao.sss.measure.Distance; 027 import edu.nrao.sss.measure.LinearVelocity; 028 import edu.nrao.sss.measure.LinearVelocityUnits; 029 import edu.nrao.sss.measure.Longitude; 030 import edu.nrao.sss.measure.TimeInterval; 031 import edu.nrao.sss.util.JaxbUtility; 032 import edu.nrao.sss.util.StringUtil; 033 034 /** 035 * A table of position entries for an astronomical source. 036 * <p> 037 * <b>Version Info:</b> 038 * <table style="margin-left:2em"> 039 * <tr><td>$Revision: 1707 $</td></tr> 040 * <tr><td>$Date: 2008-11-14 10:23:59 -0700 (Fri, 14 Nov 2008) $</td></tr> 041 * <tr><td>$Author: dharland $</td></tr> 042 * </table></p> 043 * 044 * @author David M. Harland 045 * @since 2006-06-07 046 */ 047 @XmlRootElement 048 public class EphemerisTable 049 extends AbsSkyPos 050 { 051 //Units used by interpolators to ensure that interpolation is done 052 //using a consistent set of units. 053 private static final ArcUnits INTERP_ARC_UNITS = 054 ArcUnits.ARC_SECOND; 055 056 private static final DistanceUnits INTERP_DIST_UNITS = 057 DistanceUnits.KILOMETER; 058 059 private static final LinearVelocityUnits INTERP_VELOC_UNITS = 060 LinearVelocityUnits.KILOMETERS_PER_SECOND; 061 062 //The table itself 063 SortedMap<Date, EphemerisTableEntry> entries; 064 065 //Used for calculating values in between the points in the table 066 private Interpolator interpLat; 067 private Interpolator interpLon; 068 private Interpolator interpDist; 069 private Interpolator interpVel; 070 071 private boolean needToAdjustLongitudeInterpolator; 072 073 /** Creates a new instance. */ 074 public EphemerisTable() 075 { 076 super(); 077 078 entries = new TreeMap<Date, EphemerisTableEntry>(); 079 080 createInterpolators(); 081 082 needToAdjustLongitudeInterpolator = true; 083 } 084 085 private void createInterpolators() 086 { 087 interpLat = new CubicSpline(); 088 interpLon = new CubicSpline(); 089 interpDist = new CubicSpline(); 090 interpVel = new CubicSpline(); 091 } 092 093 /** 094 * Clears all entries from this table. 095 * <p> 096 * A reset table is equivalent to a new table created via the no-argument 097 * constructor. Note that this means that even the class used as an 098 * interpolator is reset by this method.</p> 099 */ 100 @Override 101 public void reset() 102 { 103 super.reset(); 104 105 resetEntries(); 106 } 107 108 private void resetEntries() 109 { 110 entries.clear(); 111 112 createInterpolators(); 113 114 needToAdjustLongitudeInterpolator = true; 115 } 116 117 /* (non-Javadoc) 118 * @see edu.nrao.sss.astronomy.SkyPosition#isMoving() 119 */ 120 public boolean isMoving() { return entries.size() > 1; } 121 122 //=========================================================================== 123 // VALID TIME RANGE 124 //=========================================================================== 125 126 /** 127 * Returns the interval of time for which this table is valid. 128 * 129 * @return the interval of time for which this table is valid. 130 */ 131 public TimeInterval getValidTime() 132 { 133 TimeInterval result = new TimeInterval(); 134 135 //TimeInterval is half-open, but we want closed interval, so we add 136 //smallest unit of time (one millisecond) to end of interval. 137 if (entries.size() > 0) 138 result.set(entries.firstKey(), new Date(entries.lastKey().getTime()+1L)); 139 140 return result; 141 } 142 143 /** 144 * Returns <i>true</i> if this position is valid for 145 * the given point in time. 146 * 147 * @param time the point in time to be checked. 148 */ 149 public boolean isValidFor(Date time) 150 { 151 return getValidTime().contains(time); 152 } 153 154 //=========================================================================== 155 // INTERFACE SkyPosition 156 //=========================================================================== 157 158 /* (non-Javadoc) 159 * @see SkyPosition#getType() 160 */ 161 public SkyPositionType getType() { return SkyPositionType.EPHEMERIS_TABLE; } 162 163 /** 164 * Returns the longitude of this position at the given point in time. 165 * <p> 166 * If {@link #isValidFor(Date) isValidFor(time)} returns <i>false</i> 167 * this method will throw an {@code IllegalArgumentException}.</p> 168 * 169 * @param time the point in time for which a value is requested. 170 * 171 * @return the longitude of this position at the given point in time. 172 * 173 * @throws IllegalArgumentException if {@code param} is not a valid time, 174 * as determined by {@link #isValidFor(Date)}. 175 */ 176 public Longitude getLongitude(Date time) 177 { 178 //Throw IllegalArgumentException if time is out of range 179 assertValidTime(time); 180 181 //If this table was reconstituted from a persistent store (a database, 182 //XML file, etc.), the interpolators (as unpersisted attributes) 183 //may not be properly populated. 184 if (this.size() != interpLon.size()) 185 repopulateInterpolators(); 186 187 //There are some special issues with longitude, due to the discontinuity 188 //at the 360/0-degree point. 189 if (needToAdjustLongitudeInterpolator) 190 rationalizeLongitudeInterpolator(); 191 192 //Use interpolator to get value in "normal" units 193 double value = interpLon.getValueFor(time.getTime()); 194 195 return new Longitude(Double.toString(value), INTERP_ARC_UNITS); 196 } 197 198 /** 199 * Returns the latitude of this position at the given point in time. 200 * <p> 201 * If {@link #isValidFor(Date) isValidFor(time)} returns <i>false</i> 202 * this method will throw an {@code IllegalArgumentException}.</p> 203 * 204 * @param time the point in time for which a value is requested. 205 * 206 * @return the latitude of this position at the given point in time. 207 * 208 * @throws IllegalArgumentException if {@code param} is not a valid time, 209 * as determined by {@link #isValidFor(Date)}. 210 */ 211 public Latitude getLatitude(Date time) 212 { 213 //Throw IllegalArgumentException if time is out of range 214 assertValidTime(time); 215 216 //If this table was reconstituted from a persistent store (a database, 217 //XML file, etc.), the interpolators (as unpersisted attributes) 218 //may not be properly populated. 219 if (this.size() != interpLat.size()) 220 repopulateInterpolators(); 221 222 //Use interpolator to get value in "normal" units 223 double value = interpLat.getValueFor(time.getTime()); 224 225 return new Latitude(Double.toString(value), INTERP_ARC_UNITS); 226 } 227 228 /** 229 * Returns the distance of this position at the given point in time. 230 * <p> 231 * If {@link #isValidFor(Date) isValidFor(time)} returns <i>false</i> 232 * this method will throw an {@code IllegalArgumentException}.</p> 233 * 234 * @param time the point in time for which a value is requested. 235 * 236 * @return the distance of this position at the given point in time. 237 * 238 * @throws IllegalArgumentException if {@code param} is not a valid time, 239 * as determined by {@link #isValidFor(Date)}. 240 */ 241 public Distance getDistance(Date time) 242 { 243 //Throw IllegalArgumentException if time is out of range 244 assertValidTime(time); 245 246 //If this table was reconstituted from a persistent store (a database, 247 //XML file, etc.), the interpolators (as unpersisted attributes) 248 //may not be properly populated. 249 if (this.size() != interpDist.size()) 250 repopulateInterpolators(); 251 252 //Use interpolator to get value in "normal" units 253 BigDecimal value = 254 BigDecimal.valueOf(interpDist.getValueFor(time.getTime())); 255 256 return new Distance(value, INTERP_DIST_UNITS); 257 } 258 259 /** 260 * Returns the radial velocity of this position at the given point in time. 261 * <p> 262 * If {@link #isValidFor(Date) isValidFor(time)} returns <i>false</i> 263 * this method will throw an {@code IllegalArgumentException}.</p> 264 * 265 * @param time the point in time for which a value is requested. 266 * 267 * @return the radial velocity of this position at the given point in time. 268 * 269 * @throws IllegalArgumentException if {@code param} is not a valid time, 270 * as determined by {@link #isValidFor(Date)}. 271 */ 272 public LinearVelocity getVelocity(Date time) 273 { 274 //Throw IllegalArgumentException if time is out of range 275 assertValidTime(time); 276 277 //If this table was reconstituted from a persistent store (a database, 278 //XML file, etc.), the interpolators (as unpersisted attributes) 279 //may not be properly populated. 280 if (this.size() != interpVel.size()) 281 repopulateInterpolators(); 282 283 //Use interpolator to get value in "normal" units 284 BigDecimal value = 285 BigDecimal.valueOf(interpVel.getValueFor(time.getTime())); 286 287 return new LinearVelocity(value, INTERP_VELOC_UNITS); 288 } 289 290 /** 291 * Returns the table entry with the latest time that is before or 292 * coincident with {@code time}. If {@code time} is earlier than 293 * the earliest entry in the table, <i>null</i> is returned. 294 * 295 Doesn't look like we use this method. Not even for Hibernate or JAXB? 296 297 private EphemerisTableEntry getEntryFor(Date time) 298 { 299 SortedMap<Date, EphemerisTableEntry> tailMap = entries.tailMap(time); 300 301 return (tailMap.size() < 1) ? null : entries.get(tailMap.firstKey()); 302 }*/ 303 304 /** Throws IllegalArgumentException it time is outside valid range. */ 305 private void assertValidTime(Date time) 306 { 307 if (!this.isValidFor(time)) 308 throw new IllegalArgumentException("The requested time of " + time + 309 " is not in the valid range, " + getValidTime() + 310 ", for this table."); 311 } 312 313 //=========================================================================== 314 // ENTRIES 315 //=========================================================================== 316 317 /** 318 * Adds {@code entry} to this table. 319 * <p> 320 * If this table already has an entry with the same timestamp as 321 * {@code entry}, the existing entry will be replaced with 322 * {@code entry}.</p> 323 * 324 * @param entry a new, or replacement, entry for this table. If this value 325 * is <i>null</i> this method does nothing. 326 */ 327 void add(EphemerisTableEntry entry) 328 { 329 if (entry != null) 330 { 331 entries.put(entry.getTime(), entry); 332 addToInterpolators(entry); 333 } 334 } 335 336 /** 337 * Reads data from {@code fileName} and adds it to this table. 338 * 339 * @param fileName the name of the file that holds the data. 340 * 341 * @param tableType the particular type of table data in the file. 342 * At the present time the only 343 * supported type is "JPL". See 344 * {@link EphemerisTableReaderFactory#getNewReader(String)} 345 * for more details. 346 * 347 * @throws IllegalArgumentException if {@code tableType} is cannot be 348 * understood. 349 * 350 * @throws FileNotFoundException if no file can be found for {@code fileName}. 351 * 352 * @throws IOException if anything goes wrong while reading the data. 353 * 354 * @see JplEphemerisTableReader 355 */ 356 public void addFrom(String fileName, String tableType) 357 throws FileNotFoundException, IOException 358 { 359 addFrom(new FileInputStream(fileName), tableType); 360 } 361 362 /** 363 * Reads data from {@code in} and adds it to this table. 364 * 365 * @param in a stream that contains ephemeris data. 366 * 367 * @param tableType the particular type of table data in the stream. 368 * At the present time the only 369 * supported type is "JPL". See 370 * {@link EphemerisTableReaderFactory#getNewReader(String)} 371 * for more details. 372 * 373 * @throws IllegalArgumentException if {@code tableType} is cannot be 374 * understood. 375 * 376 * @throws IOException if anything goes wrong while reading the data. 377 * 378 * @see JplEphemerisTableReader 379 */ 380 public void addFrom(InputStream in, String tableType) 381 throws IOException, IllegalArgumentException 382 { 383 EphemerisTableReader reader = 384 EphemerisTableReaderFactory.getSharedInstance().getNewReader(tableType); 385 386 //This method calls-back this table's add method, which will update 387 //the interpolators. Therefore we do not need to worry about that here. 388 reader.read(in, this); 389 } 390 391 /** 392 * Creates a new ephemeris table and tries to load it with data from 393 * the given file. 394 * 395 * @param fileName the name of a file that contains ephemeris data. 396 * 397 * @param tableType the particular type of table data in the stream. 398 * At the present time the only 399 * supported type is "JPL". See 400 * {@link EphemerisTableReaderFactory#getNewReader(String)} 401 * for more details. 402 * 403 * @return a new ephemeris table. 404 * 405 * @throws IllegalArgumentException if {@code tableType} is cannot be 406 * understood. 407 * 408 * @throws FileNotFoundException if no file can be found for {@code fileName}. 409 * 410 * @throws IOException if anything goes wrong while reading the data. 411 * 412 * @see #addFrom(String, String) 413 */ 414 public static EphemerisTable createFrom(String fileName, String tableType) 415 throws FileNotFoundException, IOException 416 { 417 EphemerisTable newTable = new EphemerisTable(); 418 419 newTable.addFrom(fileName, tableType); 420 421 return newTable; 422 } 423 424 /** 425 * Creates a new ephemeris table and tries to load it with data from 426 * the given stream. 427 * 428 * @param in a stream that contains ephemeris data. 429 * 430 * @param tableType the particular type of table data in the stream. 431 * At the present time the only 432 * supported type is "JPL". See 433 * {@link EphemerisTableReaderFactory#getNewReader(String)} 434 * for more details. 435 * 436 * @return a new ephemeris table. 437 * 438 * @throws IllegalArgumentException if {@code tableType} is cannot be 439 * understood. 440 * 441 * @throws IOException if anything goes wrong while reading the data. 442 * 443 * @see #addFrom(InputStream, String) 444 */ 445 public static EphemerisTable createFrom(InputStream in, String tableType) 446 throws IOException 447 { 448 EphemerisTable newTable = new EphemerisTable(); 449 450 newTable.addFrom(in, tableType); 451 452 return newTable; 453 } 454 455 /** 456 * Returns the number of entries in this table. 457 * @return the number of entries in this table. 458 */ 459 public int size() 460 { 461 return entries.size(); 462 } 463 464 /** 465 * Returns <i>true</i> if this table has no entries. 466 * @return <i>true</i> if this table has no entries, <i>false</i> otherwise. 467 */ 468 public boolean isEmpty() 469 { 470 return entries.isEmpty(); 471 } 472 473 //=========================================================================== 474 // INTERPOLATORS 475 //=========================================================================== 476 477 /** 478 * Sets the implementation of {@link Interpolator} that this table will use 479 * for calculating positional values. 480 * 481 * @param interpClass a class that implements {@link Interpolator}. 482 * 483 * @throws IllegalAccessException if this method "does not have access to the 484 * definition of the specified class" (see Sun's documentation on 485 * this exception). 486 * 487 * @throws InstantiationException if "the specified class object cannot be 488 * instantiated because it is an interface or is an abstract class" 489 * (see Sun's documentation on this exception). 490 */ 491 public void setInterpolatorClass(Class<? extends Interpolator> interpClass) 492 throws IllegalAccessException, InstantiationException 493 { 494 interpLat = interpClass.newInstance(); 495 interpLon = interpClass.newInstance(); 496 interpDist = interpClass.newInstance(); 497 interpVel = interpClass.newInstance(); 498 499 repopulateInterpolators(); 500 } 501 502 /** Clears the interpolators and repopulates them from the entry data. */ 503 private void repopulateInterpolators() 504 { 505 interpLat.clear(); 506 interpLon.clear(); 507 interpDist.clear(); 508 interpVel.clear(); 509 510 for (EphemerisTableEntry entry : entries.values()) 511 { 512 addToInterpolators(entry); 513 } 514 } 515 516 /** Adds the data from entry to the interpolators. */ 517 private void addToInterpolators(EphemerisTableEntry entry) 518 { 519 Point2D xy = new Point2D.Double(); 520 521 //The time will be used as the independent variable 522 double x = (double)entry.getTime().getTime(); 523 524 //Dependent variable = latitude 525 double y = entry.getLatitude().toUnits(INTERP_ARC_UNITS).doubleValue(); 526 xy.setLocation(x, y); 527 interpLat.addPoint(xy); 528 529 //Dependent variable = longitude 530 y = entry.getLongitude().toUnits(INTERP_ARC_UNITS).doubleValue(); 531 xy.setLocation(x, y); 532 interpLon.addPoint(xy); 533 534 //Dependent variable = distance 535 y = entry.getDistance().toUnits(INTERP_DIST_UNITS).doubleValue(); 536 xy.setLocation(x, y); 537 interpDist.addPoint(xy); 538 539 //Dependent variable = velocity 540 y = entry.getVelocity().toUnits(INTERP_VELOC_UNITS).doubleValue(); 541 xy.setLocation(x, y); 542 interpVel.addPoint(xy); 543 } 544 545 /** 546 * Longitude values travel around in a circle. As a longitude value 547 * approaches and then passes its maximum value, it reaches its 548 * minimum value and continues increasing from there. (Thing of 549 * starting with a longitude of 355 degrees and progressing to 550 * 5 degrees.) 551 * 552 * It is not appropriate for the interpolators to see this discontinuity. 553 * Instead the interpolators should be presented with a smoothed 554 * representation of the movement in longitude. (In the example above, 555 * the interpolators should see 355 degrees and 365 degrees.) 556 * This method performs this adjustment. 557 */ 558 private void rationalizeLongitudeInterpolator() 559 { 560 //This code relies on the fact that the Longitude class normalizes the 561 //angles sent to it so that they are always positive and that they 562 //are in the range [0..fullCircle), eg [0 degrees..360 degrees), 563 //[0 radians..2*pi radians), etc. 564 // 565 //It also relies on this table having a sufficient number of points 566 //to make interpolation reasonable. That is, if a source is fast-moving, 567 //this code expects that the entries in the table are not widely 568 //separated in time. This assumption allows us to deduce when we 569 //are crossing the 360/0 degree point. 570 571 int positiveCrossings = 0; //Going from large # to small (eg, 355d to 5d) 572 int negativeCrossings = 0; //Going from small # to large (eg, 2d to 358d) 573 574 final double FULL_CIRCLE = INTERP_ARC_UNITS.toFullCircle().doubleValue(); 575 final double BIG_JUMP = 0.8 * FULL_CIRCLE; 576 577 double adjustment = 0.0; 578 579 List<Point2D> points = interpLon.getPoints(); 580 581 double previousLongitude = points.get(0).getY(); 582 583 for (Point2D point : points) 584 { 585 double longitude = point.getY(); 586 587 //Probable discontinuity 588 if (Math.abs(longitude - previousLongitude) > BIG_JUMP) 589 { 590 if (previousLongitude > longitude) 591 positiveCrossings++; 592 else 593 negativeCrossings++; 594 595 adjustment = 596 FULL_CIRCLE * (double)(positiveCrossings - negativeCrossings); 597 } 598 599 //If an adjustment is needed, replace the interpolator's current point 600 //with one that contains the adjustment. 601 if (adjustment != 0.0) 602 { 603 point.setLocation(point.getX(), longitude + adjustment); 604 interpLon.addPoint(point); 605 } 606 607 previousLongitude = longitude; //unadjusted 608 } 609 610 needToAdjustLongitudeInterpolator = false; 611 } 612 613 //=========================================================================== 614 // TEXT 615 //=========================================================================== 616 617 /** 618 * Returns a text representation of this table. 619 * <p> 620 * The form of the returned string is:<pre> 621 * time<sub>1</sub>;rightAscension<sub>1</sub>;declination<sub>1</sub>;distance<sub>1</sub>;velocity<sub>1</sub>; 622 * time<sub>2</sub>;rightAscension<sub>2</sub>;declination<sub>2</sub>;distance<sub>2</sub>;velocity<sub>2</sub>; 623 * ... 624 * time<sub>N</sub>;rightAscension<sub>N</sub>;declination<sub>N</sub>;distance<sub>N</sub>;velocity<sub>N</sub>;</pre></p> 625 */ 626 public String toString() 627 { 628 StringBuilder buff = new StringBuilder(); 629 630 for (EphemerisTableEntry entry : entries.values()) 631 buff = entry.appendTo(buff).append(StringUtil.EOL); 632 633 return buff.toString(); 634 } 635 636 /** 637 * Returns an XML representation of this ephemeris table. 638 * @return an XML representation of this ephemeris table. 639 * @throws JAXBException if anything goes wrong during the conversion to XML. 640 */ 641 public String toXml() throws JAXBException 642 { 643 return JaxbUtility.getSharedInstance().objectToXmlString(this); 644 } 645 646 /** 647 * Writes an XML representation of this ephemeris table to {@code writer}. 648 * @param writer the device to which XML is written. 649 * @throws JAXBException if anything goes wrong during the conversion to XML. 650 */ 651 public void writeAsXmlTo(Writer writer) throws JAXBException 652 { 653 JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null); 654 } 655 656 /** 657 * Creates a new ephemeris table from the XML data in the given file. 658 * 659 * @param xmlFile the name of an XML file. This method will attempt to locate 660 * the file by using {@link Class#getResource(String)}. 661 * 662 * @return a new ephemeris table from the XML data in the given file. 663 * 664 * @throws FileNotFoundException if the XML file cannot be found. 665 * 666 * @throws JAXBException if the schema file used (if any) is malformed, if 667 * the XML file cannot be read, or if the XML file is not 668 * schema-valid. 669 * 670 * @throws XMLStreamException if there is a problem opening the XML file, 671 * if the XML is not well-formed, or for some other 672 * "unexpected processing conditions". 673 */ 674 public static EphemerisTable fromXml(String xmlFile) 675 throws JAXBException, XMLStreamException, FileNotFoundException 676 { 677 return JaxbUtility.getSharedInstance() 678 .xmlFileToObject(xmlFile, EphemerisTable.class); 679 } 680 681 /** 682 * Creates a new ephemeris table based on the XML data read from 683 * {@code reader}. 684 * 685 * @param reader the source of the XML data. 686 * If this value is <i>null</i>, <i>null</i> is returned. 687 * 688 * @return a new ephemeris table based on the XML data read from 689 * {@code reader}. 690 * 691 * @throws XMLStreamException if the XML is not well-formed, 692 * or for some other "unexpected processing conditions". 693 * 694 * @throws JAXBException if anything else goes wrong during the 695 * transformation. 696 */ 697 public static EphemerisTable fromXml(Reader reader) 698 throws JAXBException, XMLStreamException 699 { 700 return JaxbUtility.getSharedInstance() 701 .readObjectAsXmlFrom(reader, EphemerisTable.class, null); 702 } 703 704 //Used only for JAXB. Helps deal w/ the underlying sorted map. 705 @XmlElement(name="entry") 706 @SuppressWarnings("unused") 707 private EphemerisTableEntry[] getEntries() 708 { 709 EphemerisTableEntry[] result = new EphemerisTableEntry[size()]; 710 711 int e=0; 712 for (Date key : entries.keySet()) 713 result[e++] = entries.get(key); 714 715 return result; 716 } 717 718 //Used only for JAXB. Helps deal w/ the underlying sorted map. 719 @SuppressWarnings("unused") 720 private void setEntries(EphemerisTableEntry[] replacements) 721 { 722 resetEntries(); 723 for (EphemerisTableEntry e : replacements) 724 add(e); 725 } 726 727 //=========================================================================== 728 // 729 //=========================================================================== 730 731 /** 732 * Returns a copy of this table. 733 * <p> 734 * If anything goes wrong during the cloning procedure, 735 * a {@code RuntimeException} will be thrown.</p> 736 */ 737 public EphemerisTable clone() 738 { 739 EphemerisTable clone = null; 740 741 try 742 { 743 //This line takes care of the primitive fields properly 744 clone = (EphemerisTable)super.clone(); 745 746 //Clone the map and each of its entries 747 clone.entries = new TreeMap<Date, EphemerisTableEntry>(); 748 749 for (Date key : entries.keySet()) 750 clone.add(this.entries.get(key).clone()); 751 752 //Give the clone the same kind of interpolator 753 clone.setInterpolatorClass(interpLon.getClass()); 754 } 755 catch (Exception ex) 756 { 757 throw new RuntimeException(ex); 758 } 759 760 return clone; 761 } 762 763 /** Returns <i>true</i> if {@code o} is equal to this table. */ 764 public boolean equals(Object o) 765 { 766 //Quick exit if o is this 767 if (o == this) 768 return true; 769 770 //Quick exit if super class determines not equal 771 if (!super.equals(o)) 772 return false; 773 774 //Super class determined o is non-null & of same class 775 EphemerisTable other = (EphemerisTable)o; 776 777 return other.entries.equals(this.entries); 778 } 779 780 /** Returns a hash code value for this table. */ 781 public int hashCode() 782 { 783 //Taken from the Effective Java book by Joshua Bloch. 784 //The constants 17 & 37 are arbitrary & carry no meaning. 785 786 //You MUST keep this method in synch with equals() 787 788 int result = super.hashCode(); 789 790 result = 37 * result + entries.hashCode(); 791 792 return result; 793 } 794 795 //============================================================================ 796 // 797 //============================================================================ 798 /* 799 public static void main(String[] args) 800 { 801 EphemerisTable table = new EphemerisTableBuilder().makeTable(); 802 803 try 804 { 805 System.out.println(table.toXml()); 806 } 807 catch (JAXBException ex) 808 { 809 System.out.println("Trouble w/ table.toXml. Msg:"); 810 System.out.println(ex.getMessage()); 811 ex.printStackTrace(); 812 } 813 814 System.out.println(table.toString()); 815 } 816 */ 817 }