001 package edu.nrao.sss.model.resource; 002 003 import java.math.BigDecimal; 004 import java.math.RoundingMode; 005 import java.util.SortedMap; 006 import java.util.SortedSet; 007 import java.util.TreeMap; 008 import java.util.TreeSet; 009 010 import javax.xml.bind.annotation.XmlList; 011 012 import edu.nrao.sss.astronomy.SpectralLine; 013 import edu.nrao.sss.astronomy.StokesParameter; 014 import edu.nrao.sss.astronomy.VelocityConvention; 015 import edu.nrao.sss.measure.Frequency; 016 import edu.nrao.sss.measure.FrequencyRange; 017 import edu.nrao.sss.measure.LinearVelocity; 018 import edu.nrao.sss.measure.LinearVelocityUnits; 019 import edu.nrao.sss.measure.TimeDuration; 020 import edu.nrao.sss.measure.TimeUnits; 021 import edu.nrao.sss.util.Identifiable; 022 import edu.nrao.sss.util.StringUtil; 023 024 /** 025 * A specification for observing a spectral transition in a distance object. 026 * Instances of this class are used to help choose and configure instrumentation 027 * that can obtain data that conform to the encapsulated specifications. 028 * <p> 029 * <b>Version Info:</b> 030 * <table style="margin-left:2em"> 031 * <tr><td>$Revision: 1709 $</td></tr> 032 * <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr> 033 * <tr><td>$Author: dharland $</td></tr> 034 * </table></p> 035 * 036 * @author David M. Harland 037 * @since 2007-01-29 038 */ 039 public class SpectralLineSpecification 040 implements Cloneable 041 { 042 private static final BigDecimal TWO = new BigDecimal("2.0"); 043 044 //This ID field is here for the persistance layer 045 @SuppressWarnings("unused") 046 private Long id; 047 048 private SpectralLine line; 049 private LinearVelocity sourceVelocity; 050 private LinearVelocity velocityBandwidth; 051 private LinearVelocity velocityResolution; 052 private TimeDuration timeResolution; 053 054 private SortedSet<StokesParameter> polarizations; 055 private SortedMap<String, String> additionalSpecifications; 056 057 /** Creates a new instance. */ 058 public SpectralLineSpecification() 059 { 060 id = Identifiable.UNIDENTIFIED; 061 062 line = new SpectralLine(); 063 sourceVelocity = new LinearVelocity(); 064 velocityBandwidth = new LinearVelocity(); 065 velocityResolution = new LinearVelocity(); 066 timeResolution = new TimeDuration("0.0", TimeUnits.MILLISECOND); 067 068 polarizations = new TreeSet<StokesParameter>(); 069 additionalSpecifications = new TreeMap<String, String>(); 070 } 071 072 /** 073 * Resets the identifier of this specification 074 * to a value that represents the unidentified state. 075 * <p> 076 * This method is useful for preparing a specification for storage in a 077 * database. 078 * The ID property (as of now, though this may change in the future) is 079 * used by our persistence mechanism to identify objects. If you are 080 * persisting this object for the first time, you may need to call 081 * this method before performing a save. This is especially true if 082 * you have created this object from XML, as the XML unmarshalling 083 * brings along the ID property.</p> 084 */ 085 void clearId() 086 { 087 this.id = Identifiable.UNIDENTIFIED; 088 } 089 090 //============================================================================ 091 // DERIVED VALUES 092 //============================================================================ 093 094 /** 095 * Returns a sky frequency specification that corresponds to this spectral 096 * line specification. 097 * 098 * @return a new sky frequency specification derived from this specification. 099 */ 100 public SkyFrequencySpecification toSkyFrequencySpecification() 101 { 102 SkyFrequencySpecification skyFreqSpec = new SkyFrequencySpecification(); 103 104 skyFreqSpec.setTimeResolution(timeResolution.clone()); 105 106 skyFreqSpec.setPolarizationProducts(new TreeSet<StokesParameter>(polarizations)); 107 108 skyFreqSpec.setAdditionalSpecifications( 109 new TreeMap<String, String>(additionalSpecifications)); 110 111 updateFreqRangeAndResolution(skyFreqSpec); 112 113 return skyFreqSpec; 114 } 115 116 /** Calculates the sky frequency range from the other properties. */ 117 private 118 void updateFreqRangeAndResolution(SkyFrequencySpecification skyFreqSpec) 119 { 120 //Use the central velocity to decide on using either the radio 121 //or the redshift convention. 122 VelocityConvention convention = 123 sourceVelocity.getUnits().equals(LinearVelocityUnits.Z) ? 124 VelocityConvention.REDSHIFT : VelocityConvention.RADIO; 125 126 skyFreqSpec.setFrequencyRange(calcFreqRange(velocityBandwidth, convention)); 127 128 skyFreqSpec.setFrequencyResolution(calcFreqRange(velocityResolution, 129 convention).getWidth()); 130 } 131 132 /** 133 * Creates a frequency range from the parameters, the source 134 * velocity, and the rest frequency. 135 */ 136 private FrequencyRange calcFreqRange(LinearVelocity bandwidth, 137 VelocityConvention convention) 138 { 139 LinearVelocityUnits velocityUnits = convention.getDefaultUnits(); 140 141 BigDecimal halfBandwidth = bandwidth.toUnits(velocityUnits).divide(TWO, RoundingMode.HALF_UP); 142 BigDecimal centralVelocity = sourceVelocity.toUnits(velocityUnits); 143 144 BigDecimal low = centralVelocity.subtract(halfBandwidth); 145 BigDecimal high = centralVelocity.add(halfBandwidth); 146 147 BigDecimal shiftFactor1 = convention.getFrequencyShiftFactor(low); 148 BigDecimal shiftFactor2 = convention.getFrequencyShiftFactor(high); 149 150 if (shiftFactor1.signum() < 0) 151 shiftFactor1 = BigDecimal.ZERO; 152 153 if (shiftFactor2.signum() < 0) 154 shiftFactor2 = BigDecimal.ZERO; 155 156 Frequency restFrequency = line.getFrequency(); 157 158 Frequency frequency1 = restFrequency.clone().multiplyBy(shiftFactor1); 159 Frequency frequency2 = restFrequency.clone().multiplyBy(shiftFactor2); 160 161 return new FrequencyRange(frequency1, frequency2); 162 } 163 164 //============================================================================ 165 // INDIRECT MANIPULATIONS 166 //============================================================================ 167 168 /** 169 * Alters the velocity values of this specification so that its sky 170 * frequencies equal the endpoints of {@code skyFreqRange}. 171 * Only the velocity range is changed; the rest frequency and resolutions 172 * of this specification are unaltered. If this specification's rest 173 * frequency is zero, this method does nothing. 174 * 175 * @param skyFreqRange the range of sky frequencies to which the rest 176 * frequency of this specification is constrained. 177 * 178 * @return this specification. 179 */ 180 //TODO: Test this method 181 public SpectralLineSpecification 182 adjustVelocityForSkyFrequencyOf(FrequencyRange skyFreqRange) 183 { 184 Frequency restFrequency = line.getFrequency(); 185 186 //Quick exit if no rest frequency 187 if (restFrequency.getValue().compareTo(BigDecimal.ZERO) == 0) 188 return this; 189 190 VelocityConvention vc = 191 (sourceVelocity.getUnits() == LinearVelocityUnits.Z) ? 192 VelocityConvention.REDSHIFT : VelocityConvention.RADIO; 193 194 //Convert the frequency shifts to velocities 195 LinearVelocity v1 = 196 vc.getVelocity(skyFreqRange.getLowFrequency().dividedBy(restFrequency)); 197 LinearVelocity v2 = 198 vc.getVelocity(skyFreqRange.getHighFrequency().dividedBy(restFrequency)); 199 200 //Worry about both red and blue shifts 201 LinearVelocity vHigh, vLow; 202 203 if (v1.compareTo(v2) >= 0) { vHigh = v1; vLow = v2; } 204 else { vHigh = v2; vLow = v1; } 205 206 //Set the new velocity values 207 velocityBandwidth = vHigh.subtract(vLow); 208 sourceVelocity = vLow.add(velocityBandwidth.clone().divideBy(TWO)); 209 210 return this; 211 } 212 213 //============================================================================ 214 // SIMPLE PROPERTIES 215 //============================================================================ 216 217 /** 218 * Sets the spectral line of this specification. 219 * <p> 220 * This specification will hold a reference to {@code newLine} (unless it is 221 * <i>null</i>), so any changes made to it after this call will be reflected 222 * in this specification.</p> 223 * 224 * @param newLine the spectral line of this specification. 225 * If this value is <i>null</i>, 226 * it will be replaced with {@code new SpectralLine()}. 227 */ 228 public void setLine(SpectralLine newLine) 229 { 230 line = (newLine == null) ? new SpectralLine() : newLine; 231 } 232 233 /** 234 * Returns the spectral line of this specification. 235 * <p> 236 * The returned line is the one held by this specification, 237 * so any changes made to it will be reflected in this object. 238 * This value is guaranteed to be non-null.</p> 239 * 240 * @return the spectral line of this specification. 241 */ 242 public SpectralLine getLine() 243 { 244 return line; 245 } 246 247 /** 248 * Sets the velocity of the source of this spectral line away from 249 * or toward the observer. 250 * <p> 251 * Unless {@code newVelocity} is <i>null</i>, 252 * this object will hold a reference to the parameter, 253 * so any changes made to it will be reflected in this object. 254 * A value of <i>null</i> will be interpreted as a velocity 255 * of zero kilometers per second.</p> 256 * 257 * @param newVelocity the velocity of the source of this spectral line away from 258 * or toward the observer. 259 */ 260 public void setSourceVelocity(LinearVelocity newVelocity) 261 { 262 sourceVelocity = (newVelocity == null) ? new LinearVelocity() : newVelocity; 263 } 264 265 /** 266 * Returns the velocity of the source of this spectral line. 267 * The velocity returned is the component that is either away from or 268 * toward the observer. 269 * <p> 270 * Note that the returned velocity is the one held by this specification, 271 * so any changes made to it will be reflected in this object. 272 * This value is guaranteed to be non-null.</p> 273 * 274 * @return the velocity of the source of this spectral line. 275 */ 276 public LinearVelocity getSourceVelocity() 277 { 278 return sourceVelocity; 279 } 280 281 /** 282 * Helps set the minimum and maximum velocities for this observation. 283 * The minimum (maximum) velocity is the 284 * {@link #getSourceVelocity() source velocity} 285 * minus (plus) one-half of {@code newBandwidth}. 286 * If {@code newBandwidth} is <i>null</i>, this method does nothing. 287 * <p> 288 * Unless {@code newBandwidth} is <i>null</i>, 289 * this object will hold a reference to the parameter, 290 * so any changes made to it will be reflected in this object. 291 * A value of <i>null</i> will be interpreted as a bandwidth 292 * of zero kilometers per second.</p> 293 * 294 * @param newBandwidth the width of the velocity range that is centered on 295 * {@link #getSourceVelocity()}. 296 */ 297 public void setVelocityBandwidth(LinearVelocity newBandwidth) 298 { 299 velocityBandwidth = 300 (newBandwidth == null) ? new LinearVelocity() : newBandwidth; 301 } 302 303 /** 304 * Returns the width of the velocity range that is centered on 305 * {@link #getSourceVelocity()}. 306 * <p> 307 * Note that the returned width is the one held by this specification, 308 * so any changes made to it will be reflected in this object. 309 * This value is guaranteed to be non-null.</p> 310 * 311 * @return the width of the velocity range that is centered on 312 * {@link #getSourceVelocity()}. 313 * 314 * @see #setVelocityBandwidth(LinearVelocity) 315 */ 316 public LinearVelocity getVelocityBandwidth() 317 { 318 return velocityBandwidth; 319 } 320 321 /** 322 * Sets velocity resolution of this specification. 323 * <p> 324 * Unless {@code newResolution} is <i>null</i>, 325 * this object will hold a reference to the parameter, 326 * so any changes made to it will be reflected in this object. 327 * A value of <i>null</i> will be interpreted as a resolution 328 * equal to that of this specification's current bandwidth.</p> 329 * 330 * @param newResolution velocity resolution of this specification. 331 */ 332 public void setVelocityResolution(LinearVelocity newResolution) 333 { 334 velocityResolution = newResolution == null ? getVelocityBandwidth().clone() 335 : newResolution; 336 } 337 338 /** 339 * Returns the velocity resolution of this specification. 340 * <p> 341 * Note that the returned resolution is the one held by this specification, 342 * so any changes made to it will be reflected in this object. 343 * This value is guaranteed to be non-null.</p> 344 * 345 * @return the velocity resolution of this specification. 346 */ 347 public LinearVelocity getVelocityResolution() 348 { 349 return velocityResolution; 350 } 351 352 /** 353 * Sets the time resolution of this specification. 354 * <p> 355 * Unless {@code resolution} is <i>null</i>, 356 * this object will hold a reference to the parameter, 357 * so any changes made to it will be reflected in this object. 358 * A value of <i>null</i> will be interpreted as a duration 359 * of zero milliseconds.</p> 360 * 361 * @param resolution the time resolution of this specification. 362 */ 363 public void setTimeResolution(TimeDuration resolution) 364 { 365 timeResolution = 366 (resolution == null) ? new TimeDuration("0.0", TimeUnits.MILLISECOND) 367 : resolution; 368 } 369 370 /** 371 * Returns the time resolution of this specification. 372 * <p> 373 * Note that the returned duration is the one held by this specification, 374 * so any changes made to it will be reflected in this object. 375 * This value is guaranteed to be non-null.</p> 376 * 377 * @return the time resolution of this specification. 378 */ 379 public TimeDuration getTimeResolution() 380 { 381 return timeResolution; 382 } 383 384 /** 385 * Sets the polarization products requested by this specification. 386 * <p> 387 * Unless {@code newSet} is <i>null</i>, 388 * this object will hold a reference to the parameter, 389 * so any changes made to it will be reflected in this object. 390 * A value of <i>null</i> will be interpreted as a new empty set.</p> 391 * 392 * @param newSet the polarizations requested by this specification. 393 * If this value is <i>null</i>, it will be interpreted 394 * as an empty set. 395 */ 396 public void setPolarizationProducts(SortedSet<StokesParameter> newSet) 397 { 398 polarizations = (newSet == null) ? new TreeSet<StokesParameter>() 399 : newSet; 400 } 401 402 /** 403 * Returns the polarization products requested by this specification. 404 * <p> 405 * Note that the returned set is the one held by this specification, 406 * so any changes made to it will be reflected in this object. 407 * This set is guaranteed to be non-null, but it may be empty.</p> 408 * 409 * @return the polarization products requested by this specification. 410 */ 411 @XmlList 412 public SortedSet<StokesParameter> getPolarizationProducts() 413 { 414 return polarizations; 415 } 416 417 //============================================================================ 418 // 419 //============================================================================ 420 421 /** 422 * Returns a text representation of this specification. 423 * @return a text representation of this specification. 424 */ 425 public String toString() 426 { 427 StringBuilder buff = new StringBuilder(); 428 429 final String EOL = StringUtil.EOL; 430 431 buff.append("Spectral Line:").append(EOL).append('{').append(EOL); 432 buff.append(line).append('}').append(EOL); 433 buff.append("Source Velocity: ").append(sourceVelocity).append(EOL); 434 buff.append("Velocity Bandwidth: ").append(velocityBandwidth).append(EOL); 435 buff.append("Velocity Resolution: ").append(velocityResolution).append(EOL); 436 buff.append("Time Resolution: ").append(timeResolution).append(EOL); 437 438 return buff.toString(); 439 } 440 441 /** 442 * Returns a specification that is a copy of this one. 443 * <p> 444 * If anything goes wrong during the cloning procedure, 445 * a {@code RuntimeException} will be thrown.</p> 446 */ 447 @Override 448 public SpectralLineSpecification clone() 449 { 450 SpectralLineSpecification clone = null; 451 452 try 453 { 454 clone = (SpectralLineSpecification)super.clone(); 455 456 //We do NOT want the clone to have the same ID as the original. 457 //The ID is here for the persistence layer; it is in charge of 458 //setting IDs. To help it, we put the clone's ID in the uninitialized 459 //state. 460 clone.id = Identifiable.UNIDENTIFIED; 461 462 clone.line = this.line.clone(); 463 clone.sourceVelocity = this.sourceVelocity.clone(); 464 clone.velocityBandwidth = this.velocityBandwidth.clone(); 465 clone.velocityResolution = this.velocityResolution.clone(); 466 clone.timeResolution = this.timeResolution.clone(); 467 468 //These are collections of immutables, so we need clone only the collection 469 //variables, not the contents therein. 470 clone.polarizations = new TreeSet<StokesParameter>(this.polarizations); 471 472 clone.additionalSpecifications = 473 new TreeMap<String, String>(this.additionalSpecifications); 474 } 475 catch (Exception ex) 476 { 477 throw new RuntimeException(ex); 478 } 479 480 return clone; 481 } 482 483 /** Returns <i>true</i> if {@code o} is equal to this specification. */ 484 @Override 485 public boolean equals(Object o) 486 { 487 //Quick exit if o is this 488 if (o == this) 489 return true; 490 491 //Quick exit if o is null 492 if (o == null) 493 return false; 494 495 //Quick exit if classes are different 496 if (!o.getClass().equals(this.getClass())) 497 return false; 498 499 SpectralLineSpecification other = (SpectralLineSpecification)o; 500 501 //NOTE: absence of ID field is intentional 502 503 return other.line.equals(this.line) && 504 other.sourceVelocity.equals(this.sourceVelocity) && 505 other.velocityBandwidth.equals(this.velocityBandwidth) && 506 other.velocityResolution.equals(this.velocityResolution) && 507 other.timeResolution.equals(this.timeResolution) && 508 other.polarizations.equals(this.polarizations) && 509 other.additionalSpecifications.equals(this.additionalSpecifications); 510 511 //Note the comparison of the polarizations Set is OK because 512 //the elements are immutable. 513 } 514 515 /** Returns a hash code value for this specification. */ 516 @Override 517 public int hashCode() 518 { 519 //Taken from the Effective Java book by Joshua Bloch. 520 //The constants 17 & 37 are arbitrary & carry no meaning. 521 int result = 17; 522 523 //This method MUST be kept in synch w/ the equals method. 524 525 result = 37 * result + line.hashCode(); 526 result = 37 * result + sourceVelocity.hashCode(); 527 result = 37 * result + velocityBandwidth.hashCode(); 528 result = 37 * result + velocityResolution.hashCode(); 529 result = 37 * result + timeResolution.hashCode(); 530 result = 37 * result + polarizations.hashCode(); 531 result = 37 * result + additionalSpecifications.hashCode(); 532 533 return result; 534 } 535 536 //============================================================================ 537 // 538 //============================================================================ 539 /* 540 public static void main(String[] args) throws Exception 541 { 542 ResourceBuilder builder = new ResourceBuilder(); 543 SpectralLineSpecification spec = builder.makeSpectralLineSpecification(); 544 545 System.out.println(spec); 546 } 547 */ 548 }