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 ≠ 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 }