1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6from __future__ import absolute_import, print_function
8import hashlib
9import numpy as num
10from os import urandom
11from base64 import urlsafe_b64encode
12from collections import defaultdict
14from pyrocko import util
15from pyrocko.guts import Object, String, Timestamp, Float, Int, Unicode, \
16 Tuple, List, StringChoice, Any
17from pyrocko.model import Content
18from pyrocko.response import FrequencyResponse, MultiplyResponse, \
19 IntegrationResponse, DifferentiationResponse, simplify_responses, \
20 FrequencyResponseCheckpoint
22from .error import ConversionError
25guts_prefix = 'squirrel'
27separator = '\t'
29g_content_kinds = [
30 'undefined',
31 'waveform',
32 'station',
33 'channel',
34 'response',
35 'event',
36 'waveform_promise']
39g_content_kind_ids = (
40 UNDEFINED, WAVEFORM, STATION, CHANNEL, RESPONSE, EVENT,
41 WAVEFORM_PROMISE) = range(len(g_content_kinds))
43g_tmin, g_tmax = util.get_working_system_time_range()[:2]
46try:
47 g_tmin_queries = max(g_tmin, util.str_to_time_fillup('1900-01-01'))
48except Exception:
49 g_tmin_queries = g_tmin
52def to_kind(kind_id):
53 return g_content_kinds[kind_id]
56def to_kinds(kind_ids):
57 return [g_content_kinds[kind_id] for kind_id in kind_ids]
60def to_kind_id(kind):
61 return g_content_kinds.index(kind)
64def to_kind_ids(kinds):
65 return [g_content_kinds.index(kind) for kind in kinds]
68g_kind_mask_all = 0xff
71def to_kind_mask(kinds):
72 if kinds:
73 return sum(1 << kind_id for kind_id in to_kind_ids(kinds))
74 else:
75 return g_kind_mask_all
78def str_or_none(x):
79 if x is None:
80 return None
81 else:
82 return str(x)
85def float_or_none(x):
86 if x is None:
87 return None
88 else:
89 return float(x)
92def int_or_none(x):
93 if x is None:
94 return None
95 else:
96 return int(x)
99def int_or_g_tmin(x):
100 if x is None:
101 return g_tmin
102 else:
103 return int(x)
106def int_or_g_tmax(x):
107 if x is None:
108 return g_tmax
109 else:
110 return int(x)
113def tmin_or_none(x):
114 if x == g_tmin:
115 return None
116 else:
117 return x
120def tmax_or_none(x):
121 if x == g_tmax:
122 return None
123 else:
124 return x
127def time_or_none_to_str(x):
128 if x is None:
129 return '...'.ljust(17)
130 else:
131 return util.time_to_str(x)
134def codes_to_str_abbreviated(codes, indent=' '):
135 codes = ['.'.join(x) for x in codes]
137 if len(codes) > 20:
138 scodes = '\n' + util.ewrap(codes[:10], indent=indent) \
139 + '\n%s[%i more]\n' % (indent, len(codes) - 20) \
140 + util.ewrap(codes[-10:], indent=' ')
141 else:
142 scodes = '\n' + util.ewrap(codes, indent=indent) \
143 if codes else '<none>'
145 return scodes
148g_offset_time_unit_inv = 1000000000
149g_offset_time_unit = 1.0 / g_offset_time_unit_inv
152def tsplit(t):
153 if t is None:
154 return None, 0.0
156 t = util.to_time_float(t)
157 if type(t) is float:
158 t = round(t, 5)
159 else:
160 t = round(t, 9)
162 seconds = num.floor(t)
163 offset = t - seconds
164 return int(seconds), int(round(offset * g_offset_time_unit_inv))
167def tjoin(seconds, offset):
168 if seconds is None:
169 return None
171 return util.to_time_float(seconds) \
172 + util.to_time_float(offset*g_offset_time_unit)
175tscale_min = 1
176tscale_max = 365 * 24 * 3600 # last edge is one above
177tscale_logbase = 20
179tscale_edges = [tscale_min]
180while True:
181 tscale_edges.append(tscale_edges[-1]*tscale_logbase)
182 if tscale_edges[-1] >= tscale_max:
183 break
186tscale_edges = num.array(tscale_edges)
189def tscale_to_kscale(tscale):
191 # 0 <= x < tscale_edges[1]: 0
192 # tscale_edges[1] <= x < tscale_edges[2]: 1
193 # ...
194 # tscale_edges[len(tscale_edges)-1] <= x: len(tscale_edges)
196 return int(num.searchsorted(tscale_edges, tscale))
199class WaveformPromise(Content):
200 '''
201 Information about a waveform potentially available at a remote site.
202 '''
204 agency = String.T(default='', help='Agency code (2-5)')
205 network = String.T(default='', help='Deployment/network code (1-8)')
206 station = String.T(default='', help='Station code (1-5)')
207 location = String.T(default='', help='Location code (0-2)')
208 channel = String.T(default='', help='Channel code (3)')
209 extra = String.T(default='', help='Extra/custom code')
211 tmin = Timestamp.T()
212 tmax = Timestamp.T()
214 deltat = Float.T(optional=True)
216 source_hash = String.T()
218 @property
219 def codes(self):
220 return (
221 self.agency, self.network, self.station, self.location,
222 self.channel, self.extra)
224 @property
225 def time_span(self):
226 return (self.tmin, self.tmax)
229class InvalidWaveform(Exception):
230 pass
233class WaveformOrder(Object):
234 source_id = String.T()
235 codes = Tuple.T(None, String.T())
236 deltat = Float.T()
237 tmin = Timestamp.T()
238 tmax = Timestamp.T()
239 gaps = List.T(Tuple.T(2, Timestamp.T()))
241 @property
242 def client(self):
243 return self.source_id.split(':')[1]
245 def describe(self, site='?'):
246 return '%s:%s %s [%s - %s]' % (
247 self.client, site, '.'.join(self.codes),
248 util.time_to_str(self.tmin), util.time_to_str(self.tmax))
250 def validate(self, tr):
251 if tr.ydata.size == 0:
252 raise InvalidWaveform(
253 'waveform with zero data samples')
255 if tr.deltat != self.deltat:
256 raise InvalidWaveform(
257 'incorrect sampling interval - waveform: %g s, '
258 'meta-data: %g s' % (
259 tr.deltat, self.deltat))
261 if not num.all(num.isfinite(tr.ydata)):
262 raise InvalidWaveform('waveform has NaN values')
265def order_summary(orders):
266 codes = sorted(set(order.codes[1:-1] for order in orders))
267 if len(codes) >= 2:
268 return '%i orders, %s - %s' % (
269 len(orders),
270 '.'.join(codes[0]),
271 '.'.join(codes[-1]))
273 else:
274 return '%i orders, %s' % (
275 len(orders),
276 '.'.join(codes[0]))
279class Station(Content):
280 '''
281 A seismic station.
282 '''
284 agency = String.T(default='', help='Agency code (2-5)')
285 network = String.T(default='', help='Deployment/network code (1-8)')
286 station = String.T(default='', help='Station code (1-5)')
287 location = String.T(default='', optional=True, help='Location code (0-2)')
289 tmin = Timestamp.T(optional=True)
290 tmax = Timestamp.T(optional=True)
292 lat = Float.T()
293 lon = Float.T()
294 elevation = Float.T(optional=True)
295 depth = Float.T(optional=True)
297 description = Unicode.T(optional=True)
299 @property
300 def codes(self):
301 return (
302 self.agency, self.network, self.station,
303 self.location if self.location is not None else '*')
305 @property
306 def time_span(self):
307 return (self.tmin, self.tmax)
309 def get_pyrocko_station(self):
310 from pyrocko import model
311 return model.Station(
312 network=self.network,
313 station=self.station,
314 location=self.location if self.location is not None else '*',
315 lat=self.lat,
316 lon=self.lon,
317 elevation=self.elevation,
318 depth=self.depth)
320 def _get_pyrocko_station_args(self):
321 return (
322 '*',
323 self.network,
324 self.station,
325 self.location if self.location is not None else '*',
326 self.lat,
327 self.lon,
328 self.elevation,
329 self.depth)
332class Channel(Content):
333 '''
334 A channel of a seismic station.
335 '''
337 agency = String.T(default='', help='Agency code (2-5)')
338 network = String.T(default='', help='Deployment/network code (1-8)')
339 station = String.T(default='', help='Station code (1-5)')
340 location = String.T(default='', help='Location code (0-2)')
341 channel = String.T(default='', help='Channel code (3)')
342 extra = String.T(default='', help='Extra/custom code')
344 tmin = Timestamp.T(optional=True)
345 tmax = Timestamp.T(optional=True)
347 lat = Float.T()
348 lon = Float.T()
349 elevation = Float.T(optional=True)
350 depth = Float.T(optional=True)
352 dip = Float.T(optional=True)
353 azimuth = Float.T(optional=True)
354 deltat = Float.T(optional=True)
356 @property
357 def codes(self):
358 return (
359 self.agency, self.network, self.station, self.location,
360 self.channel, self.extra)
362 def set_codes(
363 self, agency=None, network=None, station=None, location=None,
364 channel=None, extra=None):
366 if agency is not None:
367 self.agency = agency
368 if network is not None:
369 self.network = network
370 if station is not None:
371 self.station = station
372 if location is not None:
373 self.location = location
374 if channel is not None:
375 self.channel = channel
376 if extra is not None:
377 self.extra = extra
379 @property
380 def time_span(self):
381 return (self.tmin, self.tmax)
383 def get_pyrocko_channel(self):
384 from pyrocko import model
385 return model.Channel(
386 name=self.channel,
387 azimuth=self.azimuth,
388 dip=self.dip)
390 def _get_pyrocko_station_args(self):
391 return (
392 self.channel,
393 self.network,
394 self.station,
395 self.location,
396 self.lat,
397 self.lon,
398 self.elevation,
399 self.depth)
401 def _get_pyrocko_channel_args(self):
402 return (
403 '*',
404 self.channel,
405 self.azimuth,
406 self.dip)
408 def _get_sensor_args(self):
409 return (
410 self.agency,
411 self.network,
412 self.station,
413 self.location,
414 self.channel[:-1] + '?',
415 self.extra,
416 self.lat,
417 self.lon,
418 self.elevation,
419 self.depth,
420 self.deltat,
421 self.tmin,
422 self.tmax)
425class Sensor(Channel):
426 '''
427 Representation of a channel group.
428 '''
430 def grouping(self, channel):
431 return channel._get_sensor_args()
433 @classmethod
434 def from_channels(cls, channels):
435 groups = defaultdict(list)
436 for channel in channels:
437 groups[channel._get_sensor_args()].append(channel)
439 return [cls(
440 agency=args[0],
441 network=args[1],
442 station=args[2],
443 location=args[3],
444 channel=args[4],
445 extra=args[5],
446 lat=args[6],
447 lon=args[7],
448 elevation=args[8],
449 depth=args[9],
450 deltat=args[10],
451 tmin=args[11],
452 tmax=args[12])
453 for args, _ in groups.items()]
456observational_quantities = [
457 'acceleration', 'velocity', 'displacement', 'pressure', 'rotation',
458 'temperature']
461technical_quantities = [
462 'voltage', 'counts']
465class QuantityType(StringChoice):
466 choices = observational_quantities + technical_quantities
469class ResponseStage(Object):
470 input_quantity = QuantityType.T(optional=True)
471 input_sample_rate = Float.T(optional=True)
472 output_quantity = QuantityType.T(optional=True)
473 output_sample_rate = Float.T(optional=True)
474 elements = List.T(FrequencyResponse.T())
475 log = List.T(Tuple.T(3, String.T()))
477 @property
478 def stage_type(self):
479 if self.input_quantity in observational_quantities \
480 and self.output_quantity in observational_quantities:
481 return 'conversion'
483 if self.input_quantity in observational_quantities \
484 and self.output_quantity == 'voltage':
485 return 'sensor'
487 elif self.input_quantity == 'voltage' \
488 and self.output_quantity == 'voltage':
489 return 'electronics'
491 elif self.input_quantity == 'voltage' \
492 and self.output_quantity == 'counts':
493 return 'digitizer'
495 elif self.input_quantity == 'counts' \
496 and self.output_quantity == 'counts' \
497 and self.input_sample_rate != self.output_sample_rate:
498 return 'decimation'
500 elif self.input_quantity in observational_quantities \
501 and self.output_quantity == 'counts':
502 return 'combination'
504 else:
505 return 'unknown'
507 @property
508 def summary(self):
509 irate = self.input_sample_rate
510 orate = self.output_sample_rate
511 factor = None
512 if irate and orate:
513 factor = irate / orate
514 return 'ResponseStage, ' + (
515 '%s%s => %s%s%s' % (
516 self.input_quantity or '?',
517 ' @ %g Hz' % irate if irate else '',
518 self.output_quantity or '?',
519 ' @ %g Hz' % orate if orate else '',
520 ' :%g' % factor if factor else '')
521 )
523 def get_effective(self):
524 return MultiplyResponse(responses=list(self.elements))
527D = 'displacement'
528V = 'velocity'
529A = 'acceleration'
531g_converters = {
532 (V, D): IntegrationResponse(1),
533 (A, D): IntegrationResponse(2),
534 (D, V): DifferentiationResponse(1),
535 (A, V): IntegrationResponse(1),
536 (D, A): DifferentiationResponse(2),
537 (V, A): DifferentiationResponse(1)}
540def response_converters(input_quantity, output_quantity):
541 if input_quantity is None or input_quantity == output_quantity:
542 return []
544 if output_quantity is None:
545 raise ConversionError('Unspecified target quantity.')
547 try:
548 return [g_converters[input_quantity, output_quantity]]
550 except KeyError:
551 raise ConversionError('No rule to convert from "%s" to "%s".' % (
552 input_quantity, output_quantity))
555class Response(Content):
556 '''
557 The instrument response of a seismic station channel.
558 '''
560 agency = String.T(default='', help='Agency code (2-5)')
561 network = String.T(default='', help='Deployment/network code (1-8)')
562 station = String.T(default='', help='Station code (1-5)')
563 location = String.T(default='', help='Location code (0-2)')
564 channel = String.T(default='', help='Channel code (3)')
565 extra = String.T(default='', help='Extra/custom code')
567 tmin = Timestamp.T(optional=True)
568 tmax = Timestamp.T(optional=True)
570 stages = List.T(ResponseStage.T())
571 checkpoints = List.T(FrequencyResponseCheckpoint.T())
573 deltat = Float.T(optional=True)
574 log = List.T(Tuple.T(3, String.T()))
576 @property
577 def codes(self):
578 return (
579 self.agency, self.network, self.station, self.location,
580 self.channel, self.extra)
582 @property
583 def time_span(self):
584 return (self.tmin, self.tmax)
586 @property
587 def nstages(self):
588 return len(self.stages)
590 @property
591 def input_quantity(self):
592 return self.stages[0].input_quantity if self.stages else None
594 @property
595 def output_quantity(self):
596 return self.stages[-1].input_quantity if self.stages else None
598 @property
599 def output_sample_rate(self):
600 return self.stages[-1].output_sample_rate if self.stages else None
602 @property
603 def stages_summary(self):
604 def grouped(xs):
605 xs = list(xs)
606 g = []
607 for i in range(len(xs)):
608 g.append(xs[i])
609 if i+1 < len(xs) and xs[i+1] != xs[i]:
610 yield g
611 g = []
613 if g:
614 yield g
616 return '+'.join(
617 '%s%s' % (g[0], '(%i)' % len(g) if len(g) > 1 else '')
618 for g in grouped(stage.stage_type for stage in self.stages))
620 @property
621 def summary(self):
622 orate = self.output_sample_rate
623 return Content.summary.fget(self) + ', ' + ', '.join((
624 '%s => %s' % (
625 self.input_quantity or '?', self.output_quantity or '?')
626 + (' @ %g Hz' % orate if orate else ''),
627 self.stages_summary,
628 ))
630 def get_effective(self, input_quantity=None):
631 elements = response_converters(input_quantity, self.input_quantity)
633 elements.extend(
634 stage.get_effective() for stage in self.stages)
636 return MultiplyResponse(responses=simplify_responses(elements))
639class Event(Content):
640 '''
641 A seismic event.
642 '''
644 name = String.T(optional=True)
645 time = Timestamp.T()
646 duration = Float.T(optional=True)
648 lat = Float.T()
649 lon = Float.T()
650 depth = Float.T(optional=True)
652 magnitude = Float.T(optional=True)
654 def get_pyrocko_event(self):
655 from pyrocko import model
656 model.Event(
657 name=self.name,
658 time=self.time,
659 lat=self.lat,
660 lon=self.lon,
661 depth=self.depth,
662 magnitude=self.magnitude,
663 duration=self.duration)
665 @property
666 def time_span(self):
667 return (self.time, self.time)
670def ehash(s):
671 return hashlib.sha1(s.encode('utf8')).hexdigest()
674def random_name(n=8):
675 return urlsafe_b64encode(urandom(n)).rstrip(b'=').decode('ascii')
678class Nut(Object):
679 '''
680 Index entry referencing an elementary piece of content.
682 So-called *nuts* are used in Pyrocko's Squirrel framework to hold common
683 meta-information about individual pieces of waveforms, stations, channels,
684 etc. together with the information where it was found or generated.
685 '''
687 file_path = String.T(optional=True)
688 file_format = String.T(optional=True)
689 file_mtime = Timestamp.T(optional=True)
690 file_size = Int.T(optional=True)
692 file_segment = Int.T(optional=True)
693 file_element = Int.T(optional=True)
695 kind_id = Int.T()
696 codes = String.T()
698 tmin_seconds = Timestamp.T()
699 tmin_offset = Int.T(default=0, optional=True)
700 tmax_seconds = Timestamp.T()
701 tmax_offset = Int.T(default=0, optional=True)
703 deltat = Float.T(default=0.0)
705 content = Content.T(optional=True)
707 content_in_db = False
709 def __init__(
710 self,
711 file_path=None,
712 file_format=None,
713 file_mtime=None,
714 file_size=None,
715 file_segment=None,
716 file_element=None,
717 kind_id=0,
718 codes='',
719 tmin_seconds=None,
720 tmin_offset=0,
721 tmax_seconds=None,
722 tmax_offset=0,
723 deltat=None,
724 content=None,
725 tmin=None,
726 tmax=None,
727 values_nocheck=None):
729 if values_nocheck is not None:
730 (self.file_path, self.file_format, self.file_mtime,
731 self.file_size,
732 self.file_segment, self.file_element,
733 self.kind_id, self.codes,
734 self.tmin_seconds, self.tmin_offset,
735 self.tmax_seconds, self.tmax_offset,
736 self.deltat) = values_nocheck
738 self.content = None
739 else:
740 if tmin is not None:
741 tmin_seconds, tmin_offset = tsplit(tmin)
743 if tmax is not None:
744 tmax_seconds, tmax_offset = tsplit(tmax)
746 self.kind_id = int(kind_id)
747 self.codes = str(codes)
748 self.tmin_seconds = int_or_g_tmin(tmin_seconds)
749 self.tmin_offset = int(tmin_offset)
750 self.tmax_seconds = int_or_g_tmax(tmax_seconds)
751 self.tmax_offset = int(tmax_offset)
752 self.deltat = float_or_none(deltat)
753 self.file_path = str_or_none(file_path)
754 self.file_segment = int_or_none(file_segment)
755 self.file_element = int_or_none(file_element)
756 self.file_format = str_or_none(file_format)
757 self.file_mtime = float_or_none(file_mtime)
758 self.file_size = int_or_none(file_size)
759 self.content = content
761 Object.__init__(self, init_props=False)
763 def __eq__(self, other):
764 return (isinstance(other, Nut) and
765 self.equality_values == other.equality_values)
767 def hash(self):
768 return ehash(','.join(str(x) for x in self.key))
770 def __ne__(self, other):
771 return not (self == other)
773 def get_io_backend(self):
774 from . import io
775 return io.get_backend(self.file_format)
777 def file_modified(self):
778 return self.get_io_backend().get_stats(self.file_path) \
779 != (self.file_mtime, self.file_size)
781 @property
782 def dkey(self):
783 return (self.kind_id, self.tmin_seconds, self.tmin_offset, self.codes)
785 @property
786 def key(self):
787 return (
788 self.file_path,
789 self.file_segment,
790 self.file_element,
791 self.file_mtime)
793 @property
794 def equality_values(self):
795 return (
796 self.file_segment, self.file_element,
797 self.kind_id, self.codes,
798 self.tmin_seconds, self.tmin_offset,
799 self.tmax_seconds, self.tmax_offset, self.deltat)
801 @property
802 def tmin(self):
803 return tjoin(self.tmin_seconds, self.tmin_offset)
805 @property
806 def tmax(self):
807 return tjoin(self.tmax_seconds, self.tmax_offset)
809 @property
810 def kscale(self):
811 if self.tmin_seconds is None or self.tmax_seconds is None:
812 return 0
813 return tscale_to_kscale(self.tmax_seconds - self.tmin_seconds)
815 @property
816 def waveform_kwargs(self):
817 agency, network, station, location, channel, extra = \
818 self.codes.split(separator)
820 return dict(
821 agency=agency,
822 network=network,
823 station=station,
824 location=location,
825 channel=channel,
826 extra=extra,
827 tmin=self.tmin,
828 tmax=self.tmax,
829 deltat=self.deltat)
831 @property
832 def waveform_promise_kwargs(self):
833 agency, network, station, location, channel, extra = \
834 self.codes.split(separator)
836 return dict(
837 agency=agency,
838 network=network,
839 station=station,
840 location=location,
841 channel=channel,
842 extra=extra,
843 tmin=self.tmin,
844 tmax=self.tmax,
845 deltat=self.deltat)
847 @property
848 def station_kwargs(self):
849 agency, network, station, location = self.codes.split(separator)
850 return dict(
851 agency=agency,
852 network=network,
853 station=station,
854 location=location if location != '*' else None,
855 tmin=tmin_or_none(self.tmin),
856 tmax=tmax_or_none(self.tmax))
858 @property
859 def channel_kwargs(self):
860 agency, network, station, location, channel, extra \
861 = self.codes.split(separator)
863 return dict(
864 agency=agency,
865 network=network,
866 station=station,
867 location=location,
868 channel=channel,
869 extra=extra,
870 tmin=tmin_or_none(self.tmin),
871 tmax=tmax_or_none(self.tmax),
872 deltat=self.deltat)
874 @property
875 def response_kwargs(self):
876 agency, network, station, location, channel, extra \
877 = self.codes.split(separator)
879 return dict(
880 agency=agency,
881 network=network,
882 station=station,
883 location=location,
884 channel=channel,
885 extra=extra,
886 tmin=tmin_or_none(self.tmin),
887 tmax=tmax_or_none(self.tmax),
888 deltat=self.deltat)
890 @property
891 def event_kwargs(self):
892 return dict(
893 name=self.codes,
894 time=self.tmin,
895 duration=(self.tmax - self.tmin) or None)
897 @property
898 def trace_kwargs(self):
899 agency, network, station, location, channel, extra = \
900 self.codes.split(separator)
902 return dict(
903 network=network,
904 station=station,
905 location=location,
906 channel=channel,
907 extra=extra,
908 tmin=self.tmin,
909 tmax=self.tmax-self.deltat,
910 deltat=self.deltat)
912 @property
913 def dummy_trace(self):
914 return DummyTrace(self)
916 @property
917 def codes_tuple(self):
918 return tuple(self.codes.split(separator))
920 @property
921 def summary(self):
922 if self.tmin == self.tmax:
923 ts = util.time_to_str(self.tmin)
924 else:
925 ts = '%s - %s' % (
926 util.time_to_str(self.tmin),
927 util.time_to_str(self.tmax))
929 return ' '.join((
930 ('%s,' % to_kind(self.kind_id)).ljust(9),
931 ('%s,' % '.'.join(self.codes.split(separator))).ljust(18),
932 ts))
935def make_waveform_nut(
936 agency='', network='', station='', location='', channel='', extra='',
937 **kwargs):
939 codes = separator.join(
940 (agency, network, station, location, channel, extra))
942 return Nut(
943 kind_id=WAVEFORM,
944 codes=codes,
945 **kwargs)
948def make_waveform_promise_nut(
949 agency='', network='', station='', location='', channel='', extra='',
950 **kwargs):
952 codes = separator.join(
953 (agency, network, station, location, channel, extra))
955 return Nut(
956 kind_id=WAVEFORM_PROMISE,
957 codes=codes,
958 **kwargs)
961def make_station_nut(
962 agency='', network='', station='', location='', **kwargs):
964 codes = separator.join((agency, network, station, location))
966 return Nut(
967 kind_id=STATION,
968 codes=codes,
969 **kwargs)
972def make_channel_nut(
973 agency='', network='', station='', location='', channel='', extra='',
974 **kwargs):
976 codes = separator.join(
977 (agency, network, station, location, channel, extra))
979 return Nut(
980 kind_id=CHANNEL,
981 codes=codes,
982 **kwargs)
985def make_response_nut(
986 agency='', network='', station='', location='', channel='', extra='',
987 **kwargs):
989 codes = separator.join(
990 (agency, network, station, location, channel, extra))
992 return Nut(
993 kind_id=RESPONSE,
994 codes=codes,
995 **kwargs)
998def make_event_nut(name='', **kwargs):
1000 codes = name
1002 return Nut(
1003 kind_id=EVENT, codes=codes,
1004 **kwargs)
1007def group_channels(nuts):
1008 by_ansl = {}
1009 for nut in nuts:
1010 if nut.kind_id != CHANNEL:
1011 continue
1013 ansl = nut.codes[:4]
1015 if ansl not in by_ansl:
1016 by_ansl[ansl] = {}
1018 group = by_ansl[ansl]
1020 k = nut.codes[4][:-1], nut.deltat, nut.tmin, nut.tmax
1022 if k not in group:
1023 group[k] = set()
1025 group.add(nut.codes[4])
1027 return by_ansl
1030class DummyTrace(object):
1032 def __init__(self, nut):
1033 self.nut = nut
1034 self._codes = None
1035 self.meta = {}
1037 @property
1038 def tmin(self):
1039 return self.nut.tmin
1041 @property
1042 def tmax(self):
1043 return self.nut.tmax
1045 @property
1046 def deltat(self):
1047 return self.nut.deltat
1049 @property
1050 def codes(self):
1051 if self._codes is None:
1052 self._codes = self.nut.codes_tuple
1054 return self._codes
1056 @property
1057 def nslc_id(self):
1058 return self.codes[1:5]
1060 @property
1061 def agency(self):
1062 return self.codes[0]
1064 @property
1065 def network(self):
1066 return self.codes[1]
1068 @property
1069 def station(self):
1070 return self.codes[2]
1072 @property
1073 def location(self):
1074 return self.codes[3]
1076 @property
1077 def channel(self):
1078 return self.codes[4]
1080 @property
1081 def extra(self):
1082 return self.codes[5]
1084 def overlaps(self, tmin, tmax):
1085 return not (tmax < self.nut.tmin or self.nut.tmax < tmin)
1088class Coverage(Object):
1089 kind_id = Int.T()
1090 pattern = String.T()
1091 codes = String.T()
1092 deltat = Float.T(optional=True)
1093 tmin = Timestamp.T(optional=True)
1094 tmax = Timestamp.T(optional=True)
1095 changes = List.T(Tuple.T(2, Any.T()))
1097 @classmethod
1098 def from_values(cls, args):
1099 pattern, codes, deltat, tmin, tmax, changes, kind_id = args
1100 return cls(
1101 kind_id=kind_id,
1102 pattern=pattern,
1103 codes=codes,
1104 deltat=deltat,
1105 tmin=tmin,
1106 tmax=tmax,
1107 changes=changes)
1109 @property
1110 def summary(self):
1111 ts = '%s - %s,' % (
1112 util.time_to_str(self.tmin),
1113 util.time_to_str(self.tmax))
1115 srate = self.sample_rate
1117 return ' '.join((
1118 ('%s,' % to_kind(self.kind_id)).ljust(9),
1119 ('%s,' % '.'.join(self.codes.split(separator))).ljust(18),
1120 ts,
1121 '%10.3g,' % srate if srate else '',
1122 '%4i' % len(self.changes)))
1124 @property
1125 def sample_rate(self):
1126 if self.deltat is None:
1127 return None
1128 elif self.deltat == 0.0:
1129 return 0.0
1130 else:
1131 return 1.0 / self.deltat
1133 @property
1134 def labels(self):
1135 srate = self.sample_rate
1136 return (
1137 ('%s' % '.'.join(self.codes.split(separator))),
1138 '%.3g' % srate if srate else '')
1141__all__ = [
1142 'separator',
1143 'to_kind',
1144 'to_kinds',
1145 'to_kind_id',
1146 'to_kind_ids',
1147 'Content',
1148 'WaveformPromise',
1149 'Station',
1150 'Channel',
1151 'Nut',
1152 'Coverage',
1153]