001 package edu.nrao.sss.model.project; 002 003 import edu.nrao.sss.astronomy.CelestialCoordinateSystem; 004 import edu.nrao.sss.astronomy.Epoch; 005 import edu.nrao.sss.astronomy.SkyPosition; 006 import edu.nrao.sss.astronomy.SimpleSkyPosition; 007 import edu.nrao.sss.astronomy.CoordinateConversionException; 008 import edu.nrao.sss.geom.EarthPosition; 009 import edu.nrao.sss.measure.Angle; 010 import edu.nrao.sss.measure.JulianDate; 011 import edu.nrao.sss.measure.Latitude; 012 import edu.nrao.sss.measure.LocalSiderealTime; 013 import edu.nrao.sss.measure.Longitude; 014 import edu.nrao.sss.measure.TimeUnits; 015 import edu.nrao.sss.measure.TimeDuration; 016 import edu.nrao.sss.model.project.scan.Scan; 017 import edu.nrao.sss.model.project.scan.ScanLoop; 018 import edu.nrao.sss.model.project.scan.ScanLoopElement; 019 import edu.nrao.sss.model.project.scan.ScanTimeSpecification; 020 import edu.nrao.sss.model.project.scan.TippingOrder; 021 import edu.nrao.sss.model.project.scan.TippingScan; 022 import edu.nrao.sss.model.source.Source; 023 import edu.nrao.sss.model.resource.Resource; 024 import edu.nrao.sss.model.resource.ReceiverBand; 025 import edu.nrao.sss.model.resource.TelescopeType; 026 import edu.nrao.sss.model.resource.evla.EvlaPointingPosition; 027 import edu.nrao.sss.model.resource.evla.EvlaTelescopeMotionSimulator; 028 import edu.nrao.sss.util.SourceNotFoundException; 029 import edu.nrao.sss.validation.FailureSeverity; 030 import edu.nrao.sss.validation.ValidationManager; 031 import edu.nrao.sss.validation.ValidationPurpose; 032 import edu.nrao.sss.validation.ValidationFailure; 033 034 import java.math.BigDecimal; 035 import java.util.ArrayList; 036 import java.util.Date; 037 import java.util.List; 038 import java.util.Set; 039 040 /** 041 * This class provides a template method ({@link #iterateOverSchedulingBlock}) 042 * that iterates over all the scans in a SB. The class provides protected 043 * variables that keep track of the current scan's start and stop MJD, 044 * currentScanNumber, totalDuration (so far), the slew time of this scan, and a 045 * boolean flag to indicate whether or not the passed in SB is scheduled 046 * dymanically. Clients of this class must create a subclass and override one 047 * or more of the following methods: 048 * <code><ul> 049 * <li>beforeIteratingAction</li> 050 * <li>beforeFirstScanAction</li> 051 * <li>afterFirstScanAction</li> 052 * <li>beforeLastScanAction</li> 053 * <li>afterLastScanAction</li> 054 * <li>beforeScanLoopAction</li> 055 * <li>afterScanLoopAction</li> 056 * <li>beforeScanAction</li> 057 * <li>scanAction</li> 058 * <li>afterScanAction</li> 059 * </ul></code> 060 * Subclasses may also toggle the {@code validatesSchedulingBlock} property. If 061 * this class is NOT validating its scheduling block, it is the client's 062 * responsibility to validate the SB before calling iterateOverSchedulingBlock. 063 * If a SB is found to be incomplete the iteration will stop. If a scan is 064 * found that has no source or no source position, a SourceNotFoundException 065 * will be thrown. A CoordinateConversionException will be thrown if there is a 066 * problem converting the source position to either RA/Dec or Az/El. If 067 * validation is turned off, there is no gauruntee that the protected variables 068 * this class provides during iteration will be correct. 069 */ 070 public class AbstractScheduleIterator 071 { 072 protected static final EarthPosition EVLA_LOCATION = TelescopeType.EVLA.getLocation(); 073 protected static final LocalSiderealTime EVLA_LST = new LocalSiderealTime(); 074 protected static final TimeDuration SUBREFLECTOR_SLEW = new TimeDuration("20.0", TimeUnits.SECOND); 075 076 protected EvlaTelescopeMotionSimulator sim = new EvlaTelescopeMotionSimulator(); 077 078 protected int currentScanNumber = 1; 079 protected int scanCount = 0; 080 protected JulianDate startMjd = null; 081 protected JulianDate endMjd = null; 082 protected TimeDuration totalDuration = null; 083 protected TimeDuration slewTime = null; 084 private ReceiverBand prevScanBand = null; 085 protected boolean dynamicSched; 086 087 private ValidationManager validator = new ValidationManager(); 088 private List<ValidationFailure> validationFailures = new ArrayList<ValidationFailure>(); 089 090 private boolean errorsExist = false; 091 private boolean warningsExist = false; 092 private boolean validateSB = true; 093 094 095 public AbstractScheduleIterator() {} 096 097 /** 098 * Returns true if the last call of {@code iterateOverSchedulingBlock} had 099 * validation errors. 100 */ 101 public boolean hasValidationErrors() 102 { 103 return this.errorsExist; 104 } 105 106 /** 107 * Returns true if the last call of {@code iterateOverSchedulingBlock} had 108 * validation warnings. 109 */ 110 public boolean hasValidationWarnings() 111 { 112 return this.warningsExist; 113 } 114 115 /** 116 * Returns a list of all warnings and errors that were found during the last 117 * call to {@code iterateOverSchedulingBlock}. 118 */ 119 public List<ValidationFailure> getValidationFailures() 120 { 121 return this.validationFailures; 122 } 123 124 public boolean getValidatesSchedulingBlock() 125 { 126 return this.validateSB; 127 } 128 129 public void setValidatesSchedulingBlock(boolean v) 130 { 131 this.validateSB = v; 132 } 133 134 /** 135 * Returns the total time spent in this schedule <em>so far</em>. The value 136 * that this method returns is updated before calling the 137 * beforeFirstScanAction, beforeScanAction and beforeLastScanAction methods. 138 * It is NOT updated before calling beforeScanLoopAction. The value returned 139 * is a clone of the real object held internally so changes to it will NOT be 140 * reflected in this class. 141 */ 142 public TimeDuration getScheduleDuration() 143 { 144 return this.totalDuration != null? this.totalDuration.clone() : new TimeDuration(); 145 } 146 147 /** 148 * See class comments. 149 */ 150 public void iterateOverSchedulingBlock(SchedulingBlock sb) 151 throws IllegalArgumentException, CoordinateConversionException, SourceNotFoundException 152 { 153 if (sb == null) 154 return; 155 156 this.errorsExist = this.warningsExist = false; 157 this.validationFailures.clear(); 158 159 if (getValidatesSchedulingBlock()) 160 { 161 this.validator.validate(sb, ValidationPurpose.CERTIFY_READY_TO_USE); 162 for (ValidationFailure f : this.validator.getFailures()) 163 { 164 FailureSeverity severity = f.getSeverity(); 165 if (FailureSeverity.ERROR.equals(severity) || FailureSeverity.FATAL.equals(severity)) 166 this.errorsExist = true; 167 168 else 169 this.warningsExist = true; 170 171 this.validationFailures.add(f); 172 } 173 } 174 175 if (!errorsExist) 176 { 177 ScanLoop loop = sb.getScanSequence(); 178 179 this.currentScanNumber = 1; 180 this.scanCount = countScans(loop); 181 this.dynamicSched = sb.mayBeScheduledDynamically(); 182 this.endMjd = null; 183 this.totalDuration = new TimeDuration(); 184 this.slewTime = null; 185 this.prevScanBand = null; 186 this.sim = new EvlaTelescopeMotionSimulator(); 187 188 if (this.dynamicSched) 189 this.startMjd = new JulianDate(); 190 191 else 192 this.startMjd = new JulianDate(sb.getFixedStartTime()); 193 194 // Update the simulator's start Az/El if necessary 195 EvlaPointingPosition initialAntennaPos = sb.getAssumedTelescopePointing(); 196 Angle az = initialAntennaPos.getLongitude(); 197 Angle el = initialAntennaPos.getLatitude(); 198 199 if (initialAntennaPos.getCoordinateSystem() != CelestialCoordinateSystem.HORIZONTAL) 200 { 201 // Convert the coordinates to HORIZONTAL 202 Date startDate = startMjd.toDate(); 203 EVLA_LST.setSolarTime(startDate); 204 SkyPosition horPos = initialAntennaPos.toSkyPosition().toPosition(CelestialCoordinateSystem.HORIZONTAL, 205 Epoch.J2000, EVLA_LOCATION, EVLA_LST); 206 207 az = horPos.getLongitude(startDate).toAngle(); 208 el = horPos.getLatitude(startDate).toAngle(); 209 } 210 211 this.sim.setCurrentAntennaAzimuth(az); 212 this.sim.setCurrentAntennaElevation(el); 213 214 beforeIteratingAction(); 215 iterateOverScanLoop(loop); 216 } 217 } 218 219 private void iterateOverScanLoop(ScanLoop loop) 220 throws IllegalArgumentException, CoordinateConversionException, SourceNotFoundException 221 { 222 List<ScanLoopElement> elements = loop.getElements(); 223 if (!elements.isEmpty()) 224 { 225 for (int i = 0; i < loop.getIterationCount(); i++) 226 { 227 for (ScanLoopElement e : elements) 228 { 229 if (e instanceof ScanLoop) 230 { 231 beforeScanLoopAction((ScanLoop)e); 232 iterateOverScanLoop((ScanLoop)e); 233 afterScanLoopAction((ScanLoop)e); 234 } 235 236 else 237 iterateOverScan((Scan)e); 238 } 239 } 240 241 if (loop.getBracketed()) 242 { 243 ScanLoopElement e = elements.get(0); 244 if (e instanceof ScanLoop) 245 { 246 beforeScanLoopAction((ScanLoop)e); 247 iterateOverScanLoop((ScanLoop)e); 248 afterScanLoopAction((ScanLoop)e); 249 } 250 251 else 252 iterateOverScan((Scan)e); 253 } 254 } 255 } 256 257 /** 258 * Calls the various protected methods of this class at the right time, but 259 * it also caches a scan and is actually always working on the 260 * <em>previous</em> scan. This lets us treat the last scan differently. 261 */ 262 private void iterateOverScan(Scan scan) 263 throws IllegalArgumentException, CoordinateConversionException, SourceNotFoundException 264 { 265 this.endMjd = null; 266 267 boolean bandsChanged = (this.prevScanBand == null); 268 269 Resource r = scan.getResource(); 270 Set<ReceiverBand> bands = null; 271 if (r != null) 272 bands = r.getAntennaElectronics().getActiveReceivers(); 273 274 if (bands != null && !bands.isEmpty()) 275 { 276 ReceiverBand band = bands.iterator().next(); 277 bandsChanged = !band.equals(this.prevScanBand); 278 this.prevScanBand = band; 279 } 280 281 Date startDate = this.startMjd.toDate(); 282 SkyPosition pos = getStartPosition(scan, startDate); 283 284 if (pos == null) 285 throw new IllegalArgumentException("Scan '" + scan.getName() + "' does not have a source position specified."); 286 287 ScanTimeSpecification timeSpec = scan.getTimeSpec(); 288 switch(timeSpec.getTimeType()) 289 { 290 case STOP_UT: 291 this.endMjd = new JulianDate(timeSpec.getTime()); 292 break; 293 294 case STOP_LST: 295 LocalSiderealTime lstDateTime = new LocalSiderealTime(this.startMjd); 296 lstDateTime.advanceTo(timeSpec.getLst()); 297 this.endMjd = lstDateTime.toJulianDate(); 298 break; 299 300 case DURATION_SIDEREAL: 301 case DURATION_UT: 302 TimeDuration dur = timeSpec.getDuration(); 303 if (timeSpec.isSiderealTime()) 304 dur = LocalSiderealTime.siderealToSolar(dur); 305 306 this.endMjd = this.startMjd.clone().add(dur); 307 break; 308 309 case ON_SOURCE_SIDEREAL: 310 case ON_SOURCE_UT: 311 TimeDuration onSrc = timeSpec.getDuration(); 312 if (timeSpec.isSiderealTime()) 313 onSrc = LocalSiderealTime.siderealToSolar(onSrc); 314 315 Angle currAz = this.sim.getCurrentAntennaAzimuth(); 316 Angle currEl = this.sim.getCurrentAntennaElevation(); 317 318 EVLA_LST.setSolarTime(startDate); 319 SkyPosition horPos = pos.toPosition(CelestialCoordinateSystem.HORIZONTAL, Epoch.J2000, 320 EVLA_LOCATION, EVLA_LST); 321 322 Longitude az = horPos.getLongitude(startDate); 323 Latitude el = horPos.getLatitude(startDate); 324 this.slewTime = this.sim.moveTo(az, el, az, el, scan.getAntennaWrap(), scan.getAllowOverTheTop()); 325 326 //TODO: replace this with a real motion model to calculate the 327 //sub-reflector move time! 328 if (bandsChanged && this.slewTime.compareTo(SUBREFLECTOR_SLEW) < 0) 329 this.slewTime = SUBREFLECTOR_SLEW.clone(); 330 331 this.endMjd = this.startMjd.clone().add(onSrc).add(this.slewTime); 332 333 // Now reset the simulator so we can have real calculations next time: 334 this.sim.setCurrentAntennaAzimuth(currAz); 335 this.sim.setCurrentAntennaElevation(currEl); 336 337 break; 338 339 case START_LST: 340 case START_UT: 341 default: 342 throw new UnsupportedOperationException("This class does not support START times for scans!"); 343 } 344 345 //Do real simulator calculation here now that we have a definitive end time! 346 Date endDate = this.endMjd.toDate(); 347 348 EVLA_LST.setSolarTime(startDate); 349 SkyPosition horPos = pos.toPosition(CelestialCoordinateSystem.HORIZONTAL, Epoch.J2000, 350 EVLA_LOCATION, EVLA_LST); 351 352 Longitude az = horPos.getLongitude(startDate); 353 Latitude el = horPos.getLatitude(startDate); 354 355 // Not checking for a null position here because if getStartPosition() 356 // did not return null then getEndPosition() won't either. 357 SkyPosition horPosEnd = getEndPosition(scan, endDate).toPosition(CelestialCoordinateSystem.HORIZONTAL, Epoch.J2000, 358 EVLA_LOCATION, new LocalSiderealTime(this.endMjd)); 359 360 this.slewTime = this.sim.moveTo(az, el, 361 horPosEnd.getLongitude(endDate), horPosEnd.getLatitude(endDate), 362 scan.getAntennaWrap(), scan.getAllowOverTheTop() 363 ); 364 365 //TODO: replace this with a real motion model to calculate the 366 //sub-reflector move time! 367 if (bandsChanged && this.slewTime.compareTo(SUBREFLECTOR_SLEW) < 0) 368 this.slewTime = SUBREFLECTOR_SLEW.clone(); 369 370 // End time really should not be null by this point! 371 long lenMs = this.endMjd.toDate().getTime() - this.startMjd.toDate().getTime(); 372 this.totalDuration.add(new TimeDuration(new BigDecimal(lenMs), TimeUnits.MILLISECOND)); 373 374 if (this.currentScanNumber == 1) 375 beforeFirstScanAction(scan); 376 377 if (this.currentScanNumber == this.scanCount) 378 beforeLastScanAction(scan); 379 380 beforeScanAction(scan); 381 scanAction(scan); 382 afterScanAction(scan); 383 384 if (this.currentScanNumber == this.scanCount) 385 afterLastScanAction(scan); 386 387 if (this.currentScanNumber == 1) 388 afterFirstScanAction(scan); 389 390 this.startMjd = this.endMjd; 391 this.currentScanNumber++; 392 } 393 394 /** 395 * A streamlined method that recursively counts the number of scan executions 396 * in {@code loop}. This is much faster and more memory/time efficient than 397 * saying {@code loop.toScanList().size()}. 398 */ 399 private int countScans(ScanLoop loop) 400 { 401 int count = 0; 402 int iterations = loop.getIterationCount(); 403 404 for (ScanLoopElement e : loop.getElements()) 405 count += (e instanceof ScanLoop)? countScans((ScanLoop)e) * iterations : iterations; 406 407 if (loop.getBracketed()) 408 count++; 409 410 return count; 411 } 412 413 private org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(AbstractScheduleIterator.class); 414 415 /** 416 * Returns the appropriate start SkyPosition object for {@code scan} at 417 * {@code dateTime}. This method takes into account more complicated Scan 418 * modes that do not necessarily specify a Source object for the scan. 419 */ 420 protected SkyPosition getStartPosition(Scan scan, Date dateTime) 421 throws SourceNotFoundException 422 { 423 SkyPosition pos = null; 424 425 switch (scan.getMode()) 426 { 427 case TIPPING: 428 TippingScan tip = (TippingScan)scan; 429 430 // This Elevation will be based on the TippingOrder of the scan. (19.5 or 55 degrees). 431 pos = new SimpleSkyPosition(CelestialCoordinateSystem.HORIZONTAL, Epoch.J2000); 432 ((SimpleSkyPosition)pos).setLatitude(new Latitude(tip.getOrder().equals(TippingOrder.HIGH_TO_LOW)? "55.0" : "19.5")); 433 ((SimpleSkyPosition)pos).setLongitude(new Longitude(tip.getAzimuth())); 434 break; 435 436 default: 437 Source src = scan.getSource(dateTime); 438 439 // We can't continue if there's a scan w/o a source position 440 if (src == null) 441 throw new SourceNotFoundException("Scan '" + scan.getName() + "' does not have a source specified."); 442 443 pos = src.getPosition(); 444 } 445 446 return pos; 447 } 448 449 /** 450 * Returns the appropriate end SkyPosition object for {@code scan} at 451 * {@code dateTime}. This method takes into account more complicated Scan 452 * modes that do not necessarily specify a Source object for the scan. 453 */ 454 protected SkyPosition getEndPosition(Scan scan, Date dateTime) 455 throws SourceNotFoundException 456 { 457 SkyPosition pos = null; 458 459 switch (scan.getMode()) 460 { 461 case TIPPING: 462 TippingScan tip = (TippingScan)scan; 463 464 // This Elevation will be based on the TippingOrder of the scan. (19.5 or 55 degrees). 465 pos = new SimpleSkyPosition(CelestialCoordinateSystem.HORIZONTAL, Epoch.J2000); 466 467 // NOTE this is reversed on purpose as this is the END position, not start! 468 ((SimpleSkyPosition)pos).setLatitude(new Latitude(tip.getOrder().equals(TippingOrder.HIGH_TO_LOW)? "19.5" : "55.0")); 469 ((SimpleSkyPosition)pos).setLongitude(new Longitude(tip.getAzimuth())); 470 break; 471 472 default: 473 Source src = scan.getSource(dateTime); 474 475 // We can't continue if there's a scan w/o a source position 476 if (src == null) 477 throw new SourceNotFoundException("Scan '" + scan.getName() + "' does not have a source specified."); 478 479 pos = src.getPosition(); 480 } 481 482 return pos; 483 } 484 485 protected void beforeScanLoopAction(ScanLoop scan) {} 486 protected void afterScanLoopAction(ScanLoop scan) {} 487 protected void beforeIteratingAction() {} 488 protected void beforeFirstScanAction(Scan scan) {} 489 protected void afterFirstScanAction(Scan scan) {} 490 protected void beforeLastScanAction(Scan scan) {} 491 protected void afterLastScanAction(Scan scan) {} 492 protected void beforeScanAction(Scan scan) {} 493 protected void scanAction(Scan scan) {} 494 protected void afterScanAction(Scan scan) {} 495 }