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    }