ctools 2.1.0.dev
Loading...
Searching...
No Matches
csviscube.py
Go to the documentation of this file.
1#! /usr/bin/env python
2# ==========================================================================
3# Compute a visibility cube
4#
5# Copyright (C) 2016-2021 Juergen Knoedlseder
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20# ==========================================================================
21import sys
22import math
23import gammalib
24import ctools
25
26
27# =============== #
28# csviscube class #
29# =============== #
30class csviscube(ctools.cscript):
31 """
32 Compute a visibility cube
33 """
34
35 # Constructor
36 def __init__(self, *argv):
37 """
38 Constructor
39
40 Parameters
41 ----------
42 argv : list of str
43 List of IRAF command line parameter strings of the form
44 ``parameter=3``.
45
46 Raises
47 ------
48 TypeError
49 An invalid number of command line arguments was provided.
50 """
51 # Initialise application by calling the base class constructor
52 self._init_cscript(self.__class__.__name__, ctools.__version__, argv)
53
54 # Initialise members
55 self._cube = gammalib.GSkyMap()
56 self._results = []
57
58 # Return
59 return
60
61
62 # Private methods
63 def _get_parameters(self):
64 """
65 Get all parameters
66 """
67 # Query parameters
68 self['mjdref'].real()
69 self['tmin'].time()
70 self['tmax'].time()
71 self['geolon'].real()
72 self['geolat'].real()
73 self['outfile'].filename()
74
75 # Write input parameters into logger
76 self._log_parameters(gammalib.TERSE)
77
78 # Return
79 return
80
81 def _zenith_angle_map(self, nx, ny, dx, dy):
82 """
83 Compute zenith angle map
84
85 The zenith angle of a position (ra,dec) depends on the declination and
86 the hour angle h and is given by
87
88 zenith(h,dec) = arccos( sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(h) )
89
90 The hour angle h (or local hour angle, LHA) is defined as the difference
91 between local siderial time (LST) and the Right Ascension
92
93 h = LST - ra
94
95 The map is computed for h=-ra which is equivalent to GST=lon (or LST=0).
96 In other words, the map corresponds to the time when ra=0 passes through
97 the local meridian.
98
99 Parameters
100 ----------
101 nx : int
102 Number of Right Ascension pixels.
103 ny : int
104 Number of Declination pixels.
105 dx : float
106 Right Ascension pixel size in degrees.
107 dy : float
108 Declination pixel size in degrees.
109
110 Returns
111 -------
112 zmap : `~gammalib.GSkyMap`
113 Allsky map comprising the zenith angle for an hour angle of 0.
114 """
115 # Initialise zenith angle map
116 zmap = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-dx,dy,nx,ny)
117
118 # Set hour angle and declination vectors
119 hours = [(float(i)+0.5)*dx for i in range(nx)]
120 decs = [(float(i)+0.5)*dy-90.0 for i in range(ny)]
121
122 # Get array geographic latitude
123 geolat = self['geolat'].real()
124
125 # Precompute latitude terms
126 cos_lat = math.cos(geolat*gammalib.deg2rad)
127 sin_lat = math.sin(geolat*gammalib.deg2rad)
128
129 # Loop over all declinations and hour angles and compute the zenith
130 # angle. Store the zenith angle in the map
131 index = 0
132 for dec in decs:
133 cos_dec = math.cos(dec*gammalib.deg2rad)
134 sin_dec = math.sin(dec*gammalib.deg2rad)
135 for h in hours:
136 cos_h = math.cos(h*gammalib.deg2rad)
137 zenith = math.acos(sin_lat*sin_dec +
138 cos_lat*cos_dec*cos_h)*gammalib.rad2deg
139 zmap[index] = zenith
140 index += 1
141
142 # Log zenith angle map
143 self._log_header2(gammalib.EXPLICIT, 'Zenith angle map')
144 self._log_string(gammalib.EXPLICIT, str(zmap))
145
146 # Return zenith angle map
147 return zmap
148
149 def _sun_ra_exclusion(self, time):
150 """
151 Compute the half length of the Right Ascension interval to exclude due
152 to the Sun constraint
153
154 The Sun zenith angle constraint implies an interval in Right Ascension
155 that is to be excluded (that's the interval during which it is day).
156 This method computes half the length of this interval in degrees.
157
158 Parameters
159 ----------
160 time : `~gammalib.GTime()`
161 Time for which the Sun exclusion is to be computed
162
163 Returns
164 -------
165 dra : float
166 Right Ascension interval half length (degrees)
167 """
168 # Get array geographic latitude and minimum Sun zenith angle in radians
169 geolat = self['geolat'].real() * gammalib.deg2rad
170 sunzenith = self['sunzenith'].real() * gammalib.deg2rad
171
172 # Compute Right Ascension and Declination of Sun in degrees and
173 # convert the Declination into radians
174 sun = gammalib.GSkyDir()
175 sun.sun(time)
176 sundec = sun.dec()
177
178 # Compute some sines and cosines
179 cos_sunzenith = math.cos(sunzenith)
180 sin_geolat = math.sin(geolat)
181 cos_geolat = math.cos(geolat)
182 sin_sundec = math.sin(sundec)
183 cos_sundec = math.cos(sundec)
184
185 # Compute Right Ascension difference when Sun is below the mimimum
186 # zenith angle in degrees
187 dra = 0.0
188 nom = cos_sunzenith - sin_geolat * sin_sundec
189 denom = cos_geolat * cos_sundec
190 if denom != 0.0:
191 arg = nom/denom
192 if arg >= -1.0 and arg <= 1.0:
193 dra = math.acos(arg) * gammalib.rad2deg
194
195 # Return
196 return dra
197
198 def _moon_ra_exclusion(self, time):
199 """
200 Compute the half length of the Right Ascension interval to exclude due
201 to the Moon constraint
202
203 The Moon zenith angle constraint implies an interval in Right Ascension
204 that is to be excluded. This method computes half the length of this
205 interval.
206
207 Parameters
208 ----------
209 time : `~gammalib.GTime()`
210 Time for which the Moon exclusion is to be computed
211
212 Returns
213 -------
214 dra : float
215 Right Ascension interval half length (degrees)
216 """
217 # Get array geographic latitude and minimum Moon zenith angle in radians
218 geolat = self['geolat'].real() * gammalib.deg2rad
219 moonzenith = self['moonzenith'].real() * gammalib.deg2rad
220
221 # Compute Right Ascension and Declination of Moon in degrees and
222 # convert the Declination into radians
223 moon = gammalib.GSkyDir()
224 moon.moon(time)
225 moondec = moon.dec()
226
227 # Compute some sines and cosines
228 cos_moonzenith = math.cos(moonzenith)
229 sin_geolat = math.sin(geolat)
230 cos_geolat = math.cos(geolat)
231 sin_moondec = math.sin(moondec)
232 cos_moondec = math.cos(moondec)
233
234 # Compute Right Ascension difference when Moon is below the mimimum
235 # zenith angle in degrees
236 dra = 0.0
237 nom = cos_moonzenith - sin_geolat * sin_moondec
238 denom = cos_geolat * cos_moondec
239 if denom != 0.0:
240 arg = nom/denom
241 if arg >= -1.0 and arg <= 1.0:
242 dra = math.acos(arg) * gammalib.rad2deg
243
244 # Return
245 return dra
246
247 def _adjust_ra_interval(self, ra_start, ra_stop):
248 """
249 Adjust Right Ascension interval so that it overlaps with the [0,360[
250 interval
251
252 Parameters
253 ----------
254 ra_start : float
255 Start of interval to exclude (degrees)
256 ra_stop : float
257 Stop of interval to exclude (degrees)
258
259 Returns
260 -------
261 ra_start : float
262 Adjusted start of interval to exclude (degrees)
263 ra_stop : float
264 Adjusted stop of interval to exclude (degrees)
265 """
266 # Adjust interval if both boundaries are negative
267 while ra_start < 0.0 and ra_stop < 0.0:
268 ra_start += 360.0
269 ra_stop += 360.0
270
271 # Adjust interval if both boundaries are equal or larger than 360
272 # degrees
273 while ra_start >= 360.0 and ra_stop >= 360.0:
274 ra_start -= 360.0
275 ra_stop -= 360.0
276
277 # Return interval
278 return ra_start, ra_stop
279
280 def _exclude_ra_interval(self, hours, ra_start, ra_stop):
281 """
282 Exclude Right Ascension interval from array of hour angles
283
284 Parameters
285 ----------
286 hours : list of floats
287 Array of hours
288 ra_start : float
289 Start of interval to exclude (degrees)
290 ra_stop : float
291 Stop of interval to exclude (degrees)
292
293 Returns
294 -------
295 hours : list of floats
296 Array of hours with excluded Right Ascension interval
297 """
298 # Conversion from RA (degrees) to index
299 nhours = len(hours)
300 ra2inx = float(nhours)/360.0
301
302 # Adjust interval
303 ra_start, ra_stop = self._adjust_ra_interval(ra_start, ra_stop)
304
305 # Case 1: The RA interval is fully comprised within the [0,360]
306 # interval, hence we simply exclude this interval.
307 if ra_start >= 0.0 and ra_stop <= 360.0:
308 inx_start = int(ra_start * ra2inx + 0.5)
309 inx_stop = int(ra_stop * ra2inx + 0.5)
310 for i in range(inx_start,inx_stop):
311 hours[i] = 0.0
312
313 # Case 2: The RA interval is starting at negative RA. In that case
314 # there are two intervals to exclude:
315 # [ra_start+360,360] and [0,ra_stop]
316 elif ra_start < 0.0:
317 inx_start = int((ra_start+360.0) * ra2inx + 0.5)
318 inx_stop = int(ra_stop * ra2inx + 0.5)
319 for i in range(inx_start,nhours):
320 hours[i] = 0.0
321 for i in range(0,inx_stop):
322 hours[i] = 0.0
323
324 # Case 3: The RA interval is stopping at RA>360. In that case there
325 # are two intervals to exclude:
326 # [0,ra_stop-360] and [0,ra_start]
327 elif ra_stop > 360.0:
328 inx_start = int(ra_start * ra2inx + 0.5)
329 inx_stop = int((ra_stop-360.0) * ra2inx + 0.5)
330 for i in range(0,inx_stop):
331 hours[i] = 0.0
332 for i in range(inx_start,nhours):
333 hours[i] = 0.0
334
335 # Return hours
336 return hours
337
339 """
340 Compute hour angle weights
341
342 Computes an array that specifies the time during which a given hour
343 angle was observed during the observing time interval [tmin,tmax].
344 The hour angle runs from 0 to 360 degrees, array values are in units
345 of hours.
346
347 The method loops over the days in the time interval [tmin,tmax], with
348 time = tmin + i*86400 seconds. At each time step the position in Right
349 Ascension and Declination of the Sun is computed (the Sun's
350 Declination is in fact not used here).
351
352 An interval of [ra_sun-dra_sun, ra_sun+dra_sun] is assumed to be the
353 day. The method _sun_ra_exclusion() is used to compute dra_sun, which
354 is half of the length of the day in degrees (recall that 15 degrees
355 is one hour). The length of the day is computed using the sunzenith
356 constraint.
357 """
358 # Write header
359 self._log_header2(gammalib.NORMAL, 'Hour angle weights')
360
361 # Get MET time reference
362 tref = gammalib.GTimeReference(self['mjdref'].real(),'s','TT','LOCAL')
363
364 # Get time interval
365 tmin = self['tmin'].time(tref)
366 tmax = self['tmax'].time(tref)
367
368 # Initialise hour angle list and results
369 hour_angles = []
370 self._results = []
371
372 # Set number of hour angle bins and compute conversion factor and weight
373 nsteps = 1000
374 ra2inx = float(nsteps)/360.0 # Conversion from RA (deg) to index
375 weight = 24.0/float(nsteps) # Weight per hour angle step
376
377 # Initialise hour angle array and weight
378 hours = [0.0 for i in range(nsteps)]
379
380 # Initialise time and loop until the end time is reached
381 time = tmin
382 while time <= tmax:
383
384 # Initialise hours array for this time step
385 hours_time = [weight for i in range(nsteps)]
386
387 # Compute half the length of the exclusion intervals in degrees
388 sun_dra = self._sun_ra_exclusion(time)
389 moon_dra = self._moon_ra_exclusion(time)
390
391 # Compute Right Ascension and Declination of Sun in degrees and
392 # derive Sun exclusion interval
393 sun = gammalib.GSkyDir()
394 sun.sun(time)
395 sun_ra = sun.ra_deg()
396 sun_dec = sun.dec_deg()
397 sun_ra_start = sun_ra - sun_dra
398 sun_ra_stop = sun_ra + sun_dra
399
400 # Adjust interval
401 sun_ra_start, sun_ra_stop = self._adjust_ra_interval(sun_ra_start,
402 sun_ra_stop)
403
404 # Compute Right Ascension and Declination of Moon in degrees and
405 # derive Moon exclusion interval
406 moon = gammalib.GSkyDir()
407 moon.moon(time)
408 moon_ra = moon.ra_deg()
409 moon_dec = moon.dec_deg()
410 moon_ra_start = moon_ra - moon_dra
411 moon_ra_stop = moon_ra + moon_dra
412
413 # Adjust interval
414 moon_ra_start, moon_ra_stop = self._adjust_ra_interval(moon_ra_start,
415 moon_ra_stop)
416
417 # Compute Moon elongation and illumation fraction (Moon phase)
418 elongation = sun.dist_deg(moon)
419 fli = (1.0 - math.cos(elongation * gammalib.deg2rad))/2.0
420
421 # Exclude hours due to Sun constraint (this defines the night)
422 hours_time = self._exclude_ra_interval(hours_time, sun_ra_start,
423 sun_ra_stop)
424
425 # Exclude hours due to Moon constraint if the illumination fraction
426 # is equal to or above the maximim fraction of illumination
427 if fli >= self['maxfli'].real():
428 hours_time = self._exclude_ra_interval(hours_time, moon_ra_start,
429 moon_ra_stop)
430
431 # Add hours
432 dark_time = 0.0
433 for i in range(nsteps):
434 hours[i] += hours_time[i]
435 dark_time += hours_time[i]
436
437 # Set result record
438 result = {'time': time.copy(),
439 'sun_ra': sun_ra, 'sun_dec': sun_dec,
440 'moon_ra': moon_ra, 'moon_dec': moon_dec,
441 'elongation': elongation, 'fli': fli,
442 'sun_ra_start': sun_ra_start, 'sun_ra_stop': sun_ra_stop,
443 'moon_ra_start': moon_ra_start, 'moon_ra_stop': moon_ra_stop,
444 'dark_time' : dark_time}
445
446 # Append result record to results
447 self._results.append(result)
448
449 # Log results
450 logs = 'Sun=(%8.3f,%7.3f) Moon=(%8.3f,%7.3f) '\
451 'Sun_RA_excl=[%8.3f,%8.3f] Moon_RA_excl=[%8.3f,%8.3f] '\
452 'FLI=%4.2f Dark time=%5.2f h' % \
453 (sun_ra, sun_dec, moon_ra, moon_dec,
454 sun_ra_start, sun_ra_stop, moon_ra_start, moon_ra_stop,
455 fli, dark_time)
456 self._log_value(gammalib.VERBOSE, time.utc(), logs)
457
458 # Add seconds of one day and start with next day
459 time += 86400.0
460
461 # Build hour angle list
462 dh = 360.0/float(nsteps)
463 for i in range(nsteps):
464 hour_angle = {'angle': dh*float(i), 'hours': hours[i]}
465 hour_angles.append(hour_angle)
466
467 # Log hour angle weights
468 total = 0.0
469 for hour_angle in hour_angles:
470 total += hour_angle['hours']
471 self._log_value(gammalib.EXPLICIT, 'Observing time', str(total)+' h')
472 self._log_value(gammalib.EXPLICIT, 'Number of hour angle bins', len(hour_angles))
473
474 # Return hour angle weights
475 return hour_angles
476
478 """
479 Compute visibility cube
480
481 Compute visibility cube by displacing the zenith angle map for all
482 hour angles. The visibility cube contains the number of hours a given
483 celestial position is visible under a given zenith angle interval.
484 Summing over all zenith angle intervals specifies for how long a given
485 celestial position will be visible.
486 """
487 # Write header
488 self._log_header1(gammalib.TERSE, 'Compute visibility cube')
489
490 # Set visibility cube and zenith angle map dimensions and bin size
491 binsz = self['binsz'].real()
492 nx = int(360.0/binsz+0.5)
493 ny = int(180.0/binsz+0.5)
494 dx = 360.0/float(nx)
495 dy = 180.0/float(ny)
496
497 # Set zenith angle dimension and bin size
498 dz = self['dz'].real()
499 zmax = self['zmax'].real()
500 nz = int(zmax/dz+0.5)
501 dz = zmax/float(nz)
502
503 # Log dimensions
504 self._log_header2(gammalib.NORMAL, 'Visibility cube dimensions')
505 self._log_value(gammalib.NORMAL, 'Number of RA bins', nx)
506 self._log_value(gammalib.NORMAL, 'Number of Dec bins', ny)
507 self._log_value(gammalib.NORMAL, 'Number of zenith angles', nz)
508 self._log_value(gammalib.NORMAL, 'RA pixel size', str(dx)+' deg')
509 self._log_value(gammalib.NORMAL, 'Dec pixel size', str(dy)+' deg')
510 self._log_value(gammalib.NORMAL, 'Zenith angle bin size', str(dz)+' deg')
511 self._log_value(gammalib.NORMAL, 'Maximum zenith angle', str(zmax)+' deg')
512
513 # Compute zenith angle map
514 zmap = self._zenith_angle_map(nx,ny,dx,dy)
515
516 # Setup hour angle weights. They specify for how many hours a given
517 # hour angle is observed during the covered time period.
518 hour_angles = self._hour_angle_weight()
519
520 # Initialise visibility cube
521 self._cube = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-dx,dy,nx,ny,nz)
522
523 # Loop over all hour angle weights
524 for hour_angle in hour_angles:
525
526 # Compute temporal shift in degrees
527 shift = hour_angle['angle']
528
529 # Initialise shifted zenith angle map
530 zmap_shift = gammalib.GSkyMap('CAR','CEL',shift,0.0,-dx,dy,nx,ny)
531
532 # Merge zenith angle map into shifted map
533 zmap_shift += zmap
534
535 # Loop over all pixels of the shifted map and add the hours during
536 # which the shifted map occurs to the relevant zenith angle bin
537 for i, zenith in enumerate(zmap_shift):
538 iz = int(zenith/dz)
539 if iz < nz:
540 self._cube[i,iz] += hour_angle['hours']
541
542 # Return
543 return
544
545 def _save_results(self, outfile, clobber):
546 """
547 Save results in VISIBILITY FITS table
548
549 Parameters
550 ----------
551 outfile : str
552 Result FITS file name
553 clobber : bool
554 Overwrite existing file?
555 """
556 # Create FITS table columns
557 nrows = len(self._results)
558 time = gammalib.GFitsTableStringCol('Time', nrows, 20)
559 mjd = gammalib.GFitsTableDoubleCol('MJD', nrows)
560 sun_ra = gammalib.GFitsTableDoubleCol('RA_sun', nrows)
561 sun_dec = gammalib.GFitsTableDoubleCol('DEC_sun', nrows)
562 moon_ra = gammalib.GFitsTableDoubleCol('RA_moon', nrows)
563 moon_dec = gammalib.GFitsTableDoubleCol('DEC_moon', nrows)
564 sun_ra_start = gammalib.GFitsTableDoubleCol('RA_sun_start', nrows)
565 sun_ra_stop = gammalib.GFitsTableDoubleCol('RA_sun_stop', nrows)
566 moon_ra_start = gammalib.GFitsTableDoubleCol('RA_moon_start', nrows)
567 moon_ra_stop = gammalib.GFitsTableDoubleCol('RA_moon_stop', nrows)
568 elongation = gammalib.GFitsTableDoubleCol('Elongation', nrows)
569 fli = gammalib.GFitsTableDoubleCol('FLI', nrows)
570 dark_time = gammalib.GFitsTableDoubleCol('Darktime', nrows)
571
572 # Set units of table columns
573 mjd.unit('days')
574 sun_ra.unit('deg')
575 sun_dec.unit('deg')
576 moon_ra.unit('deg')
577 moon_dec.unit('deg')
578 sun_ra_start.unit('deg')
579 sun_ra_stop.unit('deg')
580 moon_ra_start.unit('deg')
581 moon_ra_stop.unit('deg')
582 elongation.unit('deg')
583 dark_time.unit('hours')
584
585 # File FITS table columns
586 for i, result in enumerate(self._results):
587 time[i] = result['time'].utc()
588 mjd[i] = result['time'].mjd()
589 sun_ra[i] = result['sun_ra']
590 sun_dec[i] = result['sun_dec']
591 moon_ra[i] = result['moon_ra']
592 moon_dec[i] = result['moon_dec']
593 sun_ra_start[i] = result['sun_ra_start']
594 sun_ra_stop[i] = result['sun_ra_stop']
595 moon_ra_start[i] = result['moon_ra_start']
596 moon_ra_stop[i] = result['moon_ra_stop']
597 elongation[i] = result['elongation']
598 fli[i] = result['fli']
599 dark_time[i] = result['dark_time']
600
601 # Initialise FITS Table with extension "VISIBILITY"
602 table = gammalib.GFitsBinTable(nrows)
603 table.extname('VISIBILITY')
604
605 # Add Header for compatibility with gammalib.GMWLSpectrum
606 table.card('INSTRUME', 'CTA', 'Name of Instrument')
607 table.card('TELESCOP', 'CTA', 'Name of Telescope')
608
609 # Append filled columns to fits table
610 table.append(time)
611 table.append(mjd)
612 table.append(sun_ra)
613 table.append(sun_dec)
614 table.append(moon_ra)
615 table.append(moon_dec)
616 table.append(sun_ra_start)
617 table.append(sun_ra_stop)
618 table.append(moon_ra_start)
619 table.append(moon_ra_stop)
620 table.append(elongation)
621 table.append(fli)
622 table.append(dark_time)
623
624 # Append table to result FITS file
625 fits = gammalib.GFits(outfile)
626 fits.append(table)
627 fits.save(clobber)
628
629 # Return
630 return
631
632
633 # Public methods
634 def process(self):
635 """
636 Process the script
637 """
638 # Get parameters
639 self._get_parameters()
640
641 # Compute visibility cube
642 self._visibility_cube()
643
644 # Optionally publish map
645 if self['publish'].boolean():
646 self.publish()
647
648 # Return
649 return
650
651 def save(self):
652 """
653 Save the visibility cube
654 """
655 # Write header
656 self._log_header1(gammalib.TERSE, 'Save visibility cube')
657
658 # Get outfile parameter
659 outfile = self['outfile'].filename()
660
661 # Log file name
662 self._log_value(gammalib.NORMAL, 'Visibility cube file', outfile.url())
663
664 # Save the visibility cube
665 self._cube.save(outfile, self['clobber'].boolean())
666
667 # Save the results in VISIBILITY table extension
668 self._save_results(outfile, self['clobber'].boolean())
669
670 # Stamp visibility cube
671 self._stamp(outfile)
672
673 # Return
674 return
675
676 def publish(self, name=''):
677 """
678 Publish visibility cube
679
680 Parameters
681 ----------
682 name : str, optional
683 Name of visibility cube
684 """
685 # Write header
686 self._log_header1(gammalib.TERSE, 'Publish visibility cube')
687
688 # Set default name is user name is empty
689 if not name:
690 user_name = self._name()
691 else:
692 user_name = name
693
694 # Log cube name
695 self._log_value(gammalib.TERSE, 'Visibility cube name', user_name)
696
697 # Publish cube
698 self._cube.publish(user_name)
699
700 # Return
701 return
702
703
704# ======================== #
705# Main routine entry point #
706# ======================== #
707if __name__ == '__main__':
708
709 # Create instance of application
710 app = csviscube(sys.argv)
711
712 # Execute application
713 app.execute()
_adjust_ra_interval(self, ra_start, ra_stop)
Definition csviscube.py:247
_save_results(self, outfile, clobber)
Definition csviscube.py:545
_exclude_ra_interval(self, hours, ra_start, ra_stop)
Definition csviscube.py:280
_zenith_angle_map(self, nx, ny, dx, dy)
Definition csviscube.py:81