1#!/usr/bin/env python3
  2
  3"""Meta data for regions and sampling types to be stored in stats tables and for
  4subsampling modes in general."""
  5
  6# TBD: the .field attribute is no longer used
  7
  8from datetime import timedelta
  9from enum import Enum
 10from collections import defaultdict
 11
 12from chart.common.traits import is_listlike
 13
 14Sampling = Enum('Sampling', (
 15    'AUTO ALL_POINTS FIT ORBITAL HOURLY HALF_HOURLY MIN_20 MIN_5 MIN_1 SEC_10 HALF_DAILY '
 16    'QUARTER_DAILY DAILY THREE_DAILY DAYTIME NIGHT DAILY_TOTALS'))
 17
 18#
 19# Fields below have following meaning:
 20# region - region name. Also serves two additional purposes:
 21#            - field name in a stats table, if there is one correspondong to this region
 22#            - value attribute in a corresponding html selector
 23#
 24# field - field name if corresponding stats table has a field with region number
 25#                (currently orbits only)
 26#
 27# option - use as label in html selector
 28#
 29# title - tooltip text for menu
 30#
 31# csv - use this option in CSV export dialog
 32#
 33# nominal_duration - region time for all time-based regions (i.e. except AUTO, ALL_POINTS and FIT)
 34#
 35
 36Sampling.AUTO.region = 'AUTO'
 37Sampling.AUTO.field = None
 38Sampling.AUTO.option = 'Auto'
 39Sampling.AUTO.csv = 0
 40Sampling.AUTO.title = 'All-points or stats averages according to data size and screen width'
 41Sampling.AUTO.singular = None
 42Sampling.AUTO.plural = None
 43Sampling.AUTO.adjective = None
 44Sampling.AUTO.nominal_duration = None
 45Sampling.AUTO.description = ('Produce an all-points plot with no subsampling if the data'
 46            ' volume permits, otherwise stats (orbital, hourly,...) subsampling will be used')
 47Sampling.AUTO.stats = None  # unknown
 48
 49Sampling.ALL_POINTS.region = 'AP'
 50Sampling.ALL_POINTS.field = None
 51Sampling.ALL_POINTS.option = 'All points'
 52Sampling.ALL_POINTS.csv = 1
 53Sampling.ALL_POINTS.title = 'All-points'
 54Sampling.ALL_POINTS.singular = None
 55Sampling.ALL_POINTS.plural = None
 56Sampling.ALL_POINTS.adjective = None
 57Sampling.ALL_POINTS.nominal_duration = None
 58Sampling.ALL_POINTS.description = ('Either produce the plot without subsampling or return an'
 59        ' error message if data volume is too big for database/browser to handle')
 60Sampling.ALL_POINTS.stats = False
 61
 62Sampling.FIT.region = 'FIT'
 63Sampling.FIT.field = None
 64Sampling.FIT.option = 'All points (subs)'
 65Sampling.FIT.csv = 0
 66Sampling.FIT.title = 'Subsample to fit plot width'
 67Sampling.FIT.singular = None
 68Sampling.FIT.plural = None
 69Sampling.FIT.adjective = None
 70Sampling.FIT.nominal_duration = None
 71Sampling.FIT.description = ('Forces the screen-wise subsampling of the data. The subsampling '
 72        'factor (modulus) is automatically chosen to have the number of points in the plot '
 73        ' that approximately matches the number of pixels across the screen')
 74Sampling.FIT.stats = False
 75
 76Sampling.ORBITAL.region = 'ORB'
 77Sampling.ORBITAL.field = None   #'ORBIT'
 78Sampling.ORBITAL.option = 'Orbital stats'
 79Sampling.ORBITAL.csv = 1
 80Sampling.ORBITAL.title = 'Orbital subsampling'
 81Sampling.ORBITAL.singular = 'orbit'
 82Sampling.ORBITAL.plural = 'orbits'
 83Sampling.ORBITAL.adjective = 'orbital'
 84Sampling.ORBITAL.nominal_duration = None
 85Sampling.ORBITAL.description = ('Subsampling is based on orbital statistics tables. Fast.'
 86        ' Can handle entire mission plots')
 87Sampling.ORBITAL.stats = True
 88Sampling.ORBITAL.region_num = 4
 89
 90Sampling.HOURLY.region = 'HOUR'
 91Sampling.HOURLY.field = None
 92Sampling.HOURLY.option = 'Hourly'
 93Sampling.HOURLY.csv = 1
 94Sampling.HOURLY.title = 'Hourly subsampling'
 95Sampling.HOURLY.singular = 'hour'
 96Sampling.HOURLY.plural = 'hours'
 97Sampling.HOURLY.adjective = 'hourly'
 98Sampling.HOURLY.nominal_duration = timedelta(hours=1)
 99Sampling.HOURLY.description = ('Hourly statistics plots.')
100Sampling.HOURLY.stats = True
101Sampling.HOURLY.region_num = 2
102
103Sampling.HALF_HOURLY.region = '30MIN'
104Sampling.HALF_HOURLY.field = None
105Sampling.HALF_HOURLY.option = 'Half-hourly'
106Sampling.HALF_HOURLY.csv = 1
107Sampling.HALF_HOURLY.title = 'Half-hourly subsampling'
108Sampling.HALF_HOURLY.singular = '30min'
109Sampling.HALF_HOURLY.plural = 'half-hours'
110Sampling.HALF_HOURLY.adjective = 'half-hourly'
111Sampling.HALF_HOURLY.nominal_duration = timedelta(minutes=30)
112Sampling.HALF_HOURLY.description = ('Half-hourly statistics plots')
113Sampling.HALF_HOURLY.stats = True
114
115Sampling.MIN_20.region = '20MIN'
116Sampling.MIN_20.field = None
117Sampling.MIN_20.option = '20m stats'
118Sampling.MIN_20.csv = 1
119Sampling.MIN_20.title = '20m stats'
120Sampling.MIN_20.singular = '20-min'
121Sampling.MIN_20.plural = '20-mins'
122Sampling.MIN_20.adjective = '20-mins'
123Sampling.MIN_20.nominal_duration = timedelta(minutes=20)
124Sampling.MIN_20.description = ('Mins_20 statistics plots')
125Sampling.MIN_20.stats = True
126Sampling.MIN_20.region_num = 3
127
128Sampling.MIN_5.region = '5MIN'
129Sampling.MIN_5.field = None
130Sampling.MIN_5.option = '5m stats'
131Sampling.MIN_5.csv = 5
132Sampling.MIN_5.title = '5-min subsampling'
133Sampling.MIN_5.singular = '5-min'
134Sampling.MIN_5.plural = '5-min'
135Sampling.MIN_5.adjective = '5-min'
136Sampling.MIN_5.nominal_duration = timedelta(minutes=5)
137Sampling.MIN_5.description = ('5-Min statistics plots')
138Sampling.MIN_5.stats = True
139Sampling.MIN_5.region_num = 7
140
141Sampling.MIN_1.region = '1MIN'
142Sampling.MIN_1.field = None
143Sampling.MIN_1.option = '1m stats'
144Sampling.MIN_1.csv = 1
145Sampling.MIN_1.title = '1-min subsampling'
146Sampling.MIN_1.singular = '1-min'
147Sampling.MIN_1.plural = '1-min'
148Sampling.MIN_1.adjective = '1-min'
149Sampling.MIN_1.nominal_duration = timedelta(minutes=1)
150Sampling.MIN_1.description = ('1-Min statistics plots')
151Sampling.MIN_1.stats = True
152Sampling.MIN_1.region_num = 5
153
154Sampling.SEC_10.region = '10SEC'
155Sampling.SEC_10.field = None
156Sampling.SEC_10.option = '10s stats'
157Sampling.SEC_10.csv = 1
158Sampling.SEC_10.title = '10-sec subsampling'
159Sampling.SEC_10.singular = '10-sec'
160Sampling.SEC_10.plural = '10-secs'
161Sampling.SEC_10.adjective = '10-secs'
162Sampling.SEC_10.nominal_duration = timedelta(seconds=10)
163Sampling.SEC_10.description = ('10-sec statistics plots')
164Sampling.SEC_10.stats = True
165Sampling.SEC_10.region_num = 6
166
167Sampling.QUARTER_DAILY.region = 'QUARTERDAY'
168Sampling.QUARTER_DAILY.field = None
169Sampling.QUARTER_DAILY.option = 'Quarter-day stats'
170Sampling.QUARTER_DAILY.csv = 1
171Sampling.QUARTER_DAILY.title = '6-hour subsampling'
172Sampling.QUARTER_DAILY.singular = 'quarter-day'
173Sampling.QUARTER_DAILY.plural = 'quarter-days'
174Sampling.QUARTER_DAILY.adjective = 'quarter daily'
175Sampling.QUARTER_DAILY.nominal_duration = timedelta(hours=6)
176Sampling.QUARTER_DAILY.description = ('6-hour daily statistics plots')
177Sampling.QUARTER_DAILY.stats = True
178Sampling.QUARTER_DAILY.region_num = 10
179
180# for JCS this is stats over all orbits starting in the 12 hour period
181Sampling.HALF_DAILY.region = 'HALFDAY'
182Sampling.HALF_DAILY.field = None
183Sampling.HALF_DAILY.option = 'Half-day stats'
184Sampling.HALF_DAILY.csv = 1
185Sampling.HALF_DAILY.title = 'Half-daily subsampling'
186Sampling.HALF_DAILY.singular = 'half-day'
187Sampling.HALF_DAILY.plural = 'half-days'
188Sampling.HALF_DAILY.adjective = 'half daily'
189Sampling.HALF_DAILY.nominal_duration = timedelta(hours=12)
190Sampling.HALF_DAILY.description = ('Half daily statistics plots')
191Sampling.HALF_DAILY.stats = True
192Sampling.HALF_DAILY.region_num = 8
193
194# for JCS this is stats over all orbits starting in the 24 hour period
195Sampling.DAILY.region = 'DAY'
196Sampling.DAILY.field = None
197Sampling.DAILY.option = 'Daily stats'
198Sampling.DAILY.csv = 1
199Sampling.DAILY.title = 'Daily subsampling'
200Sampling.DAILY.singular = 'day'
201Sampling.DAILY.plural = 'days'
202Sampling.DAILY.adjective = 'daily'
203Sampling.DAILY.nominal_duration = timedelta(days=1)
204Sampling.DAILY.description = ('Daily statistics plots')
205Sampling.DAILY.stats = True
206Sampling.DAILY.region_num = 1
207
208# 3-day stats are always calendar based and not taking orbit boundaries into account
209# There's no design reason for this it's just easier to compute - algorithm could be changed
210# for polar orbiters if needed
211Sampling.THREE_DAILY.region = 'THREEDAY'
212Sampling.THREE_DAILY.field = None
213Sampling.THREE_DAILY.option = '3-day stats'
214Sampling.THREE_DAILY.csv = 1
215Sampling.THREE_DAILY.title = '3-day subsampling'
216Sampling.THREE_DAILY.singular = '3-day'
217Sampling.THREE_DAILY.plural = '3-days'
218Sampling.THREE_DAILY.adjective = '3-daily'
219Sampling.THREE_DAILY.nominal_duration = timedelta(days=3)
220Sampling.THREE_DAILY.description = ('3-day statistics plots')
221Sampling.THREE_DAILY.stats = True
222Sampling.THREE_DAILY.region_num = 9
223
224Sampling.DAYTIME.region = 'DAYLIGHT'
225Sampling.DAYTIME.field = None
226Sampling.DAYTIME.option = 'Daylight'
227Sampling.DAYTIME.csv = 0
228Sampling.DAYTIME.title = 'Daylight averages'
229Sampling.DAYTIME.singular = 'daytime'
230Sampling.DAYTIME.plural = 'daytimes'
231Sampling.DAYTIME.adjective = 'daylight'
232Sampling.DAYTIME.nominal_duration = timedelta(hours=12)
233Sampling.DAYTIME.description = ('Daylight time averages plots. This is not strictly subsampling')
234Sampling.DAYTIME.stats = True
235
236Sampling.NIGHT.region = 'NIGHT'
237Sampling.NIGHT.field = None
238Sampling.NIGHT.option = 'Nighttime'
239Sampling.NIGHT.csv = 0
240Sampling.NIGHT.title = 'Nighttime averages'
241Sampling.NIGHT.singular = 'night'
242Sampling.NIGHT.plural = 'nights'
243Sampling.NIGHT.adjective = 'nightly'
244Sampling.NIGHT.nominal_duration = timedelta(hours=12)
245Sampling.NIGHT.description = ('Nighttime averages plots. This is not strictly subsampling')
246Sampling.NIGHT.stats = True
247
248Sampling.DAILY_TOTALS.region = 'DAYTOTAL'
249Sampling.DAILY_TOTALS.field = None
250Sampling.DAILY_TOTALS.option = 'Daily totals'
251Sampling.DAILY_TOTALS.csv = 0
252Sampling.DAILY_TOTALS.title = 'Daily totals'
253Sampling.DAILY_TOTALS.singular = 'daytotal'
254Sampling.DAILY_TOTALS.plural = 'daytotals'
255Sampling.DAILY_TOTALS.adjective = 'daily totals'
256Sampling.DAILY_TOTALS.nominal_duration = None  # timedelta(days=1)
257Sampling.DAILY_TOTALS.description = ('Daily totals statistics. This is not strictly subsampling')
258Sampling.DAILY_TOTALS.stats = False
259
260
261def region_duration(region, sid=None):
262    """Return the nominal duration of a region."""
263    if region is Sampling.ORBITAL:
264        if sid is not None and sid.satellite is not None:
265            return sid.satellite.orbit_duration
266
267        else:
268            return None
269            #raise ValueError('No orbit duration for {sid}'.format(sid=sid))
270
271    else:
272        return region.nominal_duration
273
274class NoSuchSampling(Exception):
275    """Could not find a sampling type with requested name."""
276    pass
277
278def sampling_from_name(name):
279    """Return sampling type where the .region field matches `name`.
280    Note, region should perhaps be renamed to "name"."""
281    from chart.project import SID
282    if not isinstance(name, str):
283        raise NoSuchSampling('Can only search for sampling by string name not {t}'.format(
284            t=name))
285
286    for region in SID.sampling_options:
287        if region.region.lower() == name.lower() or region.name.lower() == name.lower():
288            return region
289
290    raise NoSuchSampling('Could not find sampling type {t}'.format(t=name))