001 package edu.nrao.sss.model.project; 002 003 import java.math.BigDecimal; 004 import java.net.URL; 005 import java.util.ArrayList; 006 import java.util.HashMap; 007 import java.util.List; 008 import java.util.Map; 009 010 import javax.xml.bind.annotation.XmlAccessType; 011 import javax.xml.bind.annotation.XmlAccessorType; 012 import javax.xml.bind.annotation.XmlElement; 013 import javax.xml.parsers.SAXParser; 014 import javax.xml.parsers.SAXParserFactory; 015 016 import org.apache.log4j.Logger; 017 import org.xml.sax.Attributes; 018 import org.xml.sax.SAXException; 019 import org.xml.sax.helpers.DefaultHandler; 020 021 import edu.nrao.sss.math.MathUtil; 022 import edu.nrao.sss.measure.Angle; 023 import edu.nrao.sss.measure.LinearVelocity; 024 import edu.nrao.sss.model.resource.ReceiverBand; 025 import edu.nrao.sss.model.resource.TelescopeConfiguration; 026 027 import static edu.nrao.sss.measure.ArcUnits.DEGREE; 028 import static edu.nrao.sss.measure.LinearVelocityUnits.METERS_PER_SECOND; 029 030 /** 031 * Scheduling constraints related to environmental conditions. 032 * An instance of this object is held by a {@link SchedulingBlock}. 033 * <p> 034 * <b>Version Info:</b> 035 * <table style="margin-left:2em"> 036 * <tr><td>$Revision: 1915 $</td></tr> 037 * <tr><td>$Date: 2009-01-21 16:15:07 -0700 (Wed, 21 Jan 2009) $</td></tr> 038 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 039 * </table></p> 040 * 041 * @author David M. Harland 042 * @since 2008-07-15 043 */ 044 @XmlAccessorType(XmlAccessType.NONE) 045 public class EnvironmentalConstraints 046 implements Cloneable 047 { 048 private static final Logger log = Logger.getLogger(EnvironmentalConstraints.class); 049 050 //Constants to indicate that a given constraint is not in use 051 private static final BigDecimal NO_MAX_PHASE = new BigDecimal("180.0"); 052 private static final BigDecimal NO_MAX_WIND = MathUtil.POSITIVE_INFINITY; 053 private static final BigDecimal NO_MIN_SOLAR_SEP = new BigDecimal("-1"); 054 private static final BigDecimal NO_MAX_OPACITY = MathUtil.POSITIVE_INFINITY; 055 056 private static final BigDecimal ANTENNA_MIN_ELEV = new BigDecimal("8.0"); 057 058 private LinearVelocity windSpeedMax; 059 private Angle tropospherePhaseMax; 060 private Angle ionospherePhaseMax; 061 private Angle solarSepMin; 062 private Angle elevationMin; 063 private BigDecimal opacityMax; 064 private boolean avoidSunrise; 065 private boolean avoidSunset; 066 067 /** 068 * Creates a new container of environmental scheduling constraints. 069 */ 070 public EnvironmentalConstraints() 071 { 072 windSpeedMax = new LinearVelocity(); 073 tropospherePhaseMax = new Angle(); 074 ionospherePhaseMax = new Angle(); 075 solarSepMin = new Angle(); 076 elevationMin = new Angle(); 077 078 privateReset(); 079 } 080 081 private void privateReset() 082 { 083 resetMaxAllowableWindSpeed(); 084 resetMaxAllowableApi(); 085 resetMaxAllowableIonospherePhase(); 086 resetMinAllowableSolarSeparation(); 087 resetMinAllowableElevation(); 088 resetMaxAllowableOpacity(); 089 resetAvoidSunrise(); 090 resetAvoidSunset(); 091 } 092 093 /** 094 * Puts the constraints held by this object back to their default values. 095 * The default values are those that typically would not prevent scheduling. 096 */ 097 public void reset() 098 { 099 privateReset(); 100 } 101 102 //============================================================================ 103 // SIMPLE GET/SET METHODS 104 //============================================================================ 105 //---------------------------------------------------------------------------- 106 // Wind Speed 107 //---------------------------------------------------------------------------- 108 109 /** 110 * Returns the maximum allowable wind speed. 111 * If the actual wind speed is above this value, the scheduling block 112 * holding this object should not be scheduled for execution. 113 * <p> 114 * The velocity returned is the one held internally by this object, 115 * so changes made to it will be reflected herein.</p> 116 * 117 * @return the maximum allowable wind speed. 118 * 119 * @see #hasWindSpeedConstraint() 120 * @see #resetMaxAllowableWindSpeed() 121 */ 122 public LinearVelocity getMaxAllowableWindSpeed() 123 { 124 return windSpeedMax; 125 } 126 127 /** 128 * Puts the <tt>maxAllowableWindSpeed</tt> constraint back to its default 129 * value. 130 * The default value is one that typically would not prevent scheduling. 131 * 132 * @see #getMaxAllowableWindSpeed() 133 * @see #hasWindSpeedConstraint() 134 */ 135 public final void resetMaxAllowableWindSpeed() 136 { 137 windSpeedMax.set(NO_MAX_WIND, METERS_PER_SECOND); 138 } 139 140 /** 141 * Returns <i>false</i> if the wind speed constraint is so relaxed as to 142 * practically not exist. This method is intended mainly for handling 143 * situations at the user interface level where one first wants to say 144 * I do or do not care about the wind speed, 145 * and only if I care will I enter a value. 146 * Most other clients can ignore this method and use 147 * {@link #getMaxAllowableWindSpeed()}; if wind speed is not 148 * a concern, that method will return a value sufficiently high that it 149 * will not constrain scheduling. 150 * 151 * @return 152 * <i>true</i> if this set of constraints has a wind speed constraint. 153 * 154 * @see #getMaxAllowableWindSpeed() 155 * @see #resetMaxAllowableWindSpeed() 156 */ 157 public boolean hasWindSpeedConstraint() 158 { 159 return !windSpeedMax.isInfinite(); 160 } 161 162 //---------------------------------------------------------------------------- 163 // Atmospheric Phase Index 164 //---------------------------------------------------------------------------- 165 166 /** 167 * Returns the maximum allowable atmospheric (tropospheric) phase. 168 * If the actual API is above this value, the scheduling block 169 * holding this object should not be scheduled for execution. 170 * <p> 171 * The angle returned is the one held internally by this object, 172 * so changes made to it will be reflected herein.</p> 173 * 174 * @return the maximum allowable atmospheric phase. 175 * 176 * @see #hasApiConstraint() 177 * @see #resetMaxAllowableApi() 178 */ 179 public Angle getMaxAllowableApi() 180 { 181 return tropospherePhaseMax; 182 } 183 184 /** 185 * Puts the <tt>maxAllowableApi</tt> constraint back to its default value. 186 * The default value is one that typically would not prevent scheduling 187 * and results in {@link #hasApiConstraint()} returning <i>false</i>. 188 * 189 * @see #getMaxAllowableApi() 190 * @see #hasApiConstraint() 191 */ 192 public final void resetMaxAllowableApi() 193 { 194 tropospherePhaseMax.set(NO_MAX_PHASE, DEGREE); 195 } 196 197 /** 198 * Returns <i>false</i> if the API constraint is so relaxed as to practically 199 * not exist. This method is intended mainly for handling situations at the 200 * user interface level where one first wants to say I do or do not care 201 * about the API, and only if I care will I enter a value. 202 * Most other clients can ignore this method and use 203 * {@link #getMaxAllowableApi()}; if API is not 204 * a concern, that method will return a value sufficiently high that it 205 * will not constrain scheduling. 206 * 207 * @return 208 * <i>true</i> if this set of constraints has an API constraint. 209 * 210 * @see #getMaxAllowableApi() 211 * @see #resetMaxAllowableApi() 212 */ 213 public boolean hasApiConstraint() 214 { 215 return tropospherePhaseMax.convertToPositiveNormal().toUnits(DEGREE) 216 .compareTo(NO_MAX_PHASE) < 0; 217 } 218 219 //---------------------------------------------------------------------------- 220 // Ionospheric Phase 221 //---------------------------------------------------------------------------- 222 223 /** 224 * Returns the maximum allowable ionospheric phase. 225 * If the actual phase is above this value, the scheduling block 226 * holding this object should not be scheduled for execution. 227 * <p> 228 * The angle returned is the one held internally by this object, 229 * so changes made to it will be reflected herein.</p> 230 * 231 * @return the maximum allowable ionospheric phase. 232 * 233 * @see #hasIonosphereConstraint() 234 * @see #resetMaxAllowableIonospherePhase() 235 */ 236 public Angle getMaxAllowableIonospherePhase() 237 { 238 return ionospherePhaseMax; 239 } 240 241 /** 242 * Puts the <tt>maxAllowableIonospherePhase</tt> constraint back to its 243 * default value. 244 * The default value is one that typically would not prevent scheduling 245 * and results in {@link #hasIonosphereConstraint()} returning <i>false</i>. 246 * 247 * @see #getMaxAllowableIonospherePhase() 248 * @see #hasIonosphereConstraint() 249 */ 250 public final void resetMaxAllowableIonospherePhase() 251 { 252 ionospherePhaseMax.set(NO_MAX_PHASE, DEGREE); 253 } 254 255 /** 256 * Returns <i>false</i> if the ionospheric phase constraint is so relaxed 257 * as to practically not exist. This method is intended mainly for handling 258 * situations at the user interface level where one first wants to say I do 259 * or do not care about the ionospheric phase, 260 * and only if I care will I enter a value. 261 * Most other clients can ignore this method and use 262 * {@link #getMaxAllowableIonospherePhase()}; if ionospheric phase is not 263 * a concern, that method will return a value sufficiently high that it 264 * will not constrain scheduling. 265 * 266 * @return 267 * <i>true</i> if this set of constraints has an ionosphere phase constraint. 268 * 269 * @see #getMaxAllowableIonospherePhase() 270 * @see #resetMaxAllowableIonospherePhase() 271 */ 272 public boolean hasIonosphereConstraint() 273 { 274 return ionospherePhaseMax.convertToPositiveNormal().toUnits(DEGREE) 275 .compareTo(NO_MAX_PHASE) < 0; 276 } 277 278 //---------------------------------------------------------------------------- 279 // Solar Separation 280 //---------------------------------------------------------------------------- 281 282 /** 283 * Returns the minimum allowable separation between the observed source 284 * and the sun. 285 * If the actual angle is below this value, the scheduling block 286 * holding this object should not be scheduled for execution. 287 * <p> 288 * The angle returned is the one held internally by this object, 289 * so changes made to it will be reflected herein.</p> 290 * 291 * @return 292 * the minimum allowable separation between the observed source and the sun. 293 * 294 * @see #hasSolarSeparationConstraint() 295 * @see #resetMinAllowableSolarSeparation() 296 */ 297 public Angle getMinAllowableSolarSeparation() 298 { 299 return solarSepMin; 300 } 301 302 /** 303 * Puts the <tt>minAllowableSolarSeparation</tt> constraint back to its 304 * default value. 305 * The default value is one that typically would not prevent scheduling. 306 * 307 * @see #getMinAllowableSolarSeparation() 308 * @see #hasSolarSeparationConstraint() 309 */ 310 public final void resetMinAllowableSolarSeparation() 311 { 312 solarSepMin.set(NO_MIN_SOLAR_SEP, DEGREE); 313 } 314 315 /** 316 * Returns <i>false</i> if the solar separation constraint is so relaxed as to 317 * practically not exist. This method is intended mainly for handling 318 * situations at the user interface level where one first wants to say 319 * I do or do not care about the solar separation angle, 320 * and only if I care will I enter a value. 321 * Most other clients can ignore this method and use 322 * {@link #getMinAllowableSolarSeparation()}; if solar separation is not 323 * a concern, that method will return a value sufficiently low that it 324 * will not constrain scheduling. 325 * 326 * @return 327 * <i>true</i> if this set of constraints has a solar separation constraint. 328 * 329 * @see #getMinAllowableSolarSeparation() 330 * @see #resetMinAllowableSolarSeparation() 331 */ 332 public boolean hasSolarSeparationConstraint() 333 { 334 return solarSepMin.toUnits(DEGREE).compareTo(NO_MIN_SOLAR_SEP) > 0; 335 } 336 337 //---------------------------------------------------------------------------- 338 // Source Elevation 339 //---------------------------------------------------------------------------- 340 341 /** 342 * Returns the minimum allowable elevation for an observed source. 343 * If the actual elevation is below this value, the scheduling block 344 * holding this object should not be scheduled for execution. 345 * <p> 346 * The angle returned is the one held internally by this object, 347 * so changes made to it will be reflected herein.</p> 348 * 349 * @return 350 * the minimum allowable angle between the observed source and the horizon. 351 * 352 * @see #hasSolarSeparationConstraint() 353 * @see #resetMinAllowableSolarSeparation() 354 */ 355 public Angle getMinAllowableElevation() 356 { 357 return elevationMin; 358 } 359 360 /** 361 * Puts the <tt>minAllowableElevation</tt> constraint back to its 362 * default value. 363 * The default value is the minimum elevation at which an EVLA antenna 364 * can point. 365 * 366 * @see #getMinAllowableSolarSeparation() 367 * @see #hasSolarSeparationConstraint() 368 */ 369 public final void resetMinAllowableElevation() 370 { 371 elevationMin.set(ANTENNA_MIN_ELEV, DEGREE); 372 } 373 374 /** 375 * Always returns <i>true</i> because the antennas themselves may point 376 * only so low. 377 * 378 * @return <i>true</i> 379 * 380 * @see #getMinAllowableElevation() 381 * @see #resetMinAllowableElevation() 382 */ 383 public boolean hasMinimumElevationConstraint() 384 { 385 return true; 386 } 387 388 //---------------------------------------------------------------------------- 389 // Atmospheric Opacity 390 //---------------------------------------------------------------------------- 391 392 /** 393 * Returns the maximum allowable atmospheric opacity. 394 * If the actual opacity is above this value, the scheduling block 395 * holding this object should not be scheduled for execution. 396 * 397 * @return the maximum allowable atmospheric opacity. 398 * 399 * @see #hasOpacityConstraint() 400 * @see #resetMaxAllowableOpacity() 401 * @see #setMaxAllowableOpacity(double) 402 */ 403 public double getMaxAllowableOpacity() 404 { 405 return opacityMax.doubleValue(); 406 } 407 408 /** 409 * Sets the maximum allowable atmospheric opacity. 410 * 411 * @param newMax 412 * the maximum allowable atmospheric opacity. 413 * 414 * @see #getMaxAllowableOpacity() 415 * @see #hasOpacityConstraint() 416 * @see #resetMaxAllowableOpacity() 417 */ 418 public void setMaxAllowableOpacity(double newMax) 419 { 420 opacityMax = BigDecimal.valueOf(newMax); 421 } 422 423 /** 424 * Puts the <tt>maxAllowableOpacity</tt> constraint back to its 425 * default value. 426 * The default value is one that typically would not prevent scheduling. 427 * 428 * @see #getMaxAllowableOpacity() 429 * @see #hasOpacityConstraint() 430 * @see #setMaxAllowableOpacity(double) 431 */ 432 public void resetMaxAllowableOpacity() 433 { 434 opacityMax = NO_MAX_OPACITY; 435 } 436 437 /** 438 * Returns <i>false</i> if the opacity constraint is so relaxed as to 439 * practically not exist. This method is intended mainly for handling 440 * situations at the user interface level where one first wants to say 441 * I do or do not care about the opacity, 442 * and only if I care will I enter a value. 443 * Most other clients can ignore this method and use 444 * {@link #getMaxAllowableOpacity()}; if opacity is not 445 * a concern, that method will return a value sufficiently low that it 446 * will not constrain scheduling. 447 * 448 * @return 449 * <i>true</i> if this set of constraints has an opacity constraint. 450 * 451 * @see #getMaxAllowableOpacity() 452 * @see #resetMaxAllowableOpacity() 453 * @see #setMaxAllowableOpacity(double) 454 */ 455 public boolean hasOpacityConstraint() 456 { 457 return !MathUtil.doubleValueIsInfinite(opacityMax); 458 } 459 460 //---------------------------------------------------------------------------- 461 // Sunrise & Sunset 462 //---------------------------------------------------------------------------- 463 464 /** 465 * Returns <i>true</i> if the scheduling block holding this object should 466 * not be scheduled near sunrise. 467 * 468 * @return <i>true</i> if sunrise should be avoided. 469 */ 470 @XmlElement 471 public boolean getAvoidSunrise() { return avoidSunrise; } 472 473 /** 474 * Used to specify whether or not the scheduling block holding this object 475 * should avoid execution near sunrise. 476 * 477 * @param avoid 478 * <i>true</i> if sunrise should be avoided. 479 * 480 * @see #setAvoidSunriseAndSunset(boolean) 481 */ 482 public void setAvoidSunrise(boolean avoid) { avoidSunrise = avoid; } 483 484 /** 485 * Puts the <tt>avoidSunrise</tt> constraint back to its default value. 486 * The default value is one that typically would not prevent scheduling. 487 */ 488 public final void resetAvoidSunrise() { avoidSunrise = false; } 489 490 /** 491 * Returns <i>true</i> if the scheduling block holding this object should 492 * not be scheduled near sunset. 493 * 494 * @return <i>true</i> if sunset should be avoided. 495 */ 496 @XmlElement 497 public boolean getAvoidSunset() { return avoidSunset; } 498 499 /** 500 * Used to specify whether or not the scheduling block holding this object 501 * should avoid execution near sunset. 502 * 503 * @param avoid 504 * <i>true</i> if sunset should be avoided. 505 * 506 * @see #setAvoidSunriseAndSunset(boolean) 507 */ 508 public void setAvoidSunset(boolean avoid) { avoidSunset = avoid; } 509 510 /** 511 * Puts the <tt>avoidSunset</tt> constraint back to its default value. 512 * The default value is one that typically would not prevent scheduling. 513 */ 514 public final void resetAvoidSunset() { avoidSunset = false; } 515 516 /** 517 * A convenience method for avoiding both sunrise and sunset. 518 * 519 * @param avoid 520 * <i>true</i> if both sunrise and sunset should be avoided. 521 * 522 * @see #setAvoidSunrise(boolean) 523 * @see #setAvoidSunset(boolean) 524 */ 525 public void setAvoidSunriseAndSunset(boolean avoid) 526 { 527 avoidSunrise = avoid; 528 avoidSunset = avoid; 529 } 530 531 //============================================================================ 532 // SOLAR SEPARATION AIDES 533 //============================================================================ 534 535 private static Map<ReceiverBand, 536 Map<TelescopeConfiguration, Angle>> MIN_SOLAR_SEPS = null; 537 538 private static boolean successfulRead = false; 539 540 /** 541 * Returns the suggested minimum angular separation between a target source 542 * and the sun. This value varies based on the frequency observed and the 543 * array configuration. 544 * 545 * @param receiver 546 * the receiver band used to observe the source. 547 * 548 * @param arrayConfig 549 * the configuration of the telescope at the time the source is observed. 550 * 551 * @return 552 * the minimum solar separation angle. 553 */ 554 public static Angle 555 getSuggestedMinimumSolarSeparation(ReceiverBand receiver, 556 TelescopeConfiguration arrayConfig) 557 { 558 if (!successfulRead) 559 { 560 if (MIN_SOLAR_SEPS == null) 561 MIN_SOLAR_SEPS = new HashMap<ReceiverBand, Map<TelescopeConfiguration,Angle>>(); 562 563 new SolSepTableLoader().loadInto(MIN_SOLAR_SEPS); 564 } 565 566 Angle minSep = null; 567 568 Map<TelescopeConfiguration, Angle> innerMap = MIN_SOLAR_SEPS.get(receiver); 569 570 if (innerMap != null) 571 minSep = innerMap.get(arrayConfig); 572 573 return minSep == null ? new Angle(BigDecimal.TEN) : minSep; 574 } 575 576 /** Reads table from XML file. */ 577 static class SolSepTableLoader extends DefaultHandler 578 { 579 void loadInto(Map<ReceiverBand, Map<TelescopeConfiguration, Angle>> table) 580 { 581 this.table = table; 582 583 URL tableFile = EnvironmentalConstraints.class.getResource("SolarSeparationMinimums.xml"); 584 585 if (tableFile != null) 586 { 587 SolSepTableLoader callbackTarget = this; 588 SAXParserFactory factory = SAXParserFactory.newInstance(); 589 try 590 { 591 SAXParser parser = factory.newSAXParser(); 592 parser.parse(tableFile.openStream(), callbackTarget); 593 successfulRead = true; 594 } 595 catch (Exception ex) 596 { 597 log.warn("Trouble loading SolarSeparationMinimums.xml:", ex); 598 } 599 } 600 else 601 { 602 log.warn("URL for SolarSeparationMinimums.xml is null."); 603 } 604 } 605 606 private Map<ReceiverBand, Map<TelescopeConfiguration, Angle>> table; 607 private Map<TelescopeConfiguration, Angle> bandMap; 608 609 /** Determines which element is being processed now. */ 610 @Override 611 public void startElement(String uri, 612 String localName, 613 String qName, 614 Attributes attributes) 615 throws SAXException 616 { 617 if (qName.equals("band")) 618 { 619 bandMap = new HashMap<TelescopeConfiguration, Angle>(); 620 621 String bandName = "EVLA_" + attributes.getValue("id"); 622 ReceiverBand band = ReceiverBand.fromString(bandName); 623 624 table.put(band, bandMap); 625 } 626 else if (qName.equals("config")) 627 { 628 String cfgName = "VLA_" + attributes.getValue("id"); 629 TelescopeConfiguration cfg = TelescopeConfiguration.fromString(cfgName); 630 631 BigDecimal degrees = new BigDecimal(attributes.getValue("degrees")); 632 Angle minAngle = new Angle(degrees); 633 634 bandMap.put(cfg, minAngle); 635 } 636 } 637 } 638 639 //============================================================================ 640 // PERSISTENCE 641 //============================================================================ 642 643 //Since the units are not expected to vary much by user, we're opting to store 644 //the values w/ out the units in both XML and database. 645 //We're also considering the elements to optional, so we deal with null 646 //on both output and input. 647 648 @XmlElement 649 @SuppressWarnings("unused") 650 private BigDecimal getMaxWindSpeedMetersPerSec() 651 { 652 if (hasWindSpeedConstraint()) 653 return windSpeedMax.toUnits(METERS_PER_SECOND).stripTrailingZeros(); 654 else 655 return null; 656 } 657 @SuppressWarnings("unused") 658 private void setMaxWindSpeedMetersPerSec(BigDecimal value) 659 { 660 if (value != null) 661 windSpeedMax.set(value, METERS_PER_SECOND); 662 else 663 resetMaxAllowableWindSpeed(); 664 } 665 666 @XmlElement 667 @SuppressWarnings("unused") 668 private BigDecimal getMaxApiDegrees() 669 { 670 if (hasApiConstraint()) 671 return tropospherePhaseMax.toUnits(DEGREE).stripTrailingZeros(); 672 else 673 return null; 674 } 675 @SuppressWarnings("unused") 676 private void setMaxApiDegrees(BigDecimal value) 677 { 678 if (value != null) 679 tropospherePhaseMax.set(value, DEGREE); 680 else 681 resetMaxAllowableApi(); 682 } 683 684 @XmlElement 685 @SuppressWarnings("unused") 686 private BigDecimal getMaxIonosphereDegrees() 687 { 688 if (hasIonosphereConstraint()) 689 return ionospherePhaseMax.toUnits(DEGREE).stripTrailingZeros(); 690 else 691 return null; 692 } 693 @SuppressWarnings("unused") 694 private void setMaxIonosphereDegrees(BigDecimal value) 695 { 696 if (value != null) 697 ionospherePhaseMax.set(value, DEGREE); 698 else 699 resetMaxAllowableIonospherePhase(); 700 } 701 702 @XmlElement 703 @SuppressWarnings("unused") 704 private BigDecimal getMinSolarSepDegrees() 705 { 706 if (hasSolarSeparationConstraint()) 707 return solarSepMin.toUnits(DEGREE).stripTrailingZeros(); 708 else 709 return null; 710 } 711 @SuppressWarnings("unused") 712 private void setMinSolarSepDegrees(BigDecimal value) 713 { 714 if (value != null) 715 solarSepMin.set(value, DEGREE); 716 else 717 resetMinAllowableSolarSeparation(); 718 } 719 720 @XmlElement 721 @SuppressWarnings("unused") 722 private BigDecimal getMinElevationDegrees() 723 { 724 BigDecimal elevMinDeg = elevationMin.toUnits(DEGREE); 725 726 if (elevMinDeg.compareTo(ANTENNA_MIN_ELEV) > 0) 727 return elevMinDeg.stripTrailingZeros(); 728 else 729 return null; 730 } 731 @SuppressWarnings("unused") 732 private void setMinElevationDegrees(BigDecimal value) 733 { 734 if (value != null) 735 elevationMin.set(value, DEGREE); 736 else 737 resetMinAllowableElevation(); 738 } 739 740 @XmlElement 741 @SuppressWarnings("unused") 742 private BigDecimal getMaxOpacity() 743 { 744 if (hasOpacityConstraint()) 745 return opacityMax; 746 else 747 return null; 748 } 749 @SuppressWarnings("unused") 750 private void setMaxOpacity(BigDecimal value) 751 { 752 if (value != null) 753 opacityMax = value; 754 else 755 resetMaxAllowableOpacity(); 756 } 757 758 //============================================================================ 759 // EVALUATION OF CONDITIONS 760 //============================================================================ 761 762 /** 763 * Returns a list of all the constraints that should block scheduling based 764 * on the given conditions. A subset of the conditions may be tested by 765 * using <i>null</i> or <i>false</i> for those conditions you do not want 766 * to test. 767 * 768 * @param windSpeed 769 * the wind velocity to be tested against this constraint. 770 * If you do not wish to use wind in the test for blocking constraints, 771 * use a value of <i>null</i>. 772 * 773 * @param api 774 * the atmospheric phase to be tested against this constraint. 775 * If you do not wish to use API in the test for blocking constraints, 776 * use a value of <i>null</i>. 777 * 778 * @param ionosphericPhase 779 * the ionospheric phase to be tested against this constraint. 780 * If you do not wish to use this phase in the test for blocking constraints, 781 * use a value of <i>null</i>. 782 * 783 * @param solarSeparation 784 * the angle between the sun and the target to be tested against this constraint. 785 * If you do not wish to use this angle in the test for blocking constraints, 786 * use a value of <i>null</i>. 787 * 788 * @param nearSunrise 789 * an indication that the observing time is near sunrise. 790 * If you do not wish to use this quantity in the test for blocking constraints, 791 * use a value of <i>false</i>. 792 * 793 * @param nearSunset 794 * an indication that the observing time is near sunset. 795 * If you do not wish to use this quantity in the test for blocking constraints, 796 * use a value of <i>false</i>. 797 * 798 * @return 799 * a list of all the constraints that failed based on the given conditions. 800 */ 801 //TODO update for minimum elevation. Also, don't make client calc solarSeparation -- 802 // this class should do that as a service. 803 // Add'l params: Date start, Date end, SkyPosition sourcePos. 804 // Remove param Angle solarSeparation. 805 public List<SchedulingConstraint> getBlockingConstraints(LinearVelocity windSpeed, 806 Angle api, 807 Angle ionosphericPhase, 808 Angle solarSeparation, 809 boolean nearSunrise, 810 boolean nearSunset) 811 { 812 return testConstraints(false, 813 windSpeed, api, ionosphericPhase, 814 solarSeparation, nearSunrise, nearSunset); 815 } 816 817 /** 818 * Returns <i>true</i> if any of the given conditions are outside 819 * of this set of constraints. 820 * See {@link #getBlockingConstraints(LinearVelocity, Angle, Angle, Angle, 821 * boolean, boolean)} for a description of the parameters. 822 * <p> 823 * This method is logically equivalent to calling 824 * <tt>getBlockingConstraints(...).size() > 0</tt>, but will often be 825 * faster because it stops testing on the first failure.</p> 826 * 827 * @return 828 * <i>true</i> if any of the given conditions are outside 829 * of this set of constraints. 830 */ 831 //See TODO for getBlockingConstraints 832 public boolean hasBlockingConstraints(LinearVelocity windSpeed, 833 Angle api, 834 Angle ionosphericPhase, 835 Angle solarSeparation, 836 boolean nearSunrise, 837 boolean nearSunset) 838 { 839 return testConstraints(true, 840 windSpeed, api, ionosphericPhase, 841 solarSeparation, nearSunrise, nearSunset).size() > 0; 842 } 843 844 /** 845 * Does the work for both 846 * getBlockingConstraints(...) and hasBlockingConstraints(...). 847 */ 848 //See TODO for getBlockingConstraints 849 private List<SchedulingConstraint> testConstraints(boolean failFast, 850 LinearVelocity windSpeed, 851 Angle api, 852 Angle ionosphericPhase, 853 Angle solarSeparation, 854 boolean nearSunrise, 855 boolean nearSunset) 856 { 857 List<SchedulingConstraint> blocks = new ArrayList<SchedulingConstraint>(); 858 859 if (nearSunrise && avoidSunrise) 860 { 861 blocks.add(SchedulingConstraint.NEAR_SUNRISE); 862 if (failFast) 863 return blocks; 864 } 865 if (nearSunset && avoidSunset) 866 { 867 blocks.add(SchedulingConstraint.NEAR_SUNSET); 868 if (failFast) 869 return blocks; 870 } 871 if (windSpeed != null && windSpeed.compareTo(windSpeedMax) > 0) 872 { 873 blocks.add(SchedulingConstraint.WIND_SPEED); 874 if (failFast) 875 return blocks; 876 } 877 if (api != null && api.compareTo(tropospherePhaseMax) > 0) 878 { 879 blocks.add(SchedulingConstraint.ATMOSPHERIC_PHASE); 880 if (failFast) 881 return blocks; 882 } 883 if (ionosphericPhase != null && 884 ionosphericPhase.compareTo(ionospherePhaseMax) > 0) 885 { 886 blocks.add(SchedulingConstraint.IONOSPHERIC_PHASE); 887 if (failFast) 888 return blocks; 889 } 890 if (solarSeparation != null && 891 solarSeparation.compareTo(solarSepMin) < 0) 892 { 893 blocks.add(SchedulingConstraint.SOLAR_SEPARATION); 894 if (failFast) 895 return blocks; 896 } 897 898 return blocks; 899 } 900 901 //============================================================================ 902 // 903 //============================================================================ 904 905 /** 906 * Returns a copy of these constraints. 907 * <p> 908 * If anything goes wrong during the cloning procedure, 909 * a {@code RuntimeException} will be thrown.</p> 910 */ 911 @Override 912 public EnvironmentalConstraints clone() 913 { 914 EnvironmentalConstraints clone = null; 915 916 try 917 { 918 //This line takes care of the primitive & immutable fields properly 919 clone = (EnvironmentalConstraints)super.clone(); 920 921 clone.windSpeedMax = this.windSpeedMax.clone(); 922 clone.tropospherePhaseMax = this.tropospherePhaseMax.clone(); 923 clone.ionospherePhaseMax = this.ionospherePhaseMax.clone(); 924 clone.solarSepMin = this.solarSepMin.clone(); 925 clone.elevationMin = this.elevationMin.clone(); 926 } 927 catch (Exception ex) 928 { 929 throw new RuntimeException(ex); 930 } 931 932 return clone; 933 } 934 935 /** Returns <i>true</i> if {@code o} is equal to these constraints. */ 936 @Override 937 public boolean equals(Object o) 938 { 939 //Quick exit if o is this 940 if (o == this) 941 return true; 942 943 //Quick exit if o is null 944 if (o == null) 945 return false; 946 947 //Quick exit if classes are different 948 if (!o.getClass().equals(this.getClass())) 949 return false; 950 951 EnvironmentalConstraints other = (EnvironmentalConstraints)o; 952 953 return 954 other.avoidSunrise == this.avoidSunrise && 955 other.avoidSunset == this.avoidSunset && 956 957 other.opacityMax.compareTo(this.opacityMax) == 0 && 958 959 other.windSpeedMax.equals(this.windSpeedMax) && 960 other.tropospherePhaseMax.equals(this.tropospherePhaseMax) && 961 other.ionospherePhaseMax.equals(this.ionospherePhaseMax) && 962 other.solarSepMin.equals(this.solarSepMin) && 963 other.elevationMin.equals(this.elevationMin); 964 } 965 966 /** Returns a hash code value for these constraints. */ 967 @Override 968 public int hashCode() 969 { 970 //Taken from the Effective Java book by Joshua Bloch. 971 //The constants 17 & 37 are arbitrary & carry no meaning. 972 int result = 17; 973 974 result = 37 * result + (avoidSunrise ? 17 : 3); 975 result = 37 * result + (avoidSunset ? 17 : 3); 976 result = 37 * result + opacityMax.hashCode(); 977 result = 37 * result + windSpeedMax.hashCode(); 978 result = 37 * result + tropospherePhaseMax.hashCode(); 979 result = 37 * result + ionospherePhaseMax.hashCode(); 980 result = 37 * result + solarSepMin.hashCode(); 981 result = 37 * result + elevationMin.hashCode(); 982 983 return result; 984 } 985 /* 986 public static void main(String... args) throws Exception 987 { 988 System.out.println(getSuggestedMinimumSolarSeparation(ReceiverBand.EVLA_X, TelescopeConfiguration.VLA_A)); 989 } 990 */ 991 }