1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5'''
6Module to read and write GSE2.0, GSE2.1, and IMS1.0 files.
7'''
8from __future__ import print_function, absolute_import
10import sys
11import re
12import logging
14from . import util
15from .io_common import FileLoadError, FileSaveError
16from pyrocko.guts import (
17 Object, String, StringChoice, Timestamp, Int, Float, List, Bool, Complex,
18 ValidationError)
20try:
21 range = xrange
22except NameError:
23 pass
25logger = logging.getLogger('pyrocko.io.ims')
27km = 1000.
28nm_per_s = 1.0e-9
30g_versions = ('GSE2.0', 'GSE2.1', 'IMS1.0')
31g_dialects = ('NOR_NDC', 'USA_DMC')
34class SerializeError(Exception):
35 '''
36 Raised when serialization of an IMS/GSE2 object fails.
37 '''
38 pass
41class DeserializeError(Exception):
42 '''
43 Raised when deserialization of an IMS/GSE2 object fails.
44 '''
46 def __init__(self, *args, **kwargs):
47 Exception.__init__(self, *args)
48 self._line_number = None
49 self._line = None
50 self._position = kwargs.get('position', None)
51 self._format = kwargs.get('format', None)
52 self._version_dialect = None
54 def set_context(self, line_number, line, version_dialect):
55 self._line_number = line_number
56 self._line = line
57 self._version_dialect = version_dialect
59 def __str__(self):
60 lst = [Exception.__str__(self)]
61 if self._version_dialect is not None:
62 lst.append('format version: %s' % self._version_dialect[0])
63 lst.append('dialect: %s' % self._version_dialect[1])
64 if self._line_number is not None:
65 lst.append('line number: %i' % self._line_number)
66 if self._line is not None:
67 lst.append('line content:\n%s' % (self._line.decode('ascii') or
68 '*** line is empty ***'))
70 if self._position is not None:
71 if self._position[1] is None:
72 length = max(1, len(self._line or '') - self._position[0])
73 else:
74 length = self._position[1]
76 lst.append(' ' * self._position[0] + '^' * length)
78 if self._format is not None:
79 i = 0
80 f = []
81 j = 1
82 for element in self._format:
83 if element.length != 0:
84 f.append(' ' * (element.position - i))
85 if element.length is not None:
86 f.append(str(j % 10) * element.length)
87 i = element.position + element.length
88 else:
89 f.append(str(j % 10) + '...')
90 i = element.position + 4
92 j += 1
94 lst.append(''.join(f))
96 return '\n'.join(lst)
99def float_or_none(x):
100 if x.strip():
101 return float(x)
102 else:
103 return None
106def int_or_none(x):
107 if x.strip():
108 return int(x)
109 else:
110 return None
113def float_to_string(fmt):
114 ef = fmt[0]
115 assert ef in 'ef'
116 ln, d = map(int, fmt[1:].split('.'))
117 pfmts = ['%%%i.%i%s' % (ln, dsub, ef) for dsub in range(d, -1, -1)]
118 blank = b' ' * ln
120 def func(v):
121 if v is None:
122 return blank
124 for pfmt in pfmts:
125 s = pfmt % v
126 if len(s) == ln:
127 return s.encode('ascii')
129 raise SerializeError('format="%s", value=%s' % (pfmt, repr(v)))
131 return func
134def int_to_string(fmt):
135 assert fmt[0] == 'i'
136 pfmt = '%'+fmt[1:]+'i'
137 ln = int(fmt[1:])
138 blank = b' ' * ln
140 def func(v):
141 if v is None:
142 return blank
144 s = pfmt % v
145 if len(s) == ln:
146 return s.encode('ascii')
147 else:
148 raise SerializeError('format="%s", value=%s' % (pfmt, repr(v)))
150 return func
153def deserialize_string(fmt):
154 if fmt.endswith('?'):
155 def func(s):
156 if s.strip():
157 return str(s.rstrip().decode('ascii'))
158 else:
159 return None
160 else:
161 def func(s):
162 return str(s.rstrip().decode('ascii'))
164 return func
167def serialize_string(fmt):
168 if fmt.endswith('+'):
169 more_ok = True
170 else:
171 more_ok = False
173 fmt = fmt.rstrip('?+')
175 assert fmt[0] == 'a'
176 ln = int(fmt[1:])
178 def func(v):
179 if v is None:
180 v = b''
181 else:
182 v = v.encode('ascii')
184 s = v.ljust(ln)
185 if more_ok or len(s) == ln:
186 return s
187 else:
188 raise SerializeError('max string length: %i, value="%s"' % ln, v)
190 return func
193def rstrip_string(v):
194 return v.rstrip()
197def x_fixed(expect):
198 def func():
199 def parse(s):
200 if s != expect:
201 raise DeserializeError(
202 'expected="%s", value="%s"' % (expect, s))
203 return s
205 def string(s):
206 return expect
208 return parse, string
210 func.width = len(expect)
211 func.help_type = 'Keyword: %s' % expect
212 return func
215def x_scaled(fmt, factor):
216 def func():
217 to_string = float_to_string(fmt)
219 def parse(s):
220 x = float_or_none(s)
221 if x is None:
222 return None
223 else:
224 return x * factor
226 def string(v):
227 if v is None:
228 return to_string(None)
229 else:
230 return to_string(v/factor)
232 return parse, string
234 func.width = int(fmt[1:].split('.')[0])
235 func.help_type = 'float'
236 return func
239def x_int_angle():
240 def string(v):
241 if v is None:
242 return b' '
243 else:
244 return ('%3i' % (int(round(v)) % 360)).encode('ascii')
246 return float_or_none, string
249x_int_angle.width = 3
250x_int_angle.help_type = 'int [0, 360]'
253def x_substitute(value):
254 def func():
255 def parse(s):
256 assert s == b''
257 return value
259 def string(s):
260 return b''
262 return parse, string
264 func.width = 0
265 func.help_type = 'Not present in this file version.'
266 return func
269def fillup_zeros(s, fmt):
270 s = s.rstrip()
271 if not s:
272 return s
274 if fmt == '%Y/%m/%d %H:%M:%S.3FRAC':
275 return s + '0000/01/01 00:00:00.000'[len(s):]
276 elif fmt == '%Y/%m/%d %H:%M:%S.2FRAC':
277 return s + '0000/01/01 00:00:00.00'[len(s):]
279 return s
282def x_date_time(fmt='%Y/%m/%d %H:%M:%S.3FRAC'):
283 def parse(s):
284 s = str(s.decode('ascii'))
285 try:
286 s = fillup_zeros(s, fmt)
287 return util.str_to_time(s, format=fmt)
289 except Exception:
290 # iris sets this dummy end dates and they don't fit into 32bit
291 # time stamps
292 if fmt[:2] == '%Y' and s[:4] in ('2599', '2045'):
293 return None
295 elif fmt[6:8] == '%Y' and s[6:10] in ('2599', '2045'):
296 return None
298 raise DeserializeError('expected date, value="%s"' % s)
300 def string(s):
301 return util.time_to_str(s, format=fmt).encode('ascii')
303 return parse, string
306x_date_time.width = 23
307x_date_time.help_type = 'YYYY/MM/DD HH:MM:SS.FFF'
310def x_date():
311 return x_date_time(fmt='%Y/%m/%d')
314x_date.width = 10
315x_date.help_type = 'YYYY/MM/DD'
318def x_date_iris():
319 return x_date_time(fmt='%m/%d/%Y')
322x_date_iris.width = 10
323x_date_iris.help_type = 'MM/DD/YYYY'
326def x_date_time_no_seconds():
327 return x_date_time(fmt='%Y/%m/%d %H:%M')
330x_date_time_no_seconds.width = 16
331x_date_time_no_seconds.help_type = 'YYYY/MM/DD HH:MM'
334def x_date_time_2frac():
335 return x_date_time(fmt='%Y/%m/%d %H:%M:%S.2FRAC')
338x_date_time_2frac.width = 22
339x_date_time_2frac.help_type = 'YYYY/MM/DD HH:MM:SS.FF'
342def x_yesno():
343 def parse(s):
344 if s == b'y':
345 return True
346 elif s == b'n':
347 return False
348 else:
349 raise DeserializeError('"y" on "n" expected')
351 def string(b):
352 return [b'n', b'y'][int(b)]
354 return parse, string
357x_yesno.width = 1
358x_yesno.help_type = 'yes/no'
361def optional(x_func):
363 def func():
364 parse, string = x_func()
366 def parse_optional(s):
367 if s.strip():
368 return parse(s)
369 else:
370 return None
372 def string_optional(s):
373 if s is None:
374 return b' ' * x_func.width
375 else:
376 return string(s)
378 return parse_optional, string_optional
380 func.width = x_func.width
381 func.help_type = 'optional %s' % x_func.help_type
383 return func
386class E(object):
387 def __init__(self, begin, end, fmt, dummy=False):
388 self.advance = 1
389 if dummy:
390 self.advance = 0
392 self.position = begin - 1
393 if end is not None:
394 self.length = end - begin + 1
395 else:
396 self.length = None
398 self.end = end
400 if isinstance(fmt, str):
401 t = fmt[0]
402 if t in 'ef':
403 self.parse = float_or_none
404 self.string = float_to_string(fmt)
405 ln = int(fmt[1:].split('.')[0])
406 self.help_type = 'float'
407 elif t == 'a':
408 self.parse = deserialize_string(fmt)
409 self.string = serialize_string(fmt)
410 ln = int(fmt[1:].rstrip('+?'))
411 self.help_type = 'string'
412 elif t == 'i':
413 self.parse = int_or_none
414 self.string = int_to_string(fmt)
415 ln = int(fmt[1:])
416 self.help_type = 'integer'
417 else:
418 assert False, 'invalid format: %s' % t
420 assert self.length is None or ln == self.length, \
421 'inconsistent length for pos=%i, fmt=%s' \
422 % (self.position, fmt)
424 else:
425 self.parse, self.string = fmt()
426 self.help_type = fmt.help_type
429def end_section(line, extra=None):
430 if line is None:
431 return True
433 ul = line.upper()
434 return ul.startswith(b'DATA_TYPE') or ul.startswith(b'STOP') or \
435 (extra is not None and ul.startswith(extra))
438class Section(Object):
439 '''
440 Base class for top level sections in IMS/GSE2 files.
442 Sections as understood by this implementation typically correspond to a
443 DATA_TYPE section in IMS/GSE2 but for some types a finer granularity has
444 been chosen.
445 '''
447 handlers = {} # filled after section have been defined below
449 @classmethod
450 def read(cls, reader):
451 datatype = DataType.read(reader)
452 reader.pushback()
453 return Section.handlers[
454 datatype.type.upper().encode('ascii')].read(reader)
456 def write_datatype(self, writer):
457 datatype = DataType(
458 type=self.keyword.decode('ascii'),
459 format=writer.version_dialect[0])
460 datatype.write(writer)
462 @classmethod
463 def read_table(cls, reader, expected_header, block_cls, end=end_section):
465 header = reader.readline()
466 if not header.upper().startswith(expected_header.upper()):
467 raise DeserializeError(
468 'invalid table header line, expected:\n'
469 '%s\nfound: %s ' % (expected_header, header))
471 while True:
472 line = reader.readline()
473 reader.pushback()
474 if end(line):
475 break
477 yield block_cls.read(reader)
479 def write_table(self, writer, header, blocks):
480 writer.writeline(header)
481 for block in blocks:
482 block.write(writer)
485def get_versioned(x, version_dialect):
486 if isinstance(x, dict):
487 for v in (tuple(version_dialect), version_dialect[0], None):
488 if v in x:
489 return x[v]
490 else:
491 return x
494class Block(Object):
495 '''
496 Base class for IMS/GSE2 data blocks / lines.
498 Blocks as understood by this implementation usually correspond to
499 individual logical lines in the IMS/GSE2 file.
500 '''
502 def values(self):
503 return list(self.T.ivals(self))
505 @classmethod
506 def format(cls, version_dialect):
507 return get_versioned(cls._format, version_dialect)
509 def serialize(self, version_dialect):
510 ivalue = 0
511 out = []
512 values = self.values()
513 for element in self.format(version_dialect):
514 if element.length != 0:
515 out.append((element.position, element.string(values[ivalue])))
516 ivalue += element.advance
518 out.sort()
520 i = 0
521 slist = []
522 for (position, s) in out:
523 slist.append(b' ' * (position - i))
524 slist.append(s)
525 i = position + len(s)
527 return b''.join(slist)
529 @classmethod
530 def deserialize_values(cls, line, version_dialect):
531 values = []
532 for element in cls.format(version_dialect):
533 try:
534 val = element.parse(
535 line[element.position:element.end])
537 if element.advance != 0:
538 values.append(val)
539 except Exception:
540 raise DeserializeError(
541 'Cannot parse %s' % (
542 element.help_type),
543 position=(element.position, element.length),
544 version=version_dialect[0],
545 dialect=version_dialect[1],
546 format=cls.format(version_dialect))
548 return values
550 @classmethod
551 def validated(cls, *args, **kwargs):
552 obj = cls(*args, **kwargs)
553 try:
554 obj.validate()
555 except ValidationError as e:
556 raise DeserializeError(str(e))
558 return obj
560 @classmethod
561 def regularized(cls, *args, **kwargs):
562 obj = cls(*args, **kwargs)
563 try:
564 obj.regularize()
565 except ValidationError as e:
566 raise DeserializeError(str(e))
568 return obj
570 @classmethod
571 def deserialize(cls, line, version_dialect):
572 values = cls.deserialize_values(line, version_dialect)
573 return cls.validated(**dict(zip(cls.T.propnames, values)))
575 @classmethod
576 def read(cls, reader):
577 line = reader.readline()
578 return cls.deserialize(line, reader.version_dialect)
580 def write(self, writer):
581 s = self.serialize(writer.version_dialect)
582 writer.writeline(s)
585class FreeFormatLine(Block):
586 '''
587 Base class for IMS/GSE2 free format lines.
588 '''
590 @classmethod
591 def deserialize_values(cls, line, version_dialect):
592 format = cls.format(version_dialect)
593 values = line.split(None, len(format)-1)
595 values_weeded = []
596 for x, v in zip(format, values):
597 if isinstance(x, bytes):
598 if v.upper() != x:
599 raise DeserializeError(
600 'expected keyword: %s, found %s' % (x, v.upper()))
602 else:
603 if isinstance(x, tuple):
604 x, (parse, _) = x
605 v = parse(v)
607 values_weeded.append((x, v))
609 values_weeded.sort()
610 return [str(xv[1].decode('ascii')) for xv in values_weeded]
612 @classmethod
613 def deserialize(cls, line, version_dialect):
614 values = cls.deserialize_values(line, version_dialect)
615 propnames = cls.T.propnames
616 stuff = dict(zip(propnames, values))
617 return cls.regularized(**stuff)
619 def serialize(self, version_dialect):
620 names = self.T.propnames
621 props = self.T.properties
622 out = []
623 for x in self.format(version_dialect):
624 if isinstance(x, bytes):
625 out.append(x.decode('ascii'))
626 else:
627 if isinstance(x, tuple):
628 x, (_, string) = x
629 v = string(getattr(self, names[x-1]))
630 else:
631 v = getattr(self, names[x-1])
633 if v is None:
634 break
636 out.append(props[x-1].to_save(v))
638 return ' '.join(out).encode('ascii')
641class DataType(Block):
642 '''
643 Representation of a DATA_TYPE line.
644 '''
646 type = String.T()
647 subtype = String.T(optional=True)
648 format = String.T()
649 subformat = String.T(optional=True)
651 @classmethod
652 def deserialize(cls, line, version_dialect):
653 pat = br'DATA_TYPE +([^ :]+)(:([^ :]+))?( +([^ :]+)(:([^ :]+))?)?'
654 m = re.match(pat, line)
655 if not m:
656 raise DeserializeError('invalid DATA_TYPE line')
658 return cls.validated(
659 type=str((m.group(1) or b'').decode('ascii')),
660 subtype=str((m.group(3) or b'').decode('ascii')),
661 format=str((m.group(5) or b'').decode('ascii')),
662 subformat=str((m.group(7) or b'').decode('ascii')))
664 def serialize(self, version_dialect):
665 s = self.type
666 if self.subtype:
667 s += ':' + self.subtype
669 f = self.format
670 if self.subformat:
671 f += ':' + self.subformat
673 return ('DATA_TYPE %s %s' % (s, f)).encode('ascii')
675 @classmethod
676 def read(cls, reader):
677 line = reader.readline()
678 datatype = cls.deserialize(line, reader.version_dialect)
679 reader.version_dialect[0] = datatype.format
680 return datatype
682 def write(self, writer):
683 s = self.serialize(writer.version_dialect)
684 writer.version_dialect[0] = self.format
685 writer.writeline(s)
688class FTPFile(FreeFormatLine):
689 '''
690 Representation of an FTP_FILE line.
691 '''
693 _format = [b'FTP_FILE', 1, 2, 3, 4]
695 net_address = String.T()
696 login_mode = StringChoice.T(choices=('USER', 'GUEST'), ignore_case=True)
697 directory = String.T()
698 file = String.T()
701class WaveformSubformat(StringChoice):
702 choices = ['INT', 'CM6', 'CM8', 'AU6', 'AU8']
703 ignore_case = True
706class WID2(Block):
707 '''
708 Representation of a WID2 line.
709 '''
711 _format = [
712 E(1, 4, x_fixed(b'WID2'), dummy=True),
713 E(6, 28, x_date_time),
714 E(30, 34, 'a5'),
715 E(36, 38, 'a3'),
716 E(40, 43, 'a4'),
717 E(45, 47, 'a3'),
718 E(49, 56, 'i8'),
719 E(58, 68, 'f11.6'),
720 E(70, 79, x_scaled('e10.2', nm_per_s)),
721 E(81, 87, 'f7.3'),
722 E(89, 94, 'a6?'),
723 E(96, 100, 'f5.1'),
724 E(102, 105, 'f4.1')
725 ]
727 time = Timestamp.T()
728 station = String.T(help='station code (5 characters)')
729 channel = String.T(help='channel code (3 characters)')
730 location = String.T(
731 default='', optional=True,
732 help='location code (aux_id, 4 characters)')
733 sub_format = WaveformSubformat.T(default='CM6')
734 nsamples = Int.T(default=0)
735 sample_rate = Float.T(default=1.0)
736 calibration_factor = Float.T(
737 optional=True,
738 help='system sensitivity (m/count) at reference period '
739 '(calibration_period)')
740 calibration_period = Float.T(
741 optional=True,
742 help='calibration reference period [s]')
743 instrument_type = String.T(
744 default='', optional=True, help='instrument type (6 characters)')
745 horizontal_angle = Float.T(
746 optional=True,
747 help='horizontal orientation of sensor, clockwise from north [deg]')
748 vertical_angle = Float.T(
749 optional=True,
750 help='vertical orientation of sensor from vertical [deg]')
753class OUT2(Block):
754 '''
755 Representation of an OUT2 line.
756 '''
758 _format = [
759 E(1, 4, x_fixed(b'OUT2'), dummy=True),
760 E(6, 28, x_date_time),
761 E(30, 34, 'a5'),
762 E(36, 38, 'a3'),
763 E(40, 43, 'a4'),
764 E(45, 55, 'f11.3')
765 ]
767 time = Timestamp.T()
768 station = String.T(help='station code (5 characters)')
769 channel = String.T(help='channel code (3 characters)')
770 location = String.T(
771 default='', optional=True,
772 help='location code (aux_id, 4 characters)')
773 duration = Float.T()
776class DLY2(Block):
777 '''
778 Representation of a DLY2 line.
779 '''
781 _format = [
782 E(1, 4, x_fixed(b'DLY2'), dummy=True),
783 E(6, 28, x_date_time),
784 E(30, 34, 'a5'),
785 E(36, 38, 'a3'),
786 E(40, 43, 'a4'),
787 E(45, 55, 'f11.3')
788 ]
790 time = Timestamp.T()
791 station = String.T(help='station code (5 characters)')
792 channel = String.T(help='channel code (3 characters)')
793 location = String.T(
794 default='', optional=True,
795 help='location code (aux_id, 4 characters)')
796 queue_duration = Float.T(help='duration of queue [s]')
799class DAT2(Block):
800 '''
801 Representation of a DAT2 line.
802 '''
804 _format = [
805 E(1, 4, x_fixed(b'DAT2'), dummy=True)
806 ]
808 raw_data = List.T(String.T())
810 @classmethod
811 def read(cls, reader):
812 line = reader.readline()
813 dat2 = cls.deserialize(line, reader.version_dialect)
814 while True:
815 line = reader.readline()
816 if line.upper().startswith(b'CHK2 '):
817 reader.pushback()
818 break
819 else:
820 if reader._load_data:
821 dat2.raw_data.append(line.strip())
823 return dat2
825 def write(self, writer):
826 Block.write(self, writer)
827 for line in self.raw_data:
828 writer.writeline(line)
831class STA2(Block):
832 '''
833 Representation of a STA2 line.
834 '''
836 _format = [
837 E(1, 4, x_fixed(b'STA2'), dummy=True),
838 E(6, 14, 'a9'),
839 E(16, 24, 'f9.5'),
840 E(26, 35, 'f10.5'),
841 E(37, 48, 'a12'),
842 E(50, 54, x_scaled('f5.3', km)),
843 E(56, 60, x_scaled('f5.3', km))
844 ]
846 # the standard requires lat, lon, elevation and depth, we define them as
847 # optional, however
849 network = String.T(help='network code (9 characters)')
850 lat = Float.T(optional=True)
851 lon = Float.T(optional=True)
852 coordinate_system = String.T(default='WGS-84')
853 elevation = Float.T(optional=True, help='elevation [m]')
854 depth = Float.T(optional=True, help='emplacement depth [m]')
857class CHK2(Block):
858 '''
859 Representation of a CHK2 line.
860 '''
862 _format = [
863 E(1, 4, x_fixed(b'CHK2'), dummy=True),
864 E(6, 13, 'i8')
865 ]
867 checksum = Int.T()
870class EID2(Block):
871 '''
872 Representation of an EID2 line.
873 '''
875 _format = [
876 E(1, 4, x_fixed(b'EID2'), dummy=True),
877 E(6, 13, 'a8'),
878 E(15, 23, 'a9'),
879 ]
881 event_id = String.T(help='event ID (8 characters)')
882 bulletin_type = String.T(help='bulletin type (9 characters)')
885class BEA2(Block):
886 '''
887 Representation of a BEA2 line.
888 '''
890 _format = [
891 E(1, 4, x_fixed(b'BEA2'), dummy=True),
892 E(6, 17, 'a12'),
893 E(19, 23, 'f5.1'),
894 E(25, 29, 'f5.1')]
896 beam_id = String.T(help='beam ID (12 characters)')
897 azimuth = Float.T()
898 slowness = Float.T()
901class Network(Block):
902 '''
903 Representation of an entry in a NETWORK section.
904 '''
906 _format = [
907 E(1, 9, 'a9'),
908 E(11, None, 'a64+')]
910 network = String.T(help='network code (9 characters)')
911 description = String.T(help='description')
914class Station(Block):
915 '''
916 Representation of an entry in a STATION section.
917 '''
919 _format = {
920 None: [
921 E(1, 9, 'a9'),
922 E(11, 15, 'a5'),
923 E(17, 20, 'a4'),
924 E(22, 30, 'f9.5'),
925 E(32, 41, 'f10.5'),
926 E(43, 54, 'a12'),
927 E(56, 60, x_scaled('f5.3', km)),
928 E(62, 71, x_date),
929 E(73, 82, optional(x_date))
930 ],
931 'GSE2.0': [
932 E(0, -1, x_substitute('')),
933 E(1, 5, 'a5'),
934 E(7, 10, 'a4'),
935 E(12, 20, 'f9.5'),
936 E(22, 31, 'f10.5'),
937 E(32, 31, x_substitute('WGS-84')),
938 E(33, 39, x_scaled('f7.3', km)),
939 E(41, 50, x_date),
940 E(52, 61, optional(x_date))]}
942 _format['IMS1.0', 'USA_DMC'] = list(_format[None])
943 _format['IMS1.0', 'USA_DMC'][-2:] = [
944 E(62, 71, x_date_iris),
945 E(73, 82, optional(x_date_iris))]
947 network = String.T(help='network code (9 characters)')
948 station = String.T(help='station code (5 characters)')
949 type = String.T(
950 help='station type (4 characters) '
951 '(1C: single component, 3C: three-component, '
952 'hfa: high frequency array, lpa: long period array)')
953 lat = Float.T()
954 lon = Float.T()
955 coordinate_system = String.T(default='WGS-84')
956 elevation = Float.T(help='elevation [m]')
957 tmin = Timestamp.T()
958 tmax = Timestamp.T(optional=True)
961class Channel(Block):
962 '''
963 Representation of an entry in a CHANNEL section.
964 '''
966 _format = {
967 None: [
968 E(1, 9, 'a9'),
969 E(11, 15, 'a5'),
970 E(17, 19, 'a3'),
971 E(21, 24, 'a4'),
972 E(26, 34, 'f9.5'),
973 E(36, 45, 'f10.5'),
974 E(47, 58, 'a12'),
975 E(60, 64, x_scaled('f5.3', km)),
976 E(66, 70, x_scaled('f5.3', km)),
977 E(72, 77, 'f6.1'),
978 E(79, 83, 'f5.1'),
979 E(85, 95, 'f11.6'),
980 E(97, 102, 'a6'),
981 E(105, 114, x_date),
982 E(116, 125, optional(x_date))],
983 'GSE2.0': [
984 E(0, -1, x_substitute('')),
985 E(1, 5, 'a5'),
986 E(7, 9, 'a3'),
987 E(11, 14, 'a4'),
988 E(16, 24, 'f9.5'),
989 E(26, 35, 'f10.5'),
990 E(32, 31, x_substitute('WGS-84')),
991 E(37, 43, x_scaled('f7.3', km)),
992 E(45, 50, x_scaled('f6.3', km)),
993 E(52, 57, 'f6.1'),
994 E(59, 63, 'f5.1'),
995 E(65, 75, 'f11.6'),
996 E(77, 83, 'a7'),
997 E(85, 94, x_date),
998 E(96, 105, optional(x_date))]}
1000 # norsar plays its own game...
1001 _format['GSE2.0', 'NOR_NDC'] = list(_format['GSE2.0'])
1002 _format['GSE2.0', 'NOR_NDC'][-2:] = [
1003 E(85, 100, x_date_time_no_seconds),
1004 E(102, 117, optional(x_date_time_no_seconds))]
1006 # also iris plays its own game...
1007 _format['IMS1.0', 'USA_DMC'] = list(_format[None])
1008 _format['IMS1.0', 'USA_DMC'][-2:] = [
1009 E(105, 114, x_date_iris),
1010 E(116, 125, optional(x_date_iris))]
1012 network = String.T(help='network code (9 characters)')
1013 station = String.T(help='station code (5 characters)')
1014 channel = String.T(help='channel code (3 characters)')
1015 location = String.T(
1016 default='', optional=True,
1017 help='location code (aux_id, 4 characters)')
1018 lat = Float.T(optional=True)
1019 lon = Float.T(optional=True)
1020 coordinate_system = String.T(default='WGS-84')
1021 elevation = Float.T(optional=True, help='elevation [m]')
1022 depth = Float.T(optional=True, help='emplacement depth [m]')
1023 horizontal_angle = Float.T(
1024 optional=True,
1025 help='horizontal orientation of sensor, clockwise from north [deg]')
1026 vertical_angle = Float.T(
1027 optional=True,
1028 help='vertical orientation of sensor from vertical [deg]')
1029 sample_rate = Float.T()
1030 instrument_type = String.T(
1031 default='', optional=True, help='instrument type (6 characters)')
1032 tmin = Timestamp.T()
1033 tmax = Timestamp.T(optional=True)
1036class BeamGroup(Block):
1037 '''
1038 Representation of an entry in a BEAM group table.
1039 '''
1041 _format = [
1042 E(1, 8, 'a8'),
1043 E(10, 14, 'a5'),
1044 E(16, 18, 'a3'),
1045 E(20, 23, 'a4'),
1046 E(25, 27, 'i3'),
1047 E(29, 37, 'f9.5')]
1049 beam_group = String.T(help='beam group (8 characters)')
1050 station = String.T(help='station code (5 characters)')
1051 channel = String.T(help='channel code (3 characters)')
1052 location = String.T(
1053 default='', optional=True,
1054 help='location code (aux_id, 4 characters)')
1055 weight = Int.T(
1056 optional=True,
1057 help='weight used for this component when the beam was formed')
1058 delay = Float.T(
1059 optional=True,
1060 help='beam delay for this component [s] '
1061 '(used for meabs formed by non-plane waves)')
1064class BeamType(StringChoice):
1065 choices = ['inc', 'coh']
1066 ignore_case = True
1069class FilterType(StringChoice):
1070 choices = ['BP', 'LP', 'HP', 'BR']
1071 ignore_case = True
1074class BeamParameters(Block):
1075 '''
1076 Representation of an entry in a BEAM parameters table.
1077 '''
1079 _format = [
1080 E(1, 12, 'a12'),
1081 E(14, 21, 'a8'),
1082 E(23, 25, 'a3'),
1083 E(27, 27, x_yesno),
1084 E(29, 33, 'f5.1'),
1085 E(35, 39, 'f5.3'), # standard says f5.1 -999.0 is vertical beam
1086 E(41, 48, 'a8'),
1087 E(50, 55, 'f6.2'),
1088 E(57, 62, 'f6.2'),
1089 E(64, 65, 'i2'),
1090 E(67, 67, x_yesno),
1091 E(69, 70, 'a2'),
1092 E(72, 81, x_date),
1093 E(83, 92, optional(x_date))]
1095 beam_id = String.T()
1096 beam_group = String.T()
1097 type = BeamType.T()
1098 is_rotated = Bool.T(help='rotation flag')
1099 azimuth = Float.T(
1100 help='azimuth used to steer the beam [deg] (clockwise from North)')
1101 slowness = Float.T(
1102 help='slowness used to steer the beam [s/deg]')
1103 phase = String.T(
1104 help='phase used to set the beam slowness for origin-based beams '
1105 '(8 characters)')
1106 filter_fmin = Float.T(
1107 help='low frequency cut-off for the beam filter [Hz]')
1108 filter_fmax = Float.T(
1109 help='high frequency cut-off for the beam filter [Hz]')
1110 filter_order = Int.T(
1111 help='order of the beam filter')
1112 filter_is_zero_phase = Bool.T(
1113 help='flag to indicate zero-phase filtering')
1114 filter_type = FilterType.T(
1115 help='type of filtering')
1116 tmin = Timestamp.T(
1117 help='start date of beam use')
1118 tmax = Timestamp.T(
1119 optional=True,
1120 help='end date of beam use')
1123class OutageReportPeriod(Block):
1124 '''
1125 Representation of a the report period of an OUTAGE section.
1126 '''
1128 _format = [
1129 E(1, 18, x_fixed(b'Report period from'), dummy=True),
1130 E(20, 42, x_date_time),
1131 E(44, 45, x_fixed(b'to'), dummy=True),
1132 E(47, 69, x_date_time)]
1134 tmin = Timestamp.T()
1135 tmax = Timestamp.T()
1138class Outage(Block):
1139 '''
1140 Representation of an entry in the OUTAGE section table.
1141 '''
1142 _format = [
1143 E(1, 9, 'a9'),
1144 E(11, 15, 'a5'),
1145 E(17, 19, 'a3'),
1146 E(21, 24, 'a4'),
1147 E(26, 48, x_date_time),
1148 E(50, 72, x_date_time),
1149 E(74, 83, 'f10.3'),
1150 E(85, None, 'a48+')]
1152 network = String.T(help='network code (9 characters)')
1153 station = String.T(help='station code (5 characters)')
1154 channel = String.T(help='channel code (3 characters)')
1155 location = String.T(
1156 default='', optional=True,
1157 help='location code (aux_id, 4 characters)')
1158 tmin = Timestamp.T()
1159 tmax = Timestamp.T()
1160 duration = Float.T()
1161 comment = String.T()
1164class CAL2(Block):
1165 '''
1166 Representation of a CAL2 line.
1167 '''
1169 _format = {
1170 None: [
1171 E(1, 4, x_fixed(b'CAL2'), dummy=True),
1172 E(6, 10, 'a5'),
1173 E(12, 14, 'a3'),
1174 E(16, 19, 'a4'),
1175 E(21, 26, 'a6'),
1176 E(28, 42, x_scaled('e15.8', nm_per_s)), # standard: e15.2
1177 E(44, 50, 'f7.3'),
1178 E(52, 62, 'f11.5'),
1179 E(64, 79, x_date_time_no_seconds),
1180 E(81, 96, optional(x_date_time_no_seconds))],
1181 'GSE2.0': [
1182 E(1, 4, x_fixed(b'CAL2'), dummy=True),
1183 E(6, 10, 'a5'),
1184 E(12, 14, 'a3'),
1185 E(16, 19, 'a4'),
1186 E(21, 26, 'a6'),
1187 E(28, 37, x_scaled('e10.4', nm_per_s)),
1188 E(39, 45, 'f7.3'),
1189 E(47, 56, 'f10.5'),
1190 E(58, 73, x_date_time_no_seconds),
1191 E(75, 90, optional(x_date_time_no_seconds))]}
1193 station = String.T(help='station code (5 characters)')
1194 channel = String.T(help='channel code (3 characters)')
1195 location = String.T(
1196 default='', optional=True,
1197 help='location code (aux_id, 4 characters)')
1198 instrument_type = String.T(
1199 default='', optional=True, help='instrument type (6 characters)')
1200 calibration_factor = Float.T(
1201 help='system sensitivity (m/count) at reference period '
1202 '(calibration_period)')
1203 calibration_period = Float.T(help='calibration reference period [s]')
1204 sample_rate = Float.T(help='system output sample rate [Hz]')
1205 tmin = Timestamp.T(help='effective start date and time')
1206 tmax = Timestamp.T(optional=True, help='effective end date and time')
1207 comments = List.T(String.T(optional=True))
1209 @classmethod
1210 def read(cls, reader):
1211 lstart = reader.current_line_number()
1212 line = reader.readline()
1213 obj = cls.deserialize(line, reader.version_dialect)
1214 while True:
1215 line = reader.readline()
1216 # make sure all comments are read
1217 if line is None or not line.startswith(b' '):
1218 reader.pushback()
1219 break
1221 obj.append_dataline(line, reader.version_dialect)
1223 obj.comments.extend(reader.get_comments_after(lstart))
1224 return obj
1226 def write(self, writer):
1227 s = self.serialize(writer.version_dialect)
1228 writer.writeline(s)
1229 for c in self.comments:
1230 writer.writeline((' (%s)' % c).encode('ascii'))
1233class Units(StringChoice):
1234 choices = ['V', 'A', 'C']
1235 ignore_case = True
1238class Stage(Block):
1239 '''
1240 Base class for IMS/GSE2 response stages.
1242 Available response stages are :py:class:`PAZ2`, :py:class:`FAP2`,
1243 :py:class:`GEN2`, :py:class:`DIG2`, and :py:class:`FIR2`.
1245 '''
1247 stage_number = Int.T(help='stage sequence number')
1249 @classmethod
1250 def read(cls, reader):
1251 lstart = reader.current_line_number()
1252 line = reader.readline()
1253 obj = cls.deserialize(line, reader.version_dialect)
1255 while True:
1256 line = reader.readline()
1257 if line is None or not line.startswith(b' '):
1258 reader.pushback()
1259 break
1261 obj.append_dataline(line, reader.version_dialect)
1263 obj.comments.extend(reader.get_comments_after(lstart))
1265 return obj
1267 def write(self, writer):
1268 line = self.serialize(writer.version_dialect)
1269 writer.writeline(line)
1270 self.write_datalines(writer)
1271 for c in self.comments:
1272 writer.writeline((' (%s)' % c).encode('ascii'))
1274 def write_datalines(self, writer):
1275 pass
1278class PAZ2Data(Block):
1279 '''
1280 Representation of the complex numbers in PAZ2 sections.
1281 '''
1283 _format = [
1284 E(2, 16, 'e15.8'),
1285 E(18, 32, 'e15.8')]
1287 real = Float.T()
1288 imag = Float.T()
1291class PAZ2(Stage):
1292 '''
1293 Representation of a PAZ2 line.
1294 '''
1296 _format = {
1297 None: [
1298 E(1, 4, x_fixed(b'PAZ2'), dummy=True),
1299 E(6, 7, 'i2'),
1300 E(9, 9, 'a1'),
1301 E(11, 25, 'e15.8'),
1302 E(27, 30, 'i4'),
1303 E(32, 39, 'f8.3'),
1304 E(41, 43, 'i3'),
1305 E(45, 47, 'i3'),
1306 E(49, None, 'a25+')],
1307 ('IMS1.0', 'USA_DMC'): [
1308 E(1, 4, x_fixed(b'PAZ2'), dummy=True),
1309 E(6, 7, 'i2'),
1310 E(9, 9, 'a1'),
1311 E(11, 25, 'e15.8'),
1312 E(27, 30, 'i4'),
1313 E(32, 39, 'f8.3'),
1314 E(40, 42, 'i3'),
1315 E(44, 46, 'i3'),
1316 E(48, None, 'a25+')]}
1318 output_units = Units.T(
1319 help='output units code (V=volts, A=amps, C=counts)')
1320 scale_factor = Float.T(help='scale factor [ouput units/input units]')
1321 decimation = Int.T(optional=True, help='decimation')
1322 correction = Float.T(optional=True, help='group correction applied [s]')
1323 npoles = Int.T(help='number of poles')
1324 nzeros = Int.T(help='number of zeros')
1325 description = String.T(default='', optional=True, help='description')
1327 poles = List.T(Complex.T())
1328 zeros = List.T(Complex.T())
1330 comments = List.T(String.T(optional=True))
1332 def append_dataline(self, line, version_dialect):
1333 d = PAZ2Data.deserialize(line, version_dialect)
1334 v = complex(d.real, d.imag)
1335 i = len(self.poles) + len(self.zeros)
1337 if i < self.npoles:
1338 self.poles.append(v)
1339 elif i < self.npoles + self.nzeros:
1340 self.zeros.append(v)
1341 else:
1342 raise DeserializeError(
1343 'more poles and zeros than expected')
1345 def write_datalines(self, writer):
1346 for pole in self.poles:
1347 PAZ2Data(real=pole.real, imag=pole.imag).write(writer)
1348 for zero in self.zeros:
1349 PAZ2Data(real=zero.real, imag=zero.imag).write(writer)
1352class FAP2Data(Block):
1353 '''
1354 Representation of the data tuples in FAP2 section.
1355 '''
1357 _format = [
1358 E(2, 11, 'f10.5'),
1359 E(13, 27, 'e15.8'),
1360 E(29, 32, 'i4')]
1362 frequency = Float.T()
1363 amplitude = Float.T()
1364 phase = Float.T()
1367class FAP2(Stage):
1368 '''
1369 Representation of a FAP2 line.
1370 '''
1372 _format = [
1373 E(1, 4, x_fixed(b'FAP2'), dummy=True),
1374 E(6, 7, 'i2'),
1375 E(9, 9, 'a1'),
1376 E(11, 14, 'i4'),
1377 E(16, 23, 'f8.3'),
1378 E(25, 27, 'i3'),
1379 E(29, 53, 'a25')]
1381 output_units = Units.T(
1382 help='output units code (V=volts, A=amps, C=counts)')
1383 decimation = Int.T(optional=True, help='decimation')
1384 correction = Float.T(help='group correction applied [s]')
1385 ntrip = Int.T(help='number of frequency, amplitude, phase triplets')
1386 description = String.T(default='', optional=True, help='description')
1388 frequencies = List.T(Float.T(), help='frequency [Hz]')
1389 amplitudes = List.T(
1390 Float.T(), help='amplitude [input untits/output units]')
1391 phases = List.T(Float.T(), help='phase delay [degrees]')
1393 comments = List.T(String.T(optional=True))
1395 def append_dataline(self, line, version_dialect):
1396 d = FAP2Data.deserialize(line, version_dialect)
1397 self.frequencies.append(d.frequency)
1398 self.amplitudes.append(d.amplitude)
1399 self.phases.append(d.phase)
1401 def write_datalines(self, writer):
1402 for frequency, amplitude, phase in zip(
1403 self.frequencies, self.amplitudes, self.phases):
1405 FAP2Data(
1406 frequency=frequency,
1407 amplitude=amplitude,
1408 phase=phase).write(writer)
1411class GEN2Data(Block):
1412 '''
1413 Representation of a data tuple in GEN2 section.
1414 '''
1416 _format = [
1417 E(2, 12, 'f11.5'),
1418 E(14, 19, 'f6.3')]
1420 corner = Float.T(help='corner frequency [Hz]')
1421 slope = Float.T(help='slope above corner [dB/decate]')
1424class GEN2(Stage):
1425 '''
1426 Representation of a GEN2 line.
1427 '''
1429 _format = [
1430 E(1, 4, x_fixed(b'GEN2'), dummy=True),
1431 E(6, 7, 'i2'),
1432 E(9, 9, 'a1'),
1433 E(11, 25, x_scaled('e15.8', nm_per_s)),
1434 E(27, 33, 'f7.3'),
1435 E(35, 38, 'i4'),
1436 E(40, 47, 'f8.3'),
1437 E(49, 51, 'i3'),
1438 E(53, 77, 'a25')]
1440 output_units = Units.T(
1441 help='output units code (V=volts, A=amps, C=counts)')
1442 calibration_factor = Float.T(
1443 help='system sensitivity (m/count) at reference period '
1444 '(calibration_period)')
1445 calibration_period = Float.T(help='calibration reference period [s]')
1446 decimation = Int.T(optional=True, help='decimation')
1447 correction = Float.T(help='group correction applied [s]')
1448 ncorners = Int.T(help='number of corners')
1449 description = String.T(default='', optional=True, help='description')
1451 corners = List.T(Float.T(), help='corner frequencies [Hz]')
1452 slopes = List.T(Float.T(), help='slopes above corners [dB/decade]')
1454 comments = List.T(String.T(optional=True))
1456 def append_dataline(self, line, version_dialect):
1457 d = GEN2Data.deserialize(line, version_dialect)
1458 self.corners.append(d.corner)
1459 self.slopes.append(d.slope)
1461 def write_datalines(self, writer):
1462 for corner, slope in zip(self.corners, self.slopes):
1463 GEN2Data(corner=corner, slope=slope).write(writer)
1466class DIG2(Stage):
1467 '''
1468 Representation of a DIG2 line.
1469 '''
1471 _format = [
1472 E(1, 4, x_fixed(b'DIG2'), dummy=True),
1473 E(6, 7, 'i2'),
1474 E(9, 23, 'e15.8'),
1475 E(25, 35, 'f11.5'),
1476 E(37, None, 'a25+')]
1478 sensitivity = Float.T(help='sensitivity [counts/input units]')
1479 sample_rate = Float.T(help='digitizer sample rate [Hz]')
1480 description = String.T(default='', optional=True, help='description')
1482 comments = List.T(String.T(optional=True))
1485class SymmetryFlag(StringChoice):
1486 choices = ['A', 'B', 'C']
1487 ignore_case = True
1490class FIR2Data(Block):
1491 '''
1492 Representation of a line of coefficients in a FIR2 section.
1493 '''
1495 _format = [
1496 E(2, 16, 'e15.8'),
1497 E(18, 32, 'e15.8'),
1498 E(34, 48, 'e15.8'),
1499 E(50, 64, 'e15.8'),
1500 E(66, 80, 'e15.8')]
1502 factors = List.T(Float.T())
1504 def values(self):
1505 return self.factors + [None]*(5-len(self.factors))
1507 @classmethod
1508 def deserialize(cls, line, version_dialect):
1509 factors = [v for v in cls.deserialize_values(line, version_dialect)
1510 if v is not None]
1511 return cls.validated(factors=factors)
1514class FIR2(Stage):
1515 '''
1516 Representation of a FIR2 line.
1517 '''
1519 _format = [
1520 E(1, 4, x_fixed(b'FIR2'), dummy=True),
1521 E(6, 7, 'i2'),
1522 E(9, 18, 'e10.2'),
1523 E(20, 23, 'i4'),
1524 E(25, 32, 'f8.3'),
1525 E(34, 34, 'a1'),
1526 E(36, 39, 'i4'),
1527 E(41, None, 'a25+')]
1529 gain = Float.T(help='filter gain (relative factor, not in dB)')
1530 decimation = Int.T(optional=True, help='decimation')
1531 correction = Float.T(help='group correction applied [s]')
1532 symmetry = SymmetryFlag.T(
1533 help='symmetry flag (A=asymmetric, B=symmetric (odd), '
1534 'C=symmetric (even))')
1535 nfactors = Int.T(help='number of factors')
1536 description = String.T(default='', optional=True, help='description')
1538 comments = List.T(String.T(optional=True))
1540 factors = List.T(Float.T())
1542 def append_dataline(self, line, version_dialect):
1543 d = FIR2Data.deserialize(line, version_dialect)
1544 self.factors.extend(d.factors)
1546 def write_datalines(self, writer):
1547 i = 0
1548 while i < len(self.factors):
1549 FIR2Data(factors=self.factors[i:i+5]).write(writer)
1550 i += 5
1553class Begin(FreeFormatLine):
1554 '''
1555 Representation of a BEGIN line.
1556 '''
1558 _format = [b'BEGIN', 1]
1559 version = String.T(optional=True)
1561 @classmethod
1562 def read(cls, reader):
1563 line = reader.readline()
1564 obj = cls.deserialize(line, reader.version_dialect)
1565 reader.version_dialect[0] = obj.version
1566 return obj
1568 def write(self, writer):
1569 FreeFormatLine.write(self, writer)
1570 writer.version_dialect[0] = self.version
1573class MessageType(StringChoice):
1574 choices = ['REQUEST', 'DATA', 'SUBSCRIPTION']
1575 ignore_case = True
1578class MsgType(FreeFormatLine):
1579 _format = [b'MSG_TYPE', 1]
1580 type = MessageType.T()
1583class MsgID(FreeFormatLine):
1584 '''
1585 Representation of a MSG_ID line.
1586 '''
1588 _format = [b'MSG_ID', 1, 2]
1589 msg_id_string = String.T()
1590 msg_id_source = String.T(optional=True)
1592 @classmethod
1593 def read(cls, reader):
1594 line = reader.readline()
1595 obj = cls.deserialize(line, reader.version_dialect)
1596 if obj.msg_id_source in g_dialects:
1597 reader.version_dialect[1] = obj.msg_id_source
1599 return obj
1601 def write(self, writer):
1602 FreeFormatLine.write(self, writer)
1603 if self.msg_id_source in g_dialects:
1604 writer.version_dialect[1] = self.msg_id_source
1607class RefID(FreeFormatLine):
1608 '''
1609 Representation of a REF_ID line.
1610 '''
1612 _format = {
1613 None: [b'REF_ID', 1, 2, 'PART', 3, 'OF', 4],
1614 'GSE2.0': [b'REF_ID', 1]}
1616 msg_id_string = String.T()
1617 msg_id_source = String.T(optional=True)
1618 sequence_number = Int.T(optional=True)
1619 total_number = Int.T(optional=True)
1621 def serialize(self, version_dialect):
1622 out = ['REF_ID', self.msg_id_string]
1623 if self.msg_id_source:
1624 out.append(self.msg_id_source)
1625 i = self.sequence_number
1626 n = self.total_number
1627 if i is not None and n is not None:
1628 out.extend(['PART', str(i), 'OF', str(n)])
1630 return ' '.join(out).encode('ascii')
1633class LogSection(Section):
1634 '''
1635 Representation of a DATA_TYPE LOG section.
1636 '''
1638 keyword = b'LOG'
1639 lines = List.T(String.T())
1641 @classmethod
1642 def read(cls, reader):
1643 DataType.read(reader)
1644 lines = []
1645 while True:
1646 line = reader.readline()
1647 if end_section(line):
1648 reader.pushback()
1649 break
1650 else:
1651 lines.append(str(line.decode('ascii')))
1653 return cls(lines=lines)
1655 def write(self, writer):
1656 self.write_datatype(writer)
1657 for line in self.lines:
1658 ul = line.upper()
1659 if ul.startswith('DATA_TYPE') or ul.startswith('STOP'):
1660 line = ' ' + line
1662 writer.writeline(str.encode(line))
1665class ErrorLogSection(LogSection):
1666 '''
1667 Representation of a DATA_TYPE ERROR_LOG section.
1668 '''
1670 keyword = b'ERROR_LOG'
1673class FTPLogSection(Section):
1674 '''
1675 Representation of a DATA_TYPE FTP_LOG section.
1676 '''
1678 keyword = b'FTP_LOG'
1679 ftp_file = FTPFile.T()
1681 @classmethod
1682 def read(cls, reader):
1683 DataType.read(reader)
1684 ftp_file = FTPFile.read(reader)
1685 return cls(ftp_file=ftp_file)
1687 def write(self, writer):
1688 self.write_datatype(writer)
1689 self.ftp_file.write(writer)
1692class WID2Section(Section):
1693 '''
1694 Representation of a WID2/STA2/EID2/BEA2/DAT2/CHK2 group.
1695 '''
1697 wid2 = WID2.T()
1698 sta2 = STA2.T(optional=True)
1699 eid2s = List.T(EID2.T())
1700 bea2 = BEA2.T(optional=True)
1701 dat2 = DAT2.T()
1702 chk2 = CHK2.T()
1704 @classmethod
1705 def read(cls, reader):
1706 blocks = dict(eid2s=[])
1707 expect = [(b'WID2 ', WID2, 1)]
1709 if reader.version_dialect[0] == 'GSE2.0':
1710 # should not be there in GSE2.0, but BGR puts it there
1711 expect.append((b'STA2 ', STA2, 0))
1712 else:
1713 expect.append((b'STA2 ', STA2, 1))
1715 expect.extend([
1716 (b'EID2 ', EID2, 0),
1717 (b'BEA2 ', BEA2, 0),
1718 (b'DAT2', DAT2, 1),
1719 (b'CHK2 ', CHK2, 1)])
1721 for k, handler, required in expect:
1722 line = reader.readline()
1723 reader.pushback()
1725 if line is None:
1726 raise DeserializeError('incomplete waveform section')
1728 if line.upper().startswith(k):
1729 block = handler.read(reader)
1730 if k == b'EID2 ':
1731 blocks['eid2s'].append(block)
1732 else:
1733 blocks[str(k.lower().rstrip().decode('ascii'))] = block
1734 else:
1735 if required:
1736 raise DeserializeError('expected %s block' % k)
1737 else:
1738 continue
1740 return cls(**blocks)
1742 def write(self, writer):
1743 self.wid2.write(writer)
1744 if self.sta2:
1745 self.sta2.write(writer)
1746 for eid2 in self.eid2s:
1747 eid2.write(writer)
1748 if self.bea2:
1749 self.bea2.write(writer)
1751 self.dat2.write(writer)
1752 self.chk2.write(writer)
1754 def pyrocko_trace(self, checksum_error='raise'):
1755 from pyrocko import ims_ext, trace
1756 assert checksum_error in ('raise', 'warn', 'ignore')
1758 raw_data = self.dat2.raw_data
1759 nsamples = self.wid2.nsamples
1760 deltat = 1.0 / self.wid2.sample_rate
1761 tmin = self.wid2.time
1762 if self.sta2:
1763 net = self.sta2.network
1764 else:
1765 net = ''
1766 sta = self.wid2.station
1767 loc = self.wid2.location
1768 cha = self.wid2.channel
1770 if raw_data:
1771 ydata = ims_ext.decode_cm6(b''.join(raw_data), nsamples)
1772 if checksum_error != 'ignore':
1773 if ims_ext.checksum(ydata) != self.chk2.checksum:
1774 mess = 'computed checksum value differs from stored value'
1775 if checksum_error == 'raise':
1776 raise DeserializeError(mess)
1777 elif checksum_error == 'warn':
1778 logger.warning(mess)
1780 tmax = None
1781 else:
1782 tmax = tmin + (nsamples - 1) * deltat
1783 ydata = None
1785 return trace.Trace(
1786 net, sta, loc, cha, tmin=tmin, tmax=tmax,
1787 deltat=deltat,
1788 ydata=ydata)
1790 @classmethod
1791 def from_pyrocko_trace(cls, tr,
1792 lat=None, lon=None, elevation=None, depth=None):
1794 from pyrocko import ims_ext
1795 ydata = tr.get_ydata()
1796 raw_data = ims_ext.encode_cm6(ydata)
1797 return cls(
1798 wid2=WID2(
1799 nsamples=tr.data_len(),
1800 sample_rate=1.0 / tr.deltat,
1801 time=tr.tmin,
1802 station=tr.station,
1803 location=tr.location,
1804 channel=tr.channel),
1805 sta2=STA2(
1806 network=tr.network,
1807 lat=lat,
1808 lon=lon,
1809 elevation=elevation,
1810 depth=depth),
1811 dat2=DAT2(
1812 raw_data=[raw_data[i*80:(i+1)*80]
1813 for i in range((len(raw_data)-1)//80 + 1)]),
1814 chk2=CHK2(
1815 checksum=ims_ext.checksum(ydata)))
1818class OUT2Section(Section):
1819 '''
1820 Representation of a OUT2/STA2 group.
1821 '''
1823 out2 = OUT2.T()
1824 sta2 = STA2.T()
1826 @classmethod
1827 def read(cls, reader):
1828 out2 = OUT2.read(reader)
1829 line = reader.readline()
1830 reader.pushback()
1831 if line.startswith(b'STA2'):
1832 # the spec sais STA2 is mandatory but in practice, it is not
1833 # always there...
1834 sta2 = STA2.read(reader)
1835 else:
1836 sta2 = None
1838 return cls(out2=out2, sta2=sta2)
1840 def write(self, writer):
1841 self.out2.write(writer)
1842 if self.sta2 is not None:
1843 self.sta2.write(writer)
1846class DLY2Section(Section):
1847 '''
1848 Representation of a DLY2/STA2 group.
1849 '''
1851 dly2 = DLY2.T()
1852 sta2 = STA2.T()
1854 @classmethod
1855 def read(cls, reader):
1856 dly2 = DLY2.read(reader)
1857 sta2 = STA2.read(reader)
1858 return cls(dly2=dly2, sta2=sta2)
1860 def write(self, writer):
1861 self.dly2.write(writer)
1862 self.sta2.write(writer)
1865class WaveformSection(Section):
1866 '''
1867 Representation of a DATA_TYPE WAVEFORM line.
1869 Any subsequent WID2/OUT2/DLY2 groups are handled as indepenent sections, so
1870 this type just serves as a dummy to read/write the DATA_TYPE WAVEFORM
1871 header.
1872 '''
1874 keyword = b'WAVEFORM'
1876 datatype = DataType.T()
1878 @classmethod
1879 def read(cls, reader):
1880 datatype = DataType.read(reader)
1881 return cls(datatype=datatype)
1883 def write(self, writer):
1884 self.datatype.write(writer)
1887class TableSection(Section):
1888 '''
1889 Base class for table style sections.
1890 '''
1892 has_data_type_header = True
1894 @classmethod
1895 def read(cls, reader):
1896 if cls.has_data_type_header:
1897 DataType.read(reader)
1899 ts = cls.table_setup
1901 header = get_versioned(ts['header'], reader.version_dialect)
1902 blocks = list(cls.read_table(
1903 reader, header, ts['cls'], end=ts.get('end', end_section)))
1904 return cls(**{ts['attribute']: blocks})
1906 def write(self, writer):
1907 if self.has_data_type_header:
1908 self.write_datatype(writer)
1910 ts = self.table_setup
1911 header = get_versioned(ts['header'], writer.version_dialect)
1912 self.write_table(writer, header, getattr(self, ts['attribute']))
1915class NetworkSection(TableSection):
1916 '''
1917 Representation of a DATA_TYPE NETWORK section.
1918 '''
1920 keyword = b'NETWORK'
1921 table_setup = dict(
1922 header=b'Net Description',
1923 attribute='networks',
1924 cls=Network)
1926 networks = List.T(Network.T())
1929class StationSection(TableSection):
1930 '''
1931 Representation of a DATA_TYPE STATION section.
1932 '''
1934 keyword = b'STATION'
1935 table_setup = dict(
1936 header={
1937 None: (
1938 b'Net Sta Type Latitude Longitude Coord '
1939 b'Sys Elev On Date Off Date'),
1940 'GSE2.0': (
1941 b'Sta Type Latitude Longitude Elev On Date '
1942 b'Off Date')},
1943 attribute='stations',
1944 cls=Station)
1946 stations = List.T(Station.T())
1949class ChannelSection(TableSection):
1950 '''
1951 Representation of a DATA_TYPE CHANNEL section.
1952 '''
1954 keyword = b'CHANNEL'
1955 table_setup = dict(
1956 header={
1957 None: (
1958 b'Net Sta Chan Aux Latitude Longitude Coord Sys'
1959 b' Elev Depth Hang Vang Sample Rate Inst '
1960 b'On Date Off Date'),
1961 'GSE2.0': (
1962 b'Sta Chan Aux Latitude Longitude '
1963 b'Elev Depth Hang Vang Sample_Rate Inst '
1964 b'On Date Off Date')},
1965 attribute='channels',
1966 cls=Channel)
1968 channels = List.T(Channel.T())
1971class BeamSection(Section):
1972 '''
1973 Representation of a DATA_TYPE BEAM section.
1974 '''
1976 keyword = b'BEAM'
1977 beam_group_header = b'Bgroup Sta Chan Aux Wgt Delay'
1978 beam_parameters_header = b'BeamID Bgroup Btype R Azim Slow '\
1979 b'Phase Flo Fhi O Z F '\
1980 b'On Date Off Date'
1981 group = List.T(BeamGroup.T())
1982 parameters = List.T(BeamParameters.T())
1984 @classmethod
1985 def read(cls, reader):
1986 DataType.read(reader)
1988 def end(line):
1989 return line.upper().startswith(b'BEAMID')
1991 group = list(cls.read_table(reader, cls.beam_group_header, BeamGroup,
1992 end))
1994 parameters = list(cls.read_table(reader, cls.beam_parameters_header,
1995 BeamParameters))
1997 return cls(group=group, parameters=parameters)
1999 def write(self, writer):
2000 self.write_datatype(writer)
2001 self.write_table(writer, self.beam_group_header, self.group)
2002 writer.writeline(b'')
2003 self.write_table(writer, self.beam_parameters_header, self.parameters)
2006class CAL2Section(Section):
2007 '''
2008 Representation of a CAL2 + stages group in a response section.
2009 '''
2011 cal2 = CAL2.T()
2012 stages = List.T(Stage.T())
2014 @classmethod
2015 def read(cls, reader):
2016 cal2 = CAL2.read(reader)
2017 stages = []
2018 handlers = {
2019 b'PAZ2': PAZ2,
2020 b'FAP2': FAP2,
2021 b'GEN2': GEN2,
2022 b'DIG2': DIG2,
2023 b'FIR2': FIR2}
2025 while True:
2026 line = reader.readline()
2027 reader.pushback()
2028 if end_section(line, b'CAL2'):
2029 break
2031 k = line[:4].upper()
2032 if k in handlers:
2033 stages.append(handlers[k].read(reader))
2034 else:
2035 raise DeserializeError('unexpected line')
2037 return cls(cal2=cal2, stages=stages)
2039 def write(self, writer):
2040 self.cal2.write(writer)
2041 for stage in self.stages:
2042 stage.write(writer)
2045class ResponseSection(Section):
2046 '''
2047 Representation of a DATA_TYPE RESPONSE line.
2049 Any subsequent CAL2+stages groups are handled as indepenent sections, so
2050 this type just serves as a dummy to read/write the DATA_TYPE RESPONSE
2051 header.
2052 '''
2054 keyword = b'RESPONSE'
2056 datatype = DataType.T()
2058 @classmethod
2059 def read(cls, reader):
2060 datatype = DataType.read(reader)
2061 return cls(datatype=datatype)
2063 def write(self, writer):
2064 self.datatype.write(writer)
2067class OutageSection(Section):
2068 '''
2069 Representation of a DATA_TYPE OUTAGE section.
2070 '''
2072 keyword = b'OUTAGE'
2073 outages_header = b'NET Sta Chan Aux Start Date Time'\
2074 b' End Date Time Duration Comment'
2075 report_period = OutageReportPeriod.T()
2076 outages = List.T(Outage.T())
2078 @classmethod
2079 def read(cls, reader):
2080 DataType.read(reader)
2081 report_period = OutageReportPeriod.read(reader)
2082 outages = []
2083 outages = list(cls.read_table(reader, cls.outages_header,
2084 Outage))
2086 return cls(
2087 report_period=report_period,
2088 outages=outages)
2090 def write(self, writer):
2091 self.write_datatype(writer)
2092 self.report_period.write(writer)
2093 self.write_table(writer, self.outages_header, self.outages)
2096class BulletinTitle(Block):
2098 _format = [
2099 E(1, 136, 'a136')]
2101 title = String.T()
2104g_event_types = dict(
2105 uk='unknown',
2106 ke='known earthquake',
2107 se='suspected earthquake',
2108 kr='known rockburst',
2109 sr='suspected rockburst',
2110 ki='known induced event',
2111 si='suspected induced event',
2112 km='known mine explosion',
2113 sm='suspected mine explosion',
2114 kx='known experimental explosion',
2115 sx='suspected experimental explosion',
2116 kn='known nuclear explosion',
2117 sn='suspected nuclear explosion',
2118 ls='landslide',
2119 de='??',
2120 fe='??',)
2123class Origin(Block):
2124 _format = [
2125 E(1, 22, x_date_time_2frac),
2126 E(23, 23, 'a1?'),
2127 E(25, 29, 'f5.2'),
2128 E(31, 35, 'f5.2'),
2129 E(37, 44, 'f8.4'),
2130 E(46, 54, 'f9.4'),
2131 E(55, 55, 'a1?'),
2132 E(57, 60, x_scaled('f4.1', km)),
2133 E(62, 66, x_scaled('f5.1', km)),
2134 E(68, 70, x_int_angle),
2135 E(72, 76, x_scaled('f5.1', km)),
2136 E(77, 77, 'a1?'),
2137 E(79, 82, x_scaled('f4.1', km)),
2138 E(84, 87, 'i4'),
2139 E(89, 92, 'i4'),
2140 E(94, 96, x_int_angle),
2141 E(98, 103, 'f6.2'),
2142 E(105, 110, 'f6.2'),
2143 E(112, 112, 'a1?'),
2144 E(114, 114, 'a1?'),
2145 E(116, 117, 'a2?'),
2146 E(119, 127, 'a9'),
2147 E(129, 136, 'a8')]
2149 time = Timestamp.T(
2150 help='epicenter date and time')
2152 time_fixed = StringChoice.T(
2153 choices=['f'],
2154 optional=True,
2155 help='fixed flag, ``"f"`` if fixed origin time solution, '
2156 '``None`` if not')
2158 time_error = Float.T(
2159 optional=True,
2160 help='origin time error [seconds], ``None`` if fixed origin time')
2162 residual = Float.T(
2163 optional=True,
2164 help='root mean square of time residuals [seconds]')
2166 lat = Float.T(
2167 help='latitude')
2169 lon = Float.T(
2170 help='longitude')
2172 lat_lon_fixed = StringChoice.T(
2173 choices=['f'], optional=True,
2174 help='fixed flag, ``"f"`` if fixed epicenter solution, '
2175 '``None`` if not')
2177 ellipse_semi_major_axis = Float.T(
2178 optional=True,
2179 help='semi-major axis of 90% c. i. ellipse or its estimate [m], '
2180 '``None`` if fixed')
2182 ellipse_semi_minor_axis = Float.T(
2183 optional=True,
2184 help='semi-minor axis of 90% c. i. ellipse or its estimate [m], '
2185 '``None`` if fixed')
2187 ellipse_strike = Float.T(
2188 optional=True,
2189 help='strike of 90% c. i. ellipse [0-360], ``None`` if fixed')
2191 depth = Float.T(
2192 help='depth [m]')
2194 depth_fixed = StringChoice.T(
2195 choices=['f', 'd'], optional=True,
2196 help='fixed flag, ``"f"`` fixed depth station, "d" depth phases, '
2197 '``None`` if not fixed depth')
2199 depth_error = Float.T(
2200 optional=True,
2201 help='depth error [m], 90% c. i., ``None`` if fixed depth')
2203 nphases = Int.T(
2204 optional=True,
2205 help='number of defining phases')
2207 nstations = Int.T(
2208 optional=True,
2209 help='number of defining stations')
2211 azimuthal_gap = Float.T(
2212 optional=True,
2213 help='gap in azimuth coverage [deg]')
2215 distance_min = Float.T(
2216 optional=True,
2217 help='distance to closest station [deg]')
2219 distance_max = Float.T(
2220 optional=True,
2221 help='distance to furthest station [deg]')
2223 analysis_type = StringChoice.T(
2224 optional=True,
2225 choices=['a', 'm', 'g'],
2226 help='analysis type, ``"a"`` automatic, ``"m"`` manual, ``"g"`` guess')
2228 location_method = StringChoice.T(
2229 optional=True,
2230 choices=['i', 'p', 'g', 'o'],
2231 help='location method, ``"i"`` inversion, ``"p"`` pattern, '
2232 '``"g"`` ground truth, ``"o"`` other')
2234 event_type = StringChoice.T(
2235 optional=True,
2236 choices=sorted(g_event_types.keys()),
2237 help='event type, ' + ', '.join(
2238 '``"%s"`` %s' % (k, g_event_types[k])
2239 for k in sorted(g_event_types.keys())))
2241 author = String.T(help='author of the origin')
2242 origin_id = String.T(help='origin identification')
2245class OriginSection(TableSection):
2246 has_data_type_header = False
2248 table_setup = dict(
2249 header={
2250 None: (
2251 b' Date Time Err RMS Latitude Longitude '
2252 b'Smaj Smin Az Depth Err Ndef Nsta Gap mdist Mdist '
2253 b'Qual Author OrigID')},
2254 attribute='origins',
2255 end=lambda line: end_section(line, b'EVENT'),
2256 cls=Origin)
2258 origins = List.T(Origin.T())
2261class EventTitle(Block):
2262 _format = [
2263 E(1, 5, x_fixed(b'Event'), dummy=True),
2264 E(7, 14, 'a8'),
2265 E(16, 80, 'a65')]
2267 event_id = String.T()
2268 region = String.T()
2271class EventSection(Section):
2272 '''
2273 Groups Event, Arrival, ...
2274 '''
2276 event_title = EventTitle.T()
2277 origin_section = OriginSection.T()
2279 @classmethod
2280 def read(cls, reader):
2281 event_title = EventTitle.read(reader)
2282 origin_section = OriginSection.read(reader)
2283 return cls(
2284 event_title=event_title,
2285 origin_section=origin_section)
2287 def write(self, writer):
2288 self.event_title.write(writer)
2289 self.origin_section.write(writer)
2292class EventsSection(Section):
2293 '''
2294 Representation of a DATA_TYPE EVENT section.
2295 '''
2297 keyword = b'EVENT'
2299 bulletin_title = BulletinTitle.T()
2300 event_sections = List.T(EventSection.T())
2302 @classmethod
2303 def read(cls, reader):
2304 DataType.read(reader)
2305 bulletin_title = BulletinTitle.read(reader)
2306 event_sections = []
2307 while True:
2308 line = reader.readline()
2309 reader.pushback()
2310 if end_section(line):
2311 break
2313 if line.upper().startswith(b'EVENT'):
2314 event_sections.append(EventSection.read(reader))
2316 return cls(
2317 bulletin_title=bulletin_title,
2318 event_sections=event_sections,
2319 )
2321 def write(self, writer):
2322 self.write_datatype(writer)
2323 self.bulletin_title.write(writer)
2324 for event_section in self.event_sections:
2325 event_section.write(writer)
2328class BulletinSection(EventsSection):
2329 '''
2330 Representation of a DATA_TYPE BULLETIN section.
2331 '''
2333 keyword = b'BULLETIN'
2336for sec in (
2337 LogSection, ErrorLogSection, FTPLogSection, WaveformSection,
2338 NetworkSection, StationSection, ChannelSection, BeamSection,
2339 ResponseSection, OutageSection, EventsSection, BulletinSection):
2341 Section.handlers[sec.keyword] = sec
2343del sec
2346class MessageHeader(Section):
2347 '''
2348 Representation of a BEGIN/MSG_TYPE/MSG_ID/REF_ID group.
2349 '''
2351 version = String.T()
2352 type = String.T()
2353 msg_id = MsgID.T(optional=True)
2354 ref_id = RefID.T(optional=True)
2356 @classmethod
2357 def read(cls, reader):
2358 handlers = {
2359 b'BEGIN': Begin,
2360 b'MSG_TYPE': MsgType,
2361 b'MSG_ID': MsgID,
2362 b'REF_ID': RefID}
2364 blocks = {}
2365 while True:
2366 line = reader.readline()
2367 reader.pushback()
2368 ok = False
2369 for k in handlers:
2370 if line.upper().startswith(k):
2371 blocks[k] = handlers[k].read(reader)
2372 ok = True
2374 if not ok:
2375 break
2377 return MessageHeader(
2378 type=blocks[b'MSG_TYPE'].type,
2379 version=blocks[b'BEGIN'].version,
2380 msg_id=blocks.get(b'MSG_ID', None),
2381 ref_id=blocks.get(b'REF_ID', None))
2383 def write(self, writer):
2384 Begin(version=self.version).write(writer)
2385 MsgType(type=self.type).write(writer)
2386 if self.msg_id is not None:
2387 self.msg_id.write(writer)
2388 if self.ref_id is not None:
2389 self.ref_id.write(writer)
2392def parse_ff_date_time(s):
2393 toks = s.split()
2394 if len(toks) == 2:
2395 sdate, stime = toks
2396 else:
2397 sdate, stime = toks[0], ''
2399 stime += '00:00:00.000'[len(stime):]
2400 return util.str_to_time(
2401 sdate + ' ' + stime, format='%Y/%m/%d %H:%M:%S.3FRAC')
2404def string_ff_date_time(t):
2405 return util.time_to_str(t, format='%Y/%m/%d %H:%M:%S.3FRAC')
2408class TimeStamp(FreeFormatLine):
2409 '''
2410 Representation of a TIME_STAMP line.
2411 '''
2413 _format = [b'TIME_STAMP', 1]
2415 value = Timestamp.T()
2417 @classmethod
2418 def deserialize(cls, line, version_dialect):
2419 (s,) = cls.deserialize_values(line, version_dialect)
2420 return cls(value=parse_ff_date_time(s))
2422 def serialize(self, line, version_dialect):
2423 return (
2424 'TIME_STAMP %s' % string_ff_date_time(self.value)).encode('ascii')
2427class Stop(FreeFormatLine):
2428 '''
2429 Representation of a STOP line.
2430 '''
2432 _format = [b'STOP']
2434 dummy = String.T(optional=True)
2437class XW01(FreeFormatLine):
2438 '''
2439 Representation of a XW01 line (which is a relict from GSE1).
2440 '''
2442 _format = [b'XW01']
2444 dummy = String.T(optional=True)
2447re_comment = re.compile(br'^(%(.+)\s*| \((#?)(.+)\)\s*)$')
2448re_comment_usa_dmc = re.compile(br'^(%(.+)\s*| ?\((#?)(.+)\)\s*)$')
2451class Reader(object):
2452 def __init__(self, f, load_data=True, version=None, dialect=None):
2453 self._f = f
2454 self._load_data = load_data
2455 self._current_fpos = None
2456 self._current_lpos = None # "physical" line number
2457 self._current_line = None
2458 self._readline_count = 0
2459 self._pushed_back = False
2460 self._handlers = {
2461 b'DATA_TYPE ': Section,
2462 b'WID2 ': WID2Section,
2463 b'OUT2 ': OUT2Section,
2464 b'DLY2 ': DLY2Section,
2465 b'CAL2 ': CAL2Section,
2466 b'BEGIN': MessageHeader,
2467 b'STOP': Stop,
2468 b'XW01': XW01, # for compatibility with BGR dialect
2469 b'HANG:': None, # for compatibility with CNDC
2470 b'VANG:': None,
2471 }
2472 self._comment_lines = []
2473 self._time_stamps = []
2474 self.version_dialect = [version, dialect] # main version, dialect
2475 self._in_garbage = True
2477 def tell(self):
2478 return self._current_fpos
2480 def current_line_number(self):
2481 return self._current_lpos - int(self._pushed_back)
2483 def readline(self):
2484 if self._pushed_back:
2485 self._pushed_back = False
2486 return self._current_line
2488 while True:
2489 self._current_fpos = self._f.tell()
2490 self._current_lpos = self._readline_count + 1
2491 ln = self._f.readline()
2492 self._readline_count += 1
2493 if not ln:
2494 self._current_line = None
2495 return None
2497 lines = [ln.rstrip(b'\n\r')]
2498 while lines[-1].endswith(b'\\'):
2499 lines[-1] = lines[-1][:-1]
2500 ln = self._f.readline()
2501 self._readline_count += 1
2502 lines.append(ln.rstrip(b'\n\r'))
2504 self._current_line = b''.join(lines)
2506 if self.version_dialect[1] == 'USA_DMC':
2507 m_comment = re_comment_usa_dmc.match(self._current_line)
2508 else:
2509 m_comment = re_comment.match(self._current_line)
2511 if not self._current_line.strip():
2512 pass
2514 elif m_comment:
2515 comment_type = None
2516 if m_comment.group(3) == b'#':
2517 comment_type = 'ISF'
2518 elif m_comment.group(4) is not None:
2519 comment_type = 'IMS'
2521 comment = m_comment.group(2) or m_comment.group(4)
2523 self._comment_lines.append(
2524 (self._current_lpos, comment_type,
2525 str(comment.decode('ascii'))))
2527 elif self._current_line[:10].upper() == b'TIME_STAMP':
2528 self._time_stamps.append(
2529 TimeStamp.deserialize(
2530 self._current_line, self.version_dialect))
2532 else:
2533 return self._current_line
2535 def get_comments_after(self, lpos):
2536 comments = []
2537 i = len(self._comment_lines) - 1
2538 while i >= 0:
2539 if self._comment_lines[i][0] <= lpos:
2540 break
2542 comments.append(self._comment_lines[i][-1])
2543 i -= 1
2545 return comments
2547 def pushback(self):
2548 assert not self._pushed_back
2549 self._pushed_back = True
2551 def __iter__(self):
2552 return self
2554 def next(self):
2555 return self.__next__()
2557 def __next__(self):
2558 try:
2559 while True:
2560 line = self.readline()
2561 if line is None:
2562 raise StopIteration()
2564 ignore = False
2565 for k in self._handlers:
2566 if line.upper().startswith(k):
2567 if self._handlers[k] is None:
2568 ignore = True
2569 break
2571 self.pushback()
2572 sec = self._handlers[k].read(self)
2573 if isinstance(sec, Stop):
2574 self._in_garbage = True
2575 else:
2576 self._in_garbage = False
2577 return sec
2579 if not self._in_garbage and not ignore:
2580 raise DeserializeError('unexpected line')
2582 except DeserializeError as e:
2583 e.set_context(
2584 self._current_lpos,
2585 self._current_line,
2586 self.version_dialect)
2587 raise
2590class Writer(object):
2592 def __init__(self, f, version='GSE2.1', dialect=None):
2593 self._f = f
2594 self.version_dialect = [version, dialect]
2596 def write(self, section):
2597 section.write(self)
2599 def writeline(self, line):
2600 self._f.write(line.rstrip())
2601 self._f.write(b'\n')
2604def write_string(sections):
2605 from io import BytesIO
2606 f = BytesIO()
2607 w = Writer(f)
2608 for section in sections:
2609 w.write(section)
2611 return f.getvalue()
2614def iload_fh(f, **kwargs):
2615 '''
2616 Load IMS/GSE2 records from open file handle.
2617 '''
2618 try:
2619 r = Reader(f, **kwargs)
2620 for section in r:
2621 yield section
2623 except DeserializeError as e:
2624 raise FileLoadError(e)
2627def iload_string(s, **kwargs):
2628 '''
2629 Read IMS/GSE2 sections from bytes string.
2630 '''
2632 from io import BytesIO
2633 f = BytesIO(s)
2634 return iload_fh(f, **kwargs)
2637iload_filename, iload_dirname, iload_glob, iload = util.make_iload_family(
2638 iload_fh, 'IMS/GSE2', ':py:class:`Section`')
2641def dump_fh(sections, f):
2642 '''
2643 Dump IMS/GSE2 sections to open file handle.
2644 '''
2645 try:
2646 w = Writer(f)
2647 for section in sections:
2648 w.write(section)
2650 except SerializeError as e:
2651 raise FileSaveError(e)
2654def dump_string(sections):
2655 '''
2656 Write IMS/GSE2 sections to string.
2657 '''
2659 from io import BytesIO
2660 f = BytesIO()
2661 dump_fh(sections, f)
2662 return f.getvalue()
2665if __name__ == '__main__':
2666 from optparse import OptionParser
2668 usage = 'python -m pyrocko.ims <filenames>'
2670 util.setup_logging('pyrocko.ims.__main__', 'warning')
2672 description = '''
2673 Read and print IMS/GSE2 records.
2674 '''
2676 parser = OptionParser(
2677 usage=usage,
2678 description=description,
2679 formatter=util.BetterHelpFormatter())
2681 parser.add_option(
2682 '--version',
2683 dest='version',
2684 choices=g_versions,
2685 help='inial guess for version')
2687 parser.add_option(
2688 '--dialect',
2689 dest='dialect',
2690 choices=g_dialects,
2691 help='inial guess for dialect')
2693 parser.add_option(
2694 '--load-data',
2695 dest='load_data',
2696 action='store_true',
2697 help='unpack data samples')
2699 parser.add_option(
2700 '--out-version',
2701 dest='out_version',
2702 choices=g_versions,
2703 help='output format version')
2705 parser.add_option(
2706 '--out-dialect',
2707 dest='out_dialect',
2708 choices=g_dialects,
2709 help='output format dialect')
2711 (options, args) = parser.parse_args(sys.argv[1:])
2713 for fn in args:
2714 with open(fn, 'rb') as f:
2716 r = Reader(f, load_data=options.load_data,
2717 version=options.version, dialect=options.dialect)
2719 w = None
2720 if options.out_version is not None:
2721 w = Writer(
2722 sys.stdout, version=options.out_version,
2723 dialect=options.out_dialect)
2725 for sec in r:
2726 if not w:
2727 print(sec)
2729 else:
2730 w.write(sec)
2732 if isinstance(sec, WID2Section) and options.load_data:
2733 tr = sec.pyrocko_trace(checksum_error='warn')