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'''
9import sys
10import re
11import logging
13from . import util
14from .io_common import FileLoadError, FileSaveError
15from pyrocko.guts import (
16 Object, String, StringChoice, Timestamp, Int, Float, List, Bool, Complex,
17 ValidationError)
19try:
20 range = xrange
21except NameError:
22 pass
24logger = logging.getLogger('pyrocko.io.ims')
26km = 1000.
27nm_per_s = 1.0e-9
29g_versions = ('GSE2.0', 'GSE2.1', 'IMS1.0')
30g_dialects = ('NOR_NDC', 'USA_DMC')
33class SerializeError(Exception):
34 '''
35 Raised when serialization of an IMS/GSE2 object fails.
36 '''
37 pass
40class DeserializeError(Exception):
41 '''
42 Raised when deserialization of an IMS/GSE2 object fails.
43 '''
45 def __init__(self, *args, **kwargs):
46 Exception.__init__(self, *args)
47 self._line_number = None
48 self._line = None
49 self._position = kwargs.get('position', None)
50 self._format = kwargs.get('format', None)
51 self._version_dialect = None
53 def set_context(self, line_number, line, version_dialect):
54 self._line_number = line_number
55 self._line = line
56 self._version_dialect = version_dialect
58 def __str__(self):
59 lst = [Exception.__str__(self)]
60 if self._version_dialect is not None:
61 lst.append('format version: %s' % self._version_dialect[0])
62 lst.append('dialect: %s' % self._version_dialect[1])
63 if self._line_number is not None:
64 lst.append('line number: %i' % self._line_number)
65 if self._line is not None:
66 lst.append('line content:\n%s' % (self._line.decode('ascii') or
67 '*** line is empty ***'))
69 if self._position is not None:
70 if self._position[1] is None:
71 length = max(1, len(self._line or '') - self._position[0])
72 else:
73 length = self._position[1]
75 lst.append(' ' * self._position[0] + '^' * length)
77 if self._format is not None:
78 i = 0
79 f = []
80 j = 1
81 for element in self._format:
82 if element.length != 0:
83 f.append(' ' * (element.position - i))
84 if element.length is not None:
85 f.append(str(j % 10) * element.length)
86 i = element.position + element.length
87 else:
88 f.append(str(j % 10) + '...')
89 i = element.position + 4
91 j += 1
93 lst.append(''.join(f))
95 return '\n'.join(lst)
98def float_or_none(x):
99 if x.strip():
100 return float(x)
101 else:
102 return None
105def int_or_none(x):
106 if x.strip():
107 return int(x)
108 else:
109 return None
112def float_to_string(fmt):
113 ef = fmt[0]
114 assert ef in 'ef'
115 ln, d = map(int, fmt[1:].split('.'))
116 pfmts = ['%%%i.%i%s' % (ln, dsub, ef) for dsub in range(d, -1, -1)]
117 blank = b' ' * ln
119 def func(v):
120 if v is None:
121 return blank
123 for pfmt in pfmts:
124 s = pfmt % v
125 if len(s) == ln:
126 return s.encode('ascii')
128 raise SerializeError('format="%s", value=%s' % (pfmt, repr(v)))
130 return func
133def int_to_string(fmt):
134 assert fmt[0] == 'i'
135 pfmt = '%'+fmt[1:]+'i'
136 ln = int(fmt[1:])
137 blank = b' ' * ln
139 def func(v):
140 if v is None:
141 return blank
143 s = pfmt % v
144 if len(s) == ln:
145 return s.encode('ascii')
146 else:
147 raise SerializeError('format="%s", value=%s' % (pfmt, repr(v)))
149 return func
152def deserialize_string(fmt):
153 if fmt.endswith('?'):
154 def func(s):
155 if s.strip():
156 return str(s.rstrip().decode('ascii'))
157 else:
158 return None
159 else:
160 def func(s):
161 return str(s.rstrip().decode('ascii'))
163 return func
166def serialize_string(fmt):
167 if fmt.endswith('+'):
168 more_ok = True
169 else:
170 more_ok = False
172 fmt = fmt.rstrip('?+')
174 assert fmt[0] == 'a'
175 ln = int(fmt[1:])
177 def func(v):
178 if v is None:
179 v = b''
180 else:
181 v = v.encode('ascii')
183 s = v.ljust(ln)
184 if more_ok or len(s) == ln:
185 return s
186 else:
187 raise SerializeError('max string length: %i, value="%s"' % ln, v)
189 return func
192def rstrip_string(v):
193 return v.rstrip()
196def x_fixed(expect):
197 def func():
198 def parse(s):
199 if s != expect:
200 raise DeserializeError(
201 'expected="%s", value="%s"' % (expect, s))
202 return s
204 def string(s):
205 return expect
207 return parse, string
209 func.width = len(expect)
210 func.help_type = 'Keyword: %s' % expect
211 return func
214def x_scaled(fmt, factor):
215 def func():
216 to_string = float_to_string(fmt)
218 def parse(s):
219 x = float_or_none(s)
220 if x is None:
221 return None
222 else:
223 return x * factor
225 def string(v):
226 if v is None:
227 return to_string(None)
228 else:
229 return to_string(v/factor)
231 return parse, string
233 func.width = int(fmt[1:].split('.')[0])
234 func.help_type = 'float'
235 return func
238def x_int_angle():
239 def string(v):
240 if v is None:
241 return b' '
242 else:
243 return ('%3i' % (int(round(v)) % 360)).encode('ascii')
245 return float_or_none, string
248x_int_angle.width = 3
249x_int_angle.help_type = 'int [0, 360]'
252def x_substitute(value):
253 def func():
254 def parse(s):
255 assert s == b''
256 return value
258 def string(s):
259 return b''
261 return parse, string
263 func.width = 0
264 func.help_type = 'Not present in this file version.'
265 return func
268def fillup_zeros(s, fmt):
269 s = s.rstrip()
270 if not s:
271 return s
273 if fmt == '%Y/%m/%d %H:%M:%S.3FRAC':
274 return s + '0000/01/01 00:00:00.000'[len(s):]
275 elif fmt == '%Y/%m/%d %H:%M:%S.2FRAC':
276 return s + '0000/01/01 00:00:00.00'[len(s):]
278 return s
281def x_date_time(fmt='%Y/%m/%d %H:%M:%S.3FRAC'):
282 def parse(s):
283 s = str(s.decode('ascii'))
284 try:
285 s = fillup_zeros(s, fmt)
286 return util.str_to_time(s, format=fmt)
288 except Exception:
289 # iris sets this dummy end dates and they don't fit into 32bit
290 # time stamps
291 if fmt[:2] == '%Y' and s[:4] in ('2599', '2045'):
292 return None
294 elif fmt[6:8] == '%Y' and s[6:10] in ('2599', '2045'):
295 return None
297 raise DeserializeError('expected date, value="%s"' % s)
299 def string(s):
300 return util.time_to_str(s, format=fmt).encode('ascii')
302 return parse, string
305x_date_time.width = 23
306x_date_time.help_type = 'YYYY/MM/DD HH:MM:SS.FFF'
309def x_date():
310 return x_date_time(fmt='%Y/%m/%d')
313x_date.width = 10
314x_date.help_type = 'YYYY/MM/DD'
317def x_date_iris():
318 return x_date_time(fmt='%m/%d/%Y')
321x_date_iris.width = 10
322x_date_iris.help_type = 'MM/DD/YYYY'
325def x_date_time_no_seconds():
326 return x_date_time(fmt='%Y/%m/%d %H:%M')
329x_date_time_no_seconds.width = 16
330x_date_time_no_seconds.help_type = 'YYYY/MM/DD HH:MM'
333def x_date_time_2frac():
334 return x_date_time(fmt='%Y/%m/%d %H:%M:%S.2FRAC')
337x_date_time_2frac.width = 22
338x_date_time_2frac.help_type = 'YYYY/MM/DD HH:MM:SS.FF'
341def x_yesno():
342 def parse(s):
343 if s == b'y':
344 return True
345 elif s == b'n':
346 return False
347 else:
348 raise DeserializeError('"y" on "n" expected')
350 def string(b):
351 return [b'n', b'y'][int(b)]
353 return parse, string
356x_yesno.width = 1
357x_yesno.help_type = 'yes/no'
360def optional(x_func):
362 def func():
363 parse, string = x_func()
365 def parse_optional(s):
366 if s.strip():
367 return parse(s)
368 else:
369 return None
371 def string_optional(s):
372 if s is None:
373 return b' ' * x_func.width
374 else:
375 return string(s)
377 return parse_optional, string_optional
379 func.width = x_func.width
380 func.help_type = 'optional %s' % x_func.help_type
382 return func
385class E(object):
386 def __init__(self, begin, end, fmt, dummy=False):
387 self.advance = 1
388 if dummy:
389 self.advance = 0
391 self.position = begin - 1
392 if end is not None:
393 self.length = end - begin + 1
394 else:
395 self.length = None
397 self.end = end
399 if isinstance(fmt, str):
400 t = fmt[0]
401 if t in 'ef':
402 self.parse = float_or_none
403 self.string = float_to_string(fmt)
404 ln = int(fmt[1:].split('.')[0])
405 self.help_type = 'float'
406 elif t == 'a':
407 self.parse = deserialize_string(fmt)
408 self.string = serialize_string(fmt)
409 ln = int(fmt[1:].rstrip('+?'))
410 self.help_type = 'string'
411 elif t == 'i':
412 self.parse = int_or_none
413 self.string = int_to_string(fmt)
414 ln = int(fmt[1:])
415 self.help_type = 'integer'
416 else:
417 assert False, 'invalid format: %s' % t
419 assert self.length is None or ln == self.length, \
420 'inconsistent length for pos=%i, fmt=%s' \
421 % (self.position, fmt)
423 else:
424 self.parse, self.string = fmt()
425 self.help_type = fmt.help_type
428def end_section(line, extra=None):
429 if line is None:
430 return True
432 ul = line.upper()
433 return ul.startswith(b'DATA_TYPE') or ul.startswith(b'STOP') or \
434 (extra is not None and ul.startswith(extra))
437class Section(Object):
438 '''
439 Base class for top level sections in IMS/GSE2 files.
441 Sections as understood by this implementation typically correspond to a
442 DATA_TYPE section in IMS/GSE2 but for some types a finer granularity has
443 been chosen.
444 '''
446 handlers = {} # filled after section have been defined below
448 @classmethod
449 def read(cls, reader):
450 datatype = DataType.read(reader)
451 reader.pushback()
452 return Section.handlers[
453 datatype.type.upper().encode('ascii')].read(reader)
455 def write_datatype(self, writer):
456 datatype = DataType(
457 type=self.keyword.decode('ascii'),
458 format=writer.version_dialect[0])
459 datatype.write(writer)
461 @classmethod
462 def read_table(cls, reader, expected_header, block_cls, end=end_section):
464 header = reader.readline()
465 if not header.upper().startswith(expected_header.upper()):
466 raise DeserializeError(
467 'invalid table header line, expected:\n'
468 '%s\nfound: %s ' % (expected_header, header))
470 while True:
471 line = reader.readline()
472 reader.pushback()
473 if end(line):
474 break
476 yield block_cls.read(reader)
478 def write_table(self, writer, header, blocks):
479 writer.writeline(header)
480 for block in blocks:
481 block.write(writer)
484def get_versioned(x, version_dialect):
485 if isinstance(x, dict):
486 for v in (tuple(version_dialect), version_dialect[0], None):
487 if v in x:
488 return x[v]
489 else:
490 return x
493class Block(Object):
494 '''
495 Base class for IMS/GSE2 data blocks / lines.
497 Blocks as understood by this implementation usually correspond to
498 individual logical lines in the IMS/GSE2 file.
499 '''
501 def values(self):
502 return list(self.T.ivals(self))
504 @classmethod
505 def format(cls, version_dialect):
506 return get_versioned(cls._format, version_dialect)
508 def serialize(self, version_dialect):
509 ivalue = 0
510 out = []
511 values = self.values()
512 for element in self.format(version_dialect):
513 if element.length != 0:
514 out.append((element.position, element.string(values[ivalue])))
515 ivalue += element.advance
517 out.sort()
519 i = 0
520 slist = []
521 for (position, s) in out:
522 slist.append(b' ' * (position - i))
523 slist.append(s)
524 i = position + len(s)
526 return b''.join(slist)
528 @classmethod
529 def deserialize_values(cls, line, version_dialect):
530 values = []
531 for element in cls.format(version_dialect):
532 try:
533 val = element.parse(
534 line[element.position:element.end])
536 if element.advance != 0:
537 values.append(val)
538 except Exception:
539 raise DeserializeError(
540 'Cannot parse %s' % (
541 element.help_type),
542 position=(element.position, element.length),
543 version=version_dialect[0],
544 dialect=version_dialect[1],
545 format=cls.format(version_dialect))
547 return values
549 @classmethod
550 def validated(cls, *args, **kwargs):
551 obj = cls(*args, **kwargs)
552 try:
553 obj.validate()
554 except ValidationError as e:
555 raise DeserializeError(str(e))
557 return obj
559 @classmethod
560 def regularized(cls, *args, **kwargs):
561 obj = cls(*args, **kwargs)
562 try:
563 obj.regularize()
564 except ValidationError as e:
565 raise DeserializeError(str(e))
567 return obj
569 @classmethod
570 def deserialize(cls, line, version_dialect):
571 values = cls.deserialize_values(line, version_dialect)
572 return cls.validated(**dict(zip(cls.T.propnames, values)))
574 @classmethod
575 def read(cls, reader):
576 line = reader.readline()
577 return cls.deserialize(line, reader.version_dialect)
579 def write(self, writer):
580 s = self.serialize(writer.version_dialect)
581 writer.writeline(s)
584class FreeFormatLine(Block):
585 '''
586 Base class for IMS/GSE2 free format lines.
587 '''
589 @classmethod
590 def deserialize_values(cls, line, version_dialect):
591 format = cls.format(version_dialect)
592 values = line.split(None, len(format)-1)
594 values_weeded = []
595 for x, v in zip(format, values):
596 if isinstance(x, bytes):
597 if v.upper() != x:
598 raise DeserializeError(
599 'expected keyword: %s, found %s' % (x, v.upper()))
601 else:
602 if isinstance(x, tuple):
603 x, (parse, _) = x
604 v = parse(v)
606 values_weeded.append((x, v))
608 values_weeded.sort()
609 return [str(xv[1].decode('ascii')) for xv in values_weeded]
611 @classmethod
612 def deserialize(cls, line, version_dialect):
613 values = cls.deserialize_values(line, version_dialect)
614 propnames = cls.T.propnames
615 stuff = dict(zip(propnames, values))
616 return cls.regularized(**stuff)
618 def serialize(self, version_dialect):
619 names = self.T.propnames
620 props = self.T.properties
621 out = []
622 for x in self.format(version_dialect):
623 if isinstance(x, bytes):
624 out.append(x.decode('ascii'))
625 else:
626 if isinstance(x, tuple):
627 x, (_, string) = x
628 v = string(getattr(self, names[x-1]))
629 else:
630 v = getattr(self, names[x-1])
632 if v is None:
633 break
635 out.append(props[x-1].to_save(v))
637 return ' '.join(out).encode('ascii')
640class DataType(Block):
641 '''
642 Representation of a DATA_TYPE line.
643 '''
645 type = String.T()
646 subtype = String.T(optional=True)
647 format = String.T()
648 subformat = String.T(optional=True)
650 @classmethod
651 def deserialize(cls, line, version_dialect):
652 pat = br'DATA_TYPE +([^ :]+)(:([^ :]+))?( +([^ :]+)(:([^ :]+))?)?'
653 m = re.match(pat, line)
654 if not m:
655 raise DeserializeError('invalid DATA_TYPE line')
657 return cls.validated(
658 type=str((m.group(1) or b'').decode('ascii')),
659 subtype=str((m.group(3) or b'').decode('ascii')),
660 format=str((m.group(5) or b'').decode('ascii')),
661 subformat=str((m.group(7) or b'').decode('ascii')))
663 def serialize(self, version_dialect):
664 s = self.type
665 if self.subtype:
666 s += ':' + self.subtype
668 f = self.format
669 if self.subformat:
670 f += ':' + self.subformat
672 return ('DATA_TYPE %s %s' % (s, f)).encode('ascii')
674 @classmethod
675 def read(cls, reader):
676 line = reader.readline()
677 datatype = cls.deserialize(line, reader.version_dialect)
678 reader.version_dialect[0] = datatype.format
679 return datatype
681 def write(self, writer):
682 s = self.serialize(writer.version_dialect)
683 writer.version_dialect[0] = self.format
684 writer.writeline(s)
687class FTPFile(FreeFormatLine):
688 '''
689 Representation of an FTP_FILE line.
690 '''
692 _format = [b'FTP_FILE', 1, 2, 3, 4]
694 net_address = String.T()
695 login_mode = StringChoice.T(choices=('USER', 'GUEST'), ignore_case=True)
696 directory = String.T()
697 file = String.T()
700class WaveformSubformat(StringChoice):
701 choices = ['INT', 'CM6', 'CM8', 'AU6', 'AU8']
702 ignore_case = True
705class WID2(Block):
706 '''
707 Representation of a WID2 line.
708 '''
710 _format = [
711 E(1, 4, x_fixed(b'WID2'), dummy=True),
712 E(6, 28, x_date_time),
713 E(30, 34, 'a5'),
714 E(36, 38, 'a3'),
715 E(40, 43, 'a4'),
716 E(45, 47, 'a3'),
717 E(49, 56, 'i8'),
718 E(58, 68, 'f11.6'),
719 E(70, 79, x_scaled('e10.2', nm_per_s)),
720 E(81, 87, 'f7.3'),
721 E(89, 94, 'a6?'),
722 E(96, 100, 'f5.1'),
723 E(102, 105, 'f4.1')
724 ]
726 time = Timestamp.T()
727 station = String.T(help='station code (5 characters)')
728 channel = String.T(help='channel code (3 characters)')
729 location = String.T(
730 default='', optional=True,
731 help='location code (aux_id, 4 characters)')
732 sub_format = WaveformSubformat.T(default='CM6')
733 nsamples = Int.T(default=0)
734 sample_rate = Float.T(default=1.0)
735 calibration_factor = Float.T(
736 optional=True,
737 help='system sensitivity (m/count) at reference period '
738 '(calibration_period)')
739 calibration_period = Float.T(
740 optional=True,
741 help='calibration reference period [s]')
742 instrument_type = String.T(
743 default='', optional=True, help='instrument type (6 characters)')
744 horizontal_angle = Float.T(
745 optional=True,
746 help='horizontal orientation of sensor, clockwise from north [deg]')
747 vertical_angle = Float.T(
748 optional=True,
749 help='vertical orientation of sensor from vertical [deg]')
752class OUT2(Block):
753 '''
754 Representation of an OUT2 line.
755 '''
757 _format = [
758 E(1, 4, x_fixed(b'OUT2'), dummy=True),
759 E(6, 28, x_date_time),
760 E(30, 34, 'a5'),
761 E(36, 38, 'a3'),
762 E(40, 43, 'a4'),
763 E(45, 55, 'f11.3')
764 ]
766 time = Timestamp.T()
767 station = String.T(help='station code (5 characters)')
768 channel = String.T(help='channel code (3 characters)')
769 location = String.T(
770 default='', optional=True,
771 help='location code (aux_id, 4 characters)')
772 duration = Float.T()
775class DLY2(Block):
776 '''
777 Representation of a DLY2 line.
778 '''
780 _format = [
781 E(1, 4, x_fixed(b'DLY2'), dummy=True),
782 E(6, 28, x_date_time),
783 E(30, 34, 'a5'),
784 E(36, 38, 'a3'),
785 E(40, 43, 'a4'),
786 E(45, 55, 'f11.3')
787 ]
789 time = Timestamp.T()
790 station = String.T(help='station code (5 characters)')
791 channel = String.T(help='channel code (3 characters)')
792 location = String.T(
793 default='', optional=True,
794 help='location code (aux_id, 4 characters)')
795 queue_duration = Float.T(help='duration of queue [s]')
798class DAT2(Block):
799 '''
800 Representation of a DAT2 line.
801 '''
803 _format = [
804 E(1, 4, x_fixed(b'DAT2'), dummy=True)
805 ]
807 raw_data = List.T(String.T())
809 @classmethod
810 def read(cls, reader):
811 line = reader.readline()
812 dat2 = cls.deserialize(line, reader.version_dialect)
813 while True:
814 line = reader.readline()
815 if line.upper().startswith(b'CHK2 '):
816 reader.pushback()
817 break
818 else:
819 if reader._load_data:
820 dat2.raw_data.append(line.strip())
822 return dat2
824 def write(self, writer):
825 Block.write(self, writer)
826 for line in self.raw_data:
827 writer.writeline(line)
830class STA2(Block):
831 '''
832 Representation of a STA2 line.
833 '''
835 _format = [
836 E(1, 4, x_fixed(b'STA2'), dummy=True),
837 E(6, 14, 'a9'),
838 E(16, 24, 'f9.5'),
839 E(26, 35, 'f10.5'),
840 E(37, 48, 'a12'),
841 E(50, 54, x_scaled('f5.3', km)),
842 E(56, 60, x_scaled('f5.3', km))
843 ]
845 # the standard requires lat, lon, elevation and depth, we define them as
846 # optional, however
848 network = String.T(help='network code (9 characters)')
849 lat = Float.T(optional=True)
850 lon = Float.T(optional=True)
851 coordinate_system = String.T(default='WGS-84')
852 elevation = Float.T(optional=True, help='elevation [m]')
853 depth = Float.T(optional=True, help='emplacement depth [m]')
856class CHK2(Block):
857 '''
858 Representation of a CHK2 line.
859 '''
861 _format = [
862 E(1, 4, x_fixed(b'CHK2'), dummy=True),
863 E(6, 13, 'i8')
864 ]
866 checksum = Int.T()
869class EID2(Block):
870 '''
871 Representation of an EID2 line.
872 '''
874 _format = [
875 E(1, 4, x_fixed(b'EID2'), dummy=True),
876 E(6, 13, 'a8'),
877 E(15, 23, 'a9'),
878 ]
880 event_id = String.T(help='event ID (8 characters)')
881 bulletin_type = String.T(help='bulletin type (9 characters)')
884class BEA2(Block):
885 '''
886 Representation of a BEA2 line.
887 '''
889 _format = [
890 E(1, 4, x_fixed(b'BEA2'), dummy=True),
891 E(6, 17, 'a12'),
892 E(19, 23, 'f5.1'),
893 E(25, 29, 'f5.1')]
895 beam_id = String.T(help='beam ID (12 characters)')
896 azimuth = Float.T()
897 slowness = Float.T()
900class Network(Block):
901 '''
902 Representation of an entry in a NETWORK section.
903 '''
905 _format = [
906 E(1, 9, 'a9'),
907 E(11, None, 'a64+')]
909 network = String.T(help='network code (9 characters)')
910 description = String.T(help='description')
913class Station(Block):
914 '''
915 Representation of an entry in a STATION section.
916 '''
918 _format = {
919 None: [
920 E(1, 9, 'a9'),
921 E(11, 15, 'a5'),
922 E(17, 20, 'a4'),
923 E(22, 30, 'f9.5'),
924 E(32, 41, 'f10.5'),
925 E(43, 54, 'a12'),
926 E(56, 60, x_scaled('f5.3', km)),
927 E(62, 71, x_date),
928 E(73, 82, optional(x_date))
929 ],
930 'GSE2.0': [
931 E(0, -1, x_substitute('')),
932 E(1, 5, 'a5'),
933 E(7, 10, 'a4'),
934 E(12, 20, 'f9.5'),
935 E(22, 31, 'f10.5'),
936 E(32, 31, x_substitute('WGS-84')),
937 E(33, 39, x_scaled('f7.3', km)),
938 E(41, 50, x_date),
939 E(52, 61, optional(x_date))]}
941 _format['IMS1.0', 'USA_DMC'] = list(_format[None])
942 _format['IMS1.0', 'USA_DMC'][-2:] = [
943 E(62, 71, x_date_iris),
944 E(73, 82, optional(x_date_iris))]
946 network = String.T(help='network code (9 characters)')
947 station = String.T(help='station code (5 characters)')
948 type = String.T(
949 help='station type (4 characters) '
950 '(1C: single component, 3C: three-component, '
951 'hfa: high frequency array, lpa: long period array)')
952 lat = Float.T()
953 lon = Float.T()
954 coordinate_system = String.T(default='WGS-84')
955 elevation = Float.T(help='elevation [m]')
956 tmin = Timestamp.T()
957 tmax = Timestamp.T(optional=True)
960class Channel(Block):
961 '''
962 Representation of an entry in a CHANNEL section.
963 '''
965 _format = {
966 None: [
967 E(1, 9, 'a9'),
968 E(11, 15, 'a5'),
969 E(17, 19, 'a3'),
970 E(21, 24, 'a4'),
971 E(26, 34, 'f9.5'),
972 E(36, 45, 'f10.5'),
973 E(47, 58, 'a12'),
974 E(60, 64, x_scaled('f5.3', km)),
975 E(66, 70, x_scaled('f5.3', km)),
976 E(72, 77, 'f6.1'),
977 E(79, 83, 'f5.1'),
978 E(85, 95, 'f11.6'),
979 E(97, 102, 'a6'),
980 E(105, 114, x_date),
981 E(116, 125, optional(x_date))],
982 'GSE2.0': [
983 E(0, -1, x_substitute('')),
984 E(1, 5, 'a5'),
985 E(7, 9, 'a3'),
986 E(11, 14, 'a4'),
987 E(16, 24, 'f9.5'),
988 E(26, 35, 'f10.5'),
989 E(32, 31, x_substitute('WGS-84')),
990 E(37, 43, x_scaled('f7.3', km)),
991 E(45, 50, x_scaled('f6.3', km)),
992 E(52, 57, 'f6.1'),
993 E(59, 63, 'f5.1'),
994 E(65, 75, 'f11.6'),
995 E(77, 83, 'a7'),
996 E(85, 94, x_date),
997 E(96, 105, optional(x_date))]}
999 # norsar plays its own game...
1000 _format['GSE2.0', 'NOR_NDC'] = list(_format['GSE2.0'])
1001 _format['GSE2.0', 'NOR_NDC'][-2:] = [
1002 E(85, 100, x_date_time_no_seconds),
1003 E(102, 117, optional(x_date_time_no_seconds))]
1005 # also iris plays its own game...
1006 _format['IMS1.0', 'USA_DMC'] = list(_format[None])
1007 _format['IMS1.0', 'USA_DMC'][-2:] = [
1008 E(105, 114, x_date_iris),
1009 E(116, 125, optional(x_date_iris))]
1011 network = String.T(help='network code (9 characters)')
1012 station = String.T(help='station code (5 characters)')
1013 channel = String.T(help='channel code (3 characters)')
1014 location = String.T(
1015 default='', optional=True,
1016 help='location code (aux_id, 4 characters)')
1017 lat = Float.T(optional=True)
1018 lon = Float.T(optional=True)
1019 coordinate_system = String.T(default='WGS-84')
1020 elevation = Float.T(optional=True, help='elevation [m]')
1021 depth = Float.T(optional=True, help='emplacement depth [m]')
1022 horizontal_angle = Float.T(
1023 optional=True,
1024 help='horizontal orientation of sensor, clockwise from north [deg]')
1025 vertical_angle = Float.T(
1026 optional=True,
1027 help='vertical orientation of sensor from vertical [deg]')
1028 sample_rate = Float.T()
1029 instrument_type = String.T(
1030 default='', optional=True, help='instrument type (6 characters)')
1031 tmin = Timestamp.T()
1032 tmax = Timestamp.T(optional=True)
1035class BeamGroup(Block):
1036 '''
1037 Representation of an entry in a BEAM group table.
1038 '''
1040 _format = [
1041 E(1, 8, 'a8'),
1042 E(10, 14, 'a5'),
1043 E(16, 18, 'a3'),
1044 E(20, 23, 'a4'),
1045 E(25, 27, 'i3'),
1046 E(29, 37, 'f9.5')]
1048 beam_group = String.T(help='beam group (8 characters)')
1049 station = String.T(help='station code (5 characters)')
1050 channel = String.T(help='channel code (3 characters)')
1051 location = String.T(
1052 default='', optional=True,
1053 help='location code (aux_id, 4 characters)')
1054 weight = Int.T(
1055 optional=True,
1056 help='weight used for this component when the beam was formed')
1057 delay = Float.T(
1058 optional=True,
1059 help='beam delay for this component [s] '
1060 '(used for meabs formed by non-plane waves)')
1063class BeamType(StringChoice):
1064 choices = ['inc', 'coh']
1065 ignore_case = True
1068class FilterType(StringChoice):
1069 choices = ['BP', 'LP', 'HP', 'BR']
1070 ignore_case = True
1073class BeamParameters(Block):
1074 '''
1075 Representation of an entry in a BEAM parameters table.
1076 '''
1078 _format = [
1079 E(1, 12, 'a12'),
1080 E(14, 21, 'a8'),
1081 E(23, 25, 'a3'),
1082 E(27, 27, x_yesno),
1083 E(29, 33, 'f5.1'),
1084 E(35, 39, 'f5.3'), # standard says f5.1 -999.0 is vertical beam
1085 E(41, 48, 'a8'),
1086 E(50, 55, 'f6.2'),
1087 E(57, 62, 'f6.2'),
1088 E(64, 65, 'i2'),
1089 E(67, 67, x_yesno),
1090 E(69, 70, 'a2'),
1091 E(72, 81, x_date),
1092 E(83, 92, optional(x_date))]
1094 beam_id = String.T()
1095 beam_group = String.T()
1096 type = BeamType.T()
1097 is_rotated = Bool.T(help='rotation flag')
1098 azimuth = Float.T(
1099 help='azimuth used to steer the beam [deg] (clockwise from North)')
1100 slowness = Float.T(
1101 help='slowness used to steer the beam [s/deg]')
1102 phase = String.T(
1103 help='phase used to set the beam slowness for origin-based beams '
1104 '(8 characters)')
1105 filter_fmin = Float.T(
1106 help='low frequency cut-off for the beam filter [Hz]')
1107 filter_fmax = Float.T(
1108 help='high frequency cut-off for the beam filter [Hz]')
1109 filter_order = Int.T(
1110 help='order of the beam filter')
1111 filter_is_zero_phase = Bool.T(
1112 help='flag to indicate zero-phase filtering')
1113 filter_type = FilterType.T(
1114 help='type of filtering')
1115 tmin = Timestamp.T(
1116 help='start date of beam use')
1117 tmax = Timestamp.T(
1118 optional=True,
1119 help='end date of beam use')
1122class OutageReportPeriod(Block):
1123 '''
1124 Representation of a the report period of an OUTAGE section.
1125 '''
1127 _format = [
1128 E(1, 18, x_fixed(b'Report period from'), dummy=True),
1129 E(20, 42, x_date_time),
1130 E(44, 45, x_fixed(b'to'), dummy=True),
1131 E(47, 69, x_date_time)]
1133 tmin = Timestamp.T()
1134 tmax = Timestamp.T()
1137class Outage(Block):
1138 '''
1139 Representation of an entry in the OUTAGE section table.
1140 '''
1141 _format = [
1142 E(1, 9, 'a9'),
1143 E(11, 15, 'a5'),
1144 E(17, 19, 'a3'),
1145 E(21, 24, 'a4'),
1146 E(26, 48, x_date_time),
1147 E(50, 72, x_date_time),
1148 E(74, 83, 'f10.3'),
1149 E(85, None, 'a48+')]
1151 network = String.T(help='network code (9 characters)')
1152 station = String.T(help='station code (5 characters)')
1153 channel = String.T(help='channel code (3 characters)')
1154 location = String.T(
1155 default='', optional=True,
1156 help='location code (aux_id, 4 characters)')
1157 tmin = Timestamp.T()
1158 tmax = Timestamp.T()
1159 duration = Float.T()
1160 comment = String.T()
1163class CAL2(Block):
1164 '''
1165 Representation of a CAL2 line.
1166 '''
1168 _format = {
1169 None: [
1170 E(1, 4, x_fixed(b'CAL2'), dummy=True),
1171 E(6, 10, 'a5'),
1172 E(12, 14, 'a3'),
1173 E(16, 19, 'a4'),
1174 E(21, 26, 'a6'),
1175 E(28, 42, x_scaled('e15.8', nm_per_s)), # standard: e15.2
1176 E(44, 50, 'f7.3'),
1177 E(52, 62, 'f11.5'),
1178 E(64, 79, x_date_time_no_seconds),
1179 E(81, 96, optional(x_date_time_no_seconds))],
1180 'GSE2.0': [
1181 E(1, 4, x_fixed(b'CAL2'), dummy=True),
1182 E(6, 10, 'a5'),
1183 E(12, 14, 'a3'),
1184 E(16, 19, 'a4'),
1185 E(21, 26, 'a6'),
1186 E(28, 37, x_scaled('e10.4', nm_per_s)),
1187 E(39, 45, 'f7.3'),
1188 E(47, 56, 'f10.5'),
1189 E(58, 73, x_date_time_no_seconds),
1190 E(75, 90, optional(x_date_time_no_seconds))]}
1192 station = String.T(help='station code (5 characters)')
1193 channel = String.T(help='channel code (3 characters)')
1194 location = String.T(
1195 default='', optional=True,
1196 help='location code (aux_id, 4 characters)')
1197 instrument_type = String.T(
1198 default='', optional=True, help='instrument type (6 characters)')
1199 calibration_factor = Float.T(
1200 help='system sensitivity (m/count) at reference period '
1201 '(calibration_period)')
1202 calibration_period = Float.T(help='calibration reference period [s]')
1203 sample_rate = Float.T(help='system output sample rate [Hz]')
1204 tmin = Timestamp.T(help='effective start date and time')
1205 tmax = Timestamp.T(optional=True, help='effective end date and time')
1206 comments = List.T(String.T(optional=True))
1208 @classmethod
1209 def read(cls, reader):
1210 lstart = reader.current_line_number()
1211 line = reader.readline()
1212 obj = cls.deserialize(line, reader.version_dialect)
1213 while True:
1214 line = reader.readline()
1215 # make sure all comments are read
1216 if line is None or not line.startswith(b' '):
1217 reader.pushback()
1218 break
1220 obj.append_dataline(line, reader.version_dialect)
1222 obj.comments.extend(reader.get_comments_after(lstart))
1223 return obj
1225 def write(self, writer):
1226 s = self.serialize(writer.version_dialect)
1227 writer.writeline(s)
1228 for c in self.comments:
1229 writer.writeline((' (%s)' % c).encode('ascii'))
1232class Units(StringChoice):
1233 choices = ['V', 'A', 'C']
1234 ignore_case = True
1237class Stage(Block):
1238 '''
1239 Base class for IMS/GSE2 response stages.
1241 Available response stages are :py:class:`PAZ2`, :py:class:`FAP2`,
1242 :py:class:`GEN2`, :py:class:`DIG2`, and :py:class:`FIR2`.
1244 '''
1246 stage_number = Int.T(help='stage sequence number')
1248 @classmethod
1249 def read(cls, reader):
1250 lstart = reader.current_line_number()
1251 line = reader.readline()
1252 obj = cls.deserialize(line, reader.version_dialect)
1254 while True:
1255 line = reader.readline()
1256 if line is None or not line.startswith(b' '):
1257 reader.pushback()
1258 break
1260 obj.append_dataline(line, reader.version_dialect)
1262 obj.comments.extend(reader.get_comments_after(lstart))
1264 return obj
1266 def write(self, writer):
1267 line = self.serialize(writer.version_dialect)
1268 writer.writeline(line)
1269 self.write_datalines(writer)
1270 for c in self.comments:
1271 writer.writeline((' (%s)' % c).encode('ascii'))
1273 def write_datalines(self, writer):
1274 pass
1277class PAZ2Data(Block):
1278 '''
1279 Representation of the complex numbers in PAZ2 sections.
1280 '''
1282 _format = [
1283 E(2, 16, 'e15.8'),
1284 E(18, 32, 'e15.8')]
1286 real = Float.T()
1287 imag = Float.T()
1290class PAZ2(Stage):
1291 '''
1292 Representation of a PAZ2 line.
1293 '''
1295 _format = {
1296 None: [
1297 E(1, 4, x_fixed(b'PAZ2'), dummy=True),
1298 E(6, 7, 'i2'),
1299 E(9, 9, 'a1'),
1300 E(11, 25, 'e15.8'),
1301 E(27, 30, 'i4'),
1302 E(32, 39, 'f8.3'),
1303 E(41, 43, 'i3'),
1304 E(45, 47, 'i3'),
1305 E(49, None, 'a25+')],
1306 ('IMS1.0', 'USA_DMC'): [
1307 E(1, 4, x_fixed(b'PAZ2'), dummy=True),
1308 E(6, 7, 'i2'),
1309 E(9, 9, 'a1'),
1310 E(11, 25, 'e15.8'),
1311 E(27, 30, 'i4'),
1312 E(32, 39, 'f8.3'),
1313 E(40, 42, 'i3'),
1314 E(44, 46, 'i3'),
1315 E(48, None, 'a25+')]}
1317 output_units = Units.T(
1318 help='output units code (V=volts, A=amps, C=counts)')
1319 scale_factor = Float.T(help='scale factor [ouput units/input units]')
1320 decimation = Int.T(optional=True, help='decimation')
1321 correction = Float.T(optional=True, help='group correction applied [s]')
1322 npoles = Int.T(help='number of poles')
1323 nzeros = Int.T(help='number of zeros')
1324 description = String.T(default='', optional=True, help='description')
1326 poles = List.T(Complex.T())
1327 zeros = List.T(Complex.T())
1329 comments = List.T(String.T(optional=True))
1331 def append_dataline(self, line, version_dialect):
1332 d = PAZ2Data.deserialize(line, version_dialect)
1333 v = complex(d.real, d.imag)
1334 i = len(self.poles) + len(self.zeros)
1336 if i < self.npoles:
1337 self.poles.append(v)
1338 elif i < self.npoles + self.nzeros:
1339 self.zeros.append(v)
1340 else:
1341 raise DeserializeError(
1342 'more poles and zeros than expected')
1344 def write_datalines(self, writer):
1345 for pole in self.poles:
1346 PAZ2Data(real=pole.real, imag=pole.imag).write(writer)
1347 for zero in self.zeros:
1348 PAZ2Data(real=zero.real, imag=zero.imag).write(writer)
1351class FAP2Data(Block):
1352 '''
1353 Representation of the data tuples in FAP2 section.
1354 '''
1356 _format = [
1357 E(2, 11, 'f10.5'),
1358 E(13, 27, 'e15.8'),
1359 E(29, 32, 'i4')]
1361 frequency = Float.T()
1362 amplitude = Float.T()
1363 phase = Float.T()
1366class FAP2(Stage):
1367 '''
1368 Representation of a FAP2 line.
1369 '''
1371 _format = [
1372 E(1, 4, x_fixed(b'FAP2'), dummy=True),
1373 E(6, 7, 'i2'),
1374 E(9, 9, 'a1'),
1375 E(11, 14, 'i4'),
1376 E(16, 23, 'f8.3'),
1377 E(25, 27, 'i3'),
1378 E(29, 53, 'a25')]
1380 output_units = Units.T(
1381 help='output units code (V=volts, A=amps, C=counts)')
1382 decimation = Int.T(optional=True, help='decimation')
1383 correction = Float.T(help='group correction applied [s]')
1384 ntrip = Int.T(help='number of frequency, amplitude, phase triplets')
1385 description = String.T(default='', optional=True, help='description')
1387 frequencies = List.T(Float.T(), help='frequency [Hz]')
1388 amplitudes = List.T(
1389 Float.T(), help='amplitude [input untits/output units]')
1390 phases = List.T(Float.T(), help='phase delay [degrees]')
1392 comments = List.T(String.T(optional=True))
1394 def append_dataline(self, line, version_dialect):
1395 d = FAP2Data.deserialize(line, version_dialect)
1396 self.frequencies.append(d.frequency)
1397 self.amplitudes.append(d.amplitude)
1398 self.phases.append(d.phase)
1400 def write_datalines(self, writer):
1401 for frequency, amplitude, phase in zip(
1402 self.frequencies, self.amplitudes, self.phases):
1404 FAP2Data(
1405 frequency=frequency,
1406 amplitude=amplitude,
1407 phase=phase).write(writer)
1410class GEN2Data(Block):
1411 '''
1412 Representation of a data tuple in GEN2 section.
1413 '''
1415 _format = [
1416 E(2, 12, 'f11.5'),
1417 E(14, 19, 'f6.3')]
1419 corner = Float.T(help='corner frequency [Hz]')
1420 slope = Float.T(help='slope above corner [dB/decate]')
1423class GEN2(Stage):
1424 '''
1425 Representation of a GEN2 line.
1426 '''
1428 _format = [
1429 E(1, 4, x_fixed(b'GEN2'), dummy=True),
1430 E(6, 7, 'i2'),
1431 E(9, 9, 'a1'),
1432 E(11, 25, x_scaled('e15.8', nm_per_s)),
1433 E(27, 33, 'f7.3'),
1434 E(35, 38, 'i4'),
1435 E(40, 47, 'f8.3'),
1436 E(49, 51, 'i3'),
1437 E(53, 77, 'a25')]
1439 output_units = Units.T(
1440 help='output units code (V=volts, A=amps, C=counts)')
1441 calibration_factor = Float.T(
1442 help='system sensitivity (m/count) at reference period '
1443 '(calibration_period)')
1444 calibration_period = Float.T(help='calibration reference period [s]')
1445 decimation = Int.T(optional=True, help='decimation')
1446 correction = Float.T(help='group correction applied [s]')
1447 ncorners = Int.T(help='number of corners')
1448 description = String.T(default='', optional=True, help='description')
1450 corners = List.T(Float.T(), help='corner frequencies [Hz]')
1451 slopes = List.T(Float.T(), help='slopes above corners [dB/decade]')
1453 comments = List.T(String.T(optional=True))
1455 def append_dataline(self, line, version_dialect):
1456 d = GEN2Data.deserialize(line, version_dialect)
1457 self.corners.append(d.corner)
1458 self.slopes.append(d.slope)
1460 def write_datalines(self, writer):
1461 for corner, slope in zip(self.corners, self.slopes):
1462 GEN2Data(corner=corner, slope=slope).write(writer)
1465class DIG2(Stage):
1466 '''
1467 Representation of a DIG2 line.
1468 '''
1470 _format = [
1471 E(1, 4, x_fixed(b'DIG2'), dummy=True),
1472 E(6, 7, 'i2'),
1473 E(9, 23, 'e15.8'),
1474 E(25, 35, 'f11.5'),
1475 E(37, None, 'a25+')]
1477 sensitivity = Float.T(help='sensitivity [counts/input units]')
1478 sample_rate = Float.T(help='digitizer sample rate [Hz]')
1479 description = String.T(default='', optional=True, help='description')
1481 comments = List.T(String.T(optional=True))
1484class SymmetryFlag(StringChoice):
1485 choices = ['A', 'B', 'C']
1486 ignore_case = True
1489class FIR2Data(Block):
1490 '''
1491 Representation of a line of coefficients in a FIR2 section.
1492 '''
1494 _format = [
1495 E(2, 16, 'e15.8'),
1496 E(18, 32, 'e15.8'),
1497 E(34, 48, 'e15.8'),
1498 E(50, 64, 'e15.8'),
1499 E(66, 80, 'e15.8')]
1501 factors = List.T(Float.T())
1503 def values(self):
1504 return self.factors + [None]*(5-len(self.factors))
1506 @classmethod
1507 def deserialize(cls, line, version_dialect):
1508 factors = [v for v in cls.deserialize_values(line, version_dialect)
1509 if v is not None]
1510 return cls.validated(factors=factors)
1513class FIR2(Stage):
1514 '''
1515 Representation of a FIR2 line.
1516 '''
1518 _format = [
1519 E(1, 4, x_fixed(b'FIR2'), dummy=True),
1520 E(6, 7, 'i2'),
1521 E(9, 18, 'e10.2'),
1522 E(20, 23, 'i4'),
1523 E(25, 32, 'f8.3'),
1524 E(34, 34, 'a1'),
1525 E(36, 39, 'i4'),
1526 E(41, None, 'a25+')]
1528 gain = Float.T(help='filter gain (relative factor, not in dB)')
1529 decimation = Int.T(optional=True, help='decimation')
1530 correction = Float.T(help='group correction applied [s]')
1531 symmetry = SymmetryFlag.T(
1532 help='symmetry flag (A=asymmetric, B=symmetric (odd), '
1533 'C=symmetric (even))')
1534 nfactors = Int.T(help='number of factors')
1535 description = String.T(default='', optional=True, help='description')
1537 comments = List.T(String.T(optional=True))
1539 factors = List.T(Float.T())
1541 def append_dataline(self, line, version_dialect):
1542 d = FIR2Data.deserialize(line, version_dialect)
1543 self.factors.extend(d.factors)
1545 def write_datalines(self, writer):
1546 i = 0
1547 while i < len(self.factors):
1548 FIR2Data(factors=self.factors[i:i+5]).write(writer)
1549 i += 5
1552class Begin(FreeFormatLine):
1553 '''
1554 Representation of a BEGIN line.
1555 '''
1557 _format = [b'BEGIN', 1]
1558 version = String.T(optional=True)
1560 @classmethod
1561 def read(cls, reader):
1562 line = reader.readline()
1563 obj = cls.deserialize(line, reader.version_dialect)
1564 reader.version_dialect[0] = obj.version
1565 return obj
1567 def write(self, writer):
1568 FreeFormatLine.write(self, writer)
1569 writer.version_dialect[0] = self.version
1572class MessageType(StringChoice):
1573 choices = ['REQUEST', 'DATA', 'SUBSCRIPTION']
1574 ignore_case = True
1577class MsgType(FreeFormatLine):
1578 _format = [b'MSG_TYPE', 1]
1579 type = MessageType.T()
1582class MsgID(FreeFormatLine):
1583 '''
1584 Representation of a MSG_ID line.
1585 '''
1587 _format = [b'MSG_ID', 1, 2]
1588 msg_id_string = String.T()
1589 msg_id_source = String.T(optional=True)
1591 @classmethod
1592 def read(cls, reader):
1593 line = reader.readline()
1594 obj = cls.deserialize(line, reader.version_dialect)
1595 if obj.msg_id_source in g_dialects:
1596 reader.version_dialect[1] = obj.msg_id_source
1598 return obj
1600 def write(self, writer):
1601 FreeFormatLine.write(self, writer)
1602 if self.msg_id_source in g_dialects:
1603 writer.version_dialect[1] = self.msg_id_source
1606class RefID(FreeFormatLine):
1607 '''
1608 Representation of a REF_ID line.
1609 '''
1611 _format = {
1612 None: [b'REF_ID', 1, 2, 'PART', 3, 'OF', 4],
1613 'GSE2.0': [b'REF_ID', 1]}
1615 msg_id_string = String.T()
1616 msg_id_source = String.T(optional=True)
1617 sequence_number = Int.T(optional=True)
1618 total_number = Int.T(optional=True)
1620 def serialize(self, version_dialect):
1621 out = ['REF_ID', self.msg_id_string]
1622 if self.msg_id_source:
1623 out.append(self.msg_id_source)
1624 i = self.sequence_number
1625 n = self.total_number
1626 if i is not None and n is not None:
1627 out.extend(['PART', str(i), 'OF', str(n)])
1629 return ' '.join(out).encode('ascii')
1632class LogSection(Section):
1633 '''
1634 Representation of a DATA_TYPE LOG section.
1635 '''
1637 keyword = b'LOG'
1638 lines = List.T(String.T())
1640 @classmethod
1641 def read(cls, reader):
1642 DataType.read(reader)
1643 lines = []
1644 while True:
1645 line = reader.readline()
1646 if end_section(line):
1647 reader.pushback()
1648 break
1649 else:
1650 lines.append(str(line.decode('ascii')))
1652 return cls(lines=lines)
1654 def write(self, writer):
1655 self.write_datatype(writer)
1656 for line in self.lines:
1657 ul = line.upper()
1658 if ul.startswith('DATA_TYPE') or ul.startswith('STOP'):
1659 line = ' ' + line
1661 writer.writeline(str.encode(line))
1664class ErrorLogSection(LogSection):
1665 '''
1666 Representation of a DATA_TYPE ERROR_LOG section.
1667 '''
1669 keyword = b'ERROR_LOG'
1672class FTPLogSection(Section):
1673 '''
1674 Representation of a DATA_TYPE FTP_LOG section.
1675 '''
1677 keyword = b'FTP_LOG'
1678 ftp_file = FTPFile.T()
1680 @classmethod
1681 def read(cls, reader):
1682 DataType.read(reader)
1683 ftp_file = FTPFile.read(reader)
1684 return cls(ftp_file=ftp_file)
1686 def write(self, writer):
1687 self.write_datatype(writer)
1688 self.ftp_file.write(writer)
1691class WID2Section(Section):
1692 '''
1693 Representation of a WID2/STA2/EID2/BEA2/DAT2/CHK2 group.
1694 '''
1696 wid2 = WID2.T()
1697 sta2 = STA2.T(optional=True)
1698 eid2s = List.T(EID2.T())
1699 bea2 = BEA2.T(optional=True)
1700 dat2 = DAT2.T()
1701 chk2 = CHK2.T()
1703 @classmethod
1704 def read(cls, reader):
1705 blocks = dict(eid2s=[])
1706 expect = [(b'WID2 ', WID2, 1)]
1708 if reader.version_dialect[0] == 'GSE2.0':
1709 # should not be there in GSE2.0, but BGR puts it there
1710 expect.append((b'STA2 ', STA2, 0))
1711 else:
1712 expect.append((b'STA2 ', STA2, 1))
1714 expect.extend([
1715 (b'EID2 ', EID2, 0),
1716 (b'BEA2 ', BEA2, 0),
1717 (b'DAT2', DAT2, 1),
1718 (b'CHK2 ', CHK2, 1)])
1720 for k, handler, required in expect:
1721 line = reader.readline()
1722 reader.pushback()
1724 if line is None:
1725 raise DeserializeError('incomplete waveform section')
1727 if line.upper().startswith(k):
1728 block = handler.read(reader)
1729 if k == b'EID2 ':
1730 blocks['eid2s'].append(block)
1731 else:
1732 blocks[str(k.lower().rstrip().decode('ascii'))] = block
1733 else:
1734 if required:
1735 raise DeserializeError('expected %s block' % k)
1736 else:
1737 continue
1739 return cls(**blocks)
1741 def write(self, writer):
1742 self.wid2.write(writer)
1743 if self.sta2:
1744 self.sta2.write(writer)
1745 for eid2 in self.eid2s:
1746 eid2.write(writer)
1747 if self.bea2:
1748 self.bea2.write(writer)
1750 self.dat2.write(writer)
1751 self.chk2.write(writer)
1753 def pyrocko_trace(self, checksum_error='raise'):
1754 from pyrocko import ims_ext, trace
1755 assert checksum_error in ('raise', 'warn', 'ignore')
1757 raw_data = self.dat2.raw_data
1758 nsamples = self.wid2.nsamples
1759 deltat = 1.0 / self.wid2.sample_rate
1760 tmin = self.wid2.time
1761 if self.sta2:
1762 net = self.sta2.network
1763 else:
1764 net = ''
1765 sta = self.wid2.station
1766 loc = self.wid2.location
1767 cha = self.wid2.channel
1769 if raw_data:
1770 ydata = ims_ext.decode_cm6(b''.join(raw_data), nsamples)
1771 if checksum_error != 'ignore':
1772 if ims_ext.checksum(ydata) != self.chk2.checksum:
1773 mess = 'computed checksum value differs from stored value'
1774 if checksum_error == 'raise':
1775 raise DeserializeError(mess)
1776 elif checksum_error == 'warn':
1777 logger.warning(mess)
1779 tmax = None
1780 else:
1781 tmax = tmin + (nsamples - 1) * deltat
1782 ydata = None
1784 return trace.Trace(
1785 net, sta, loc, cha, tmin=tmin, tmax=tmax,
1786 deltat=deltat,
1787 ydata=ydata)
1789 @classmethod
1790 def from_pyrocko_trace(cls, tr,
1791 lat=None, lon=None, elevation=None, depth=None):
1793 from pyrocko import ims_ext
1794 ydata = tr.get_ydata()
1795 raw_data = ims_ext.encode_cm6(ydata)
1796 return cls(
1797 wid2=WID2(
1798 nsamples=tr.data_len(),
1799 sample_rate=1.0 / tr.deltat,
1800 time=tr.tmin,
1801 station=tr.station,
1802 location=tr.location,
1803 channel=tr.channel),
1804 sta2=STA2(
1805 network=tr.network,
1806 lat=lat,
1807 lon=lon,
1808 elevation=elevation,
1809 depth=depth),
1810 dat2=DAT2(
1811 raw_data=[raw_data[i*80:(i+1)*80]
1812 for i in range((len(raw_data)-1)//80 + 1)]),
1813 chk2=CHK2(
1814 checksum=ims_ext.checksum(ydata)))
1817class OUT2Section(Section):
1818 '''
1819 Representation of a OUT2/STA2 group.
1820 '''
1822 out2 = OUT2.T()
1823 sta2 = STA2.T()
1825 @classmethod
1826 def read(cls, reader):
1827 out2 = OUT2.read(reader)
1828 line = reader.readline()
1829 reader.pushback()
1830 if line.startswith(b'STA2'):
1831 # the spec sais STA2 is mandatory but in practice, it is not
1832 # always there...
1833 sta2 = STA2.read(reader)
1834 else:
1835 sta2 = None
1837 return cls(out2=out2, sta2=sta2)
1839 def write(self, writer):
1840 self.out2.write(writer)
1841 if self.sta2 is not None:
1842 self.sta2.write(writer)
1845class DLY2Section(Section):
1846 '''
1847 Representation of a DLY2/STA2 group.
1848 '''
1850 dly2 = DLY2.T()
1851 sta2 = STA2.T()
1853 @classmethod
1854 def read(cls, reader):
1855 dly2 = DLY2.read(reader)
1856 sta2 = STA2.read(reader)
1857 return cls(dly2=dly2, sta2=sta2)
1859 def write(self, writer):
1860 self.dly2.write(writer)
1861 self.sta2.write(writer)
1864class WaveformSection(Section):
1865 '''
1866 Representation of a DATA_TYPE WAVEFORM line.
1868 Any subsequent WID2/OUT2/DLY2 groups are handled as indepenent sections, so
1869 this type just serves as a dummy to read/write the DATA_TYPE WAVEFORM
1870 header.
1871 '''
1873 keyword = b'WAVEFORM'
1875 datatype = DataType.T()
1877 @classmethod
1878 def read(cls, reader):
1879 datatype = DataType.read(reader)
1880 return cls(datatype=datatype)
1882 def write(self, writer):
1883 self.datatype.write(writer)
1886class TableSection(Section):
1887 '''
1888 Base class for table style sections.
1889 '''
1891 has_data_type_header = True
1893 @classmethod
1894 def read(cls, reader):
1895 if cls.has_data_type_header:
1896 DataType.read(reader)
1898 ts = cls.table_setup
1900 header = get_versioned(ts['header'], reader.version_dialect)
1901 blocks = list(cls.read_table(
1902 reader, header, ts['cls'], end=ts.get('end', end_section)))
1903 return cls(**{ts['attribute']: blocks})
1905 def write(self, writer):
1906 if self.has_data_type_header:
1907 self.write_datatype(writer)
1909 ts = self.table_setup
1910 header = get_versioned(ts['header'], writer.version_dialect)
1911 self.write_table(writer, header, getattr(self, ts['attribute']))
1914class NetworkSection(TableSection):
1915 '''
1916 Representation of a DATA_TYPE NETWORK section.
1917 '''
1919 keyword = b'NETWORK'
1920 table_setup = dict(
1921 header=b'Net Description',
1922 attribute='networks',
1923 cls=Network)
1925 networks = List.T(Network.T())
1928class StationSection(TableSection):
1929 '''
1930 Representation of a DATA_TYPE STATION section.
1931 '''
1933 keyword = b'STATION'
1934 table_setup = dict(
1935 header={
1936 None: (
1937 b'Net Sta Type Latitude Longitude Coord '
1938 b'Sys Elev On Date Off Date'),
1939 'GSE2.0': (
1940 b'Sta Type Latitude Longitude Elev On Date '
1941 b'Off Date')},
1942 attribute='stations',
1943 cls=Station)
1945 stations = List.T(Station.T())
1948class ChannelSection(TableSection):
1949 '''
1950 Representation of a DATA_TYPE CHANNEL section.
1951 '''
1953 keyword = b'CHANNEL'
1954 table_setup = dict(
1955 header={
1956 None: (
1957 b'Net Sta Chan Aux Latitude Longitude Coord Sys'
1958 b' Elev Depth Hang Vang Sample Rate Inst '
1959 b'On Date Off Date'),
1960 'GSE2.0': (
1961 b'Sta Chan Aux Latitude Longitude '
1962 b'Elev Depth Hang Vang Sample_Rate Inst '
1963 b'On Date Off Date')},
1964 attribute='channels',
1965 cls=Channel)
1967 channels = List.T(Channel.T())
1970class BeamSection(Section):
1971 '''
1972 Representation of a DATA_TYPE BEAM section.
1973 '''
1975 keyword = b'BEAM'
1976 beam_group_header = b'Bgroup Sta Chan Aux Wgt Delay'
1977 beam_parameters_header = b'BeamID Bgroup Btype R Azim Slow '\
1978 b'Phase Flo Fhi O Z F '\
1979 b'On Date Off Date'
1980 group = List.T(BeamGroup.T())
1981 parameters = List.T(BeamParameters.T())
1983 @classmethod
1984 def read(cls, reader):
1985 DataType.read(reader)
1987 def end(line):
1988 return line.upper().startswith(b'BEAMID')
1990 group = list(cls.read_table(reader, cls.beam_group_header, BeamGroup,
1991 end))
1993 parameters = list(cls.read_table(reader, cls.beam_parameters_header,
1994 BeamParameters))
1996 return cls(group=group, parameters=parameters)
1998 def write(self, writer):
1999 self.write_datatype(writer)
2000 self.write_table(writer, self.beam_group_header, self.group)
2001 writer.writeline(b'')
2002 self.write_table(writer, self.beam_parameters_header, self.parameters)
2005class CAL2Section(Section):
2006 '''
2007 Representation of a CAL2 + stages group in a response section.
2008 '''
2010 cal2 = CAL2.T()
2011 stages = List.T(Stage.T())
2013 @classmethod
2014 def read(cls, reader):
2015 cal2 = CAL2.read(reader)
2016 stages = []
2017 handlers = {
2018 b'PAZ2': PAZ2,
2019 b'FAP2': FAP2,
2020 b'GEN2': GEN2,
2021 b'DIG2': DIG2,
2022 b'FIR2': FIR2}
2024 while True:
2025 line = reader.readline()
2026 reader.pushback()
2027 if end_section(line, b'CAL2'):
2028 break
2030 k = line[:4].upper()
2031 if k in handlers:
2032 stages.append(handlers[k].read(reader))
2033 else:
2034 raise DeserializeError('unexpected line')
2036 return cls(cal2=cal2, stages=stages)
2038 def write(self, writer):
2039 self.cal2.write(writer)
2040 for stage in self.stages:
2041 stage.write(writer)
2044class ResponseSection(Section):
2045 '''
2046 Representation of a DATA_TYPE RESPONSE line.
2048 Any subsequent CAL2+stages groups are handled as indepenent sections, so
2049 this type just serves as a dummy to read/write the DATA_TYPE RESPONSE
2050 header.
2051 '''
2053 keyword = b'RESPONSE'
2055 datatype = DataType.T()
2057 @classmethod
2058 def read(cls, reader):
2059 datatype = DataType.read(reader)
2060 return cls(datatype=datatype)
2062 def write(self, writer):
2063 self.datatype.write(writer)
2066class OutageSection(Section):
2067 '''
2068 Representation of a DATA_TYPE OUTAGE section.
2069 '''
2071 keyword = b'OUTAGE'
2072 outages_header = b'NET Sta Chan Aux Start Date Time'\
2073 b' End Date Time Duration Comment'
2074 report_period = OutageReportPeriod.T()
2075 outages = List.T(Outage.T())
2077 @classmethod
2078 def read(cls, reader):
2079 DataType.read(reader)
2080 report_period = OutageReportPeriod.read(reader)
2081 outages = []
2082 outages = list(cls.read_table(reader, cls.outages_header,
2083 Outage))
2085 return cls(
2086 report_period=report_period,
2087 outages=outages)
2089 def write(self, writer):
2090 self.write_datatype(writer)
2091 self.report_period.write(writer)
2092 self.write_table(writer, self.outages_header, self.outages)
2095class BulletinTitle(Block):
2097 _format = [
2098 E(1, 136, 'a136')]
2100 title = String.T()
2103g_event_types = dict(
2104 uk='unknown',
2105 ke='known earthquake',
2106 se='suspected earthquake',
2107 kr='known rockburst',
2108 sr='suspected rockburst',
2109 ki='known induced event',
2110 si='suspected induced event',
2111 km='known mine explosion',
2112 sm='suspected mine explosion',
2113 kx='known experimental explosion',
2114 sx='suspected experimental explosion',
2115 kn='known nuclear explosion',
2116 sn='suspected nuclear explosion',
2117 ls='landslide',
2118 de='??',
2119 fe='??',)
2122class Origin(Block):
2123 _format = [
2124 E(1, 22, x_date_time_2frac),
2125 E(23, 23, 'a1?'),
2126 E(25, 29, 'f5.2'),
2127 E(31, 35, 'f5.2'),
2128 E(37, 44, 'f8.4'),
2129 E(46, 54, 'f9.4'),
2130 E(55, 55, 'a1?'),
2131 E(57, 60, x_scaled('f4.1', km)),
2132 E(62, 66, x_scaled('f5.1', km)),
2133 E(68, 70, x_int_angle),
2134 E(72, 76, x_scaled('f5.1', km)),
2135 E(77, 77, 'a1?'),
2136 E(79, 82, x_scaled('f4.1', km)),
2137 E(84, 87, 'i4'),
2138 E(89, 92, 'i4'),
2139 E(94, 96, x_int_angle),
2140 E(98, 103, 'f6.2'),
2141 E(105, 110, 'f6.2'),
2142 E(112, 112, 'a1?'),
2143 E(114, 114, 'a1?'),
2144 E(116, 117, 'a2?'),
2145 E(119, 127, 'a9'),
2146 E(129, 136, 'a8')]
2148 time = Timestamp.T(
2149 help='epicenter date and time')
2151 time_fixed = StringChoice.T(
2152 choices=['f'],
2153 optional=True,
2154 help='fixed flag, ``"f"`` if fixed origin time solution, '
2155 '``None`` if not')
2157 time_error = Float.T(
2158 optional=True,
2159 help='origin time error [seconds], ``None`` if fixed origin time')
2161 residual = Float.T(
2162 optional=True,
2163 help='root mean square of time residuals [seconds]')
2165 lat = Float.T(
2166 help='latitude')
2168 lon = Float.T(
2169 help='longitude')
2171 lat_lon_fixed = StringChoice.T(
2172 choices=['f'], optional=True,
2173 help='fixed flag, ``"f"`` if fixed epicenter solution, '
2174 '``None`` if not')
2176 ellipse_semi_major_axis = Float.T(
2177 optional=True,
2178 help='semi-major axis of 90% c. i. ellipse or its estimate [m], '
2179 '``None`` if fixed')
2181 ellipse_semi_minor_axis = Float.T(
2182 optional=True,
2183 help='semi-minor axis of 90% c. i. ellipse or its estimate [m], '
2184 '``None`` if fixed')
2186 ellipse_strike = Float.T(
2187 optional=True,
2188 help='strike of 90% c. i. ellipse [0-360], ``None`` if fixed')
2190 depth = Float.T(
2191 help='depth [m]')
2193 depth_fixed = StringChoice.T(
2194 choices=['f', 'd'], optional=True,
2195 help='fixed flag, ``"f"`` fixed depth station, "d" depth phases, '
2196 '``None`` if not fixed depth')
2198 depth_error = Float.T(
2199 optional=True,
2200 help='depth error [m], 90% c. i., ``None`` if fixed depth')
2202 nphases = Int.T(
2203 optional=True,
2204 help='number of defining phases')
2206 nstations = Int.T(
2207 optional=True,
2208 help='number of defining stations')
2210 azimuthal_gap = Float.T(
2211 optional=True,
2212 help='gap in azimuth coverage [deg]')
2214 distance_min = Float.T(
2215 optional=True,
2216 help='distance to closest station [deg]')
2218 distance_max = Float.T(
2219 optional=True,
2220 help='distance to furthest station [deg]')
2222 analysis_type = StringChoice.T(
2223 optional=True,
2224 choices=['a', 'm', 'g'],
2225 help='analysis type, ``"a"`` automatic, ``"m"`` manual, ``"g"`` guess')
2227 location_method = StringChoice.T(
2228 optional=True,
2229 choices=['i', 'p', 'g', 'o'],
2230 help='location method, ``"i"`` inversion, ``"p"`` pattern, '
2231 '``"g"`` ground truth, ``"o"`` other')
2233 event_type = StringChoice.T(
2234 optional=True,
2235 choices=sorted(g_event_types.keys()),
2236 help='event type, ' + ', '.join(
2237 '``"%s"`` %s' % (k, g_event_types[k])
2238 for k in sorted(g_event_types.keys())))
2240 author = String.T(help='author of the origin')
2241 origin_id = String.T(help='origin identification')
2244class OriginSection(TableSection):
2245 has_data_type_header = False
2247 table_setup = dict(
2248 header={
2249 None: (
2250 b' Date Time Err RMS Latitude Longitude '
2251 b'Smaj Smin Az Depth Err Ndef Nsta Gap mdist Mdist '
2252 b'Qual Author OrigID')},
2253 attribute='origins',
2254 end=lambda line: end_section(line, b'EVENT'),
2255 cls=Origin)
2257 origins = List.T(Origin.T())
2260class EventTitle(Block):
2261 _format = [
2262 E(1, 5, x_fixed(b'Event'), dummy=True),
2263 E(7, 14, 'a8'),
2264 E(16, 80, 'a65')]
2266 event_id = String.T()
2267 region = String.T()
2270class EventSection(Section):
2271 '''
2272 Groups Event, Arrival, ...
2273 '''
2275 event_title = EventTitle.T()
2276 origin_section = OriginSection.T()
2278 @classmethod
2279 def read(cls, reader):
2280 event_title = EventTitle.read(reader)
2281 origin_section = OriginSection.read(reader)
2282 return cls(
2283 event_title=event_title,
2284 origin_section=origin_section)
2286 def write(self, writer):
2287 self.event_title.write(writer)
2288 self.origin_section.write(writer)
2291class EventsSection(Section):
2292 '''
2293 Representation of a DATA_TYPE EVENT section.
2294 '''
2296 keyword = b'EVENT'
2298 bulletin_title = BulletinTitle.T()
2299 event_sections = List.T(EventSection.T())
2301 @classmethod
2302 def read(cls, reader):
2303 DataType.read(reader)
2304 bulletin_title = BulletinTitle.read(reader)
2305 event_sections = []
2306 while True:
2307 line = reader.readline()
2308 reader.pushback()
2309 if end_section(line):
2310 break
2312 if line.upper().startswith(b'EVENT'):
2313 event_sections.append(EventSection.read(reader))
2315 return cls(
2316 bulletin_title=bulletin_title,
2317 event_sections=event_sections,
2318 )
2320 def write(self, writer):
2321 self.write_datatype(writer)
2322 self.bulletin_title.write(writer)
2323 for event_section in self.event_sections:
2324 event_section.write(writer)
2327class BulletinSection(EventsSection):
2328 '''
2329 Representation of a DATA_TYPE BULLETIN section.
2330 '''
2332 keyword = b'BULLETIN'
2335for sec in (
2336 LogSection, ErrorLogSection, FTPLogSection, WaveformSection,
2337 NetworkSection, StationSection, ChannelSection, BeamSection,
2338 ResponseSection, OutageSection, EventsSection, BulletinSection):
2340 Section.handlers[sec.keyword] = sec
2342del sec
2345class MessageHeader(Section):
2346 '''
2347 Representation of a BEGIN/MSG_TYPE/MSG_ID/REF_ID group.
2348 '''
2350 version = String.T()
2351 type = String.T()
2352 msg_id = MsgID.T(optional=True)
2353 ref_id = RefID.T(optional=True)
2355 @classmethod
2356 def read(cls, reader):
2357 handlers = {
2358 b'BEGIN': Begin,
2359 b'MSG_TYPE': MsgType,
2360 b'MSG_ID': MsgID,
2361 b'REF_ID': RefID}
2363 blocks = {}
2364 while True:
2365 line = reader.readline()
2366 reader.pushback()
2367 ok = False
2368 for k in handlers:
2369 if line.upper().startswith(k):
2370 blocks[k] = handlers[k].read(reader)
2371 ok = True
2373 if not ok:
2374 break
2376 return MessageHeader(
2377 type=blocks[b'MSG_TYPE'].type,
2378 version=blocks[b'BEGIN'].version,
2379 msg_id=blocks.get(b'MSG_ID', None),
2380 ref_id=blocks.get(b'REF_ID', None))
2382 def write(self, writer):
2383 Begin(version=self.version).write(writer)
2384 MsgType(type=self.type).write(writer)
2385 if self.msg_id is not None:
2386 self.msg_id.write(writer)
2387 if self.ref_id is not None:
2388 self.ref_id.write(writer)
2391def parse_ff_date_time(s):
2392 toks = s.split()
2393 if len(toks) == 2:
2394 sdate, stime = toks
2395 else:
2396 sdate, stime = toks[0], ''
2398 stime += '00:00:00.000'[len(stime):]
2399 return util.str_to_time(
2400 sdate + ' ' + stime, format='%Y/%m/%d %H:%M:%S.3FRAC')
2403def string_ff_date_time(t):
2404 return util.time_to_str(t, format='%Y/%m/%d %H:%M:%S.3FRAC')
2407class TimeStamp(FreeFormatLine):
2408 '''
2409 Representation of a TIME_STAMP line.
2410 '''
2412 _format = [b'TIME_STAMP', 1]
2414 value = Timestamp.T()
2416 @classmethod
2417 def deserialize(cls, line, version_dialect):
2418 (s,) = cls.deserialize_values(line, version_dialect)
2419 return cls(value=parse_ff_date_time(s))
2421 def serialize(self, line, version_dialect):
2422 return (
2423 'TIME_STAMP %s' % string_ff_date_time(self.value)).encode('ascii')
2426class Stop(FreeFormatLine):
2427 '''
2428 Representation of a STOP line.
2429 '''
2431 _format = [b'STOP']
2433 dummy = String.T(optional=True)
2436class XW01(FreeFormatLine):
2437 '''
2438 Representation of a XW01 line (which is a relict from GSE1).
2439 '''
2441 _format = [b'XW01']
2443 dummy = String.T(optional=True)
2446re_comment = re.compile(br'^(%(.+)\s*| \((#?)(.+)\)\s*)$')
2447re_comment_usa_dmc = re.compile(br'^(%(.+)\s*| ?\((#?)(.+)\)\s*)$')
2450class Reader(object):
2451 def __init__(self, f, load_data=True, version=None, dialect=None):
2452 self._f = f
2453 self._load_data = load_data
2454 self._current_fpos = None
2455 self._current_lpos = None # "physical" line number
2456 self._current_line = None
2457 self._readline_count = 0
2458 self._pushed_back = False
2459 self._handlers = {
2460 b'DATA_TYPE ': Section,
2461 b'WID2 ': WID2Section,
2462 b'OUT2 ': OUT2Section,
2463 b'DLY2 ': DLY2Section,
2464 b'CAL2 ': CAL2Section,
2465 b'BEGIN': MessageHeader,
2466 b'STOP': Stop,
2467 b'XW01': XW01, # for compatibility with BGR dialect
2468 b'HANG:': None, # for compatibility with CNDC
2469 b'VANG:': None,
2470 }
2471 self._comment_lines = []
2472 self._time_stamps = []
2473 self.version_dialect = [version, dialect] # main version, dialect
2474 self._in_garbage = True
2476 def tell(self):
2477 return self._current_fpos
2479 def current_line_number(self):
2480 return self._current_lpos - int(self._pushed_back)
2482 def readline(self):
2483 if self._pushed_back:
2484 self._pushed_back = False
2485 return self._current_line
2487 while True:
2488 self._current_fpos = self._f.tell()
2489 self._current_lpos = self._readline_count + 1
2490 ln = self._f.readline()
2491 self._readline_count += 1
2492 if not ln:
2493 self._current_line = None
2494 return None
2496 lines = [ln.rstrip(b'\n\r')]
2497 while lines[-1].endswith(b'\\'):
2498 lines[-1] = lines[-1][:-1]
2499 ln = self._f.readline()
2500 self._readline_count += 1
2501 lines.append(ln.rstrip(b'\n\r'))
2503 self._current_line = b''.join(lines)
2505 if self.version_dialect[1] == 'USA_DMC':
2506 m_comment = re_comment_usa_dmc.match(self._current_line)
2507 else:
2508 m_comment = re_comment.match(self._current_line)
2510 if not self._current_line.strip():
2511 pass
2513 elif m_comment:
2514 comment_type = None
2515 if m_comment.group(3) == b'#':
2516 comment_type = 'ISF'
2517 elif m_comment.group(4) is not None:
2518 comment_type = 'IMS'
2520 comment = m_comment.group(2) or m_comment.group(4)
2522 self._comment_lines.append(
2523 (self._current_lpos, comment_type,
2524 str(comment.decode('ascii'))))
2526 elif self._current_line[:10].upper() == b'TIME_STAMP':
2527 self._time_stamps.append(
2528 TimeStamp.deserialize(
2529 self._current_line, self.version_dialect))
2531 else:
2532 return self._current_line
2534 def get_comments_after(self, lpos):
2535 comments = []
2536 i = len(self._comment_lines) - 1
2537 while i >= 0:
2538 if self._comment_lines[i][0] <= lpos:
2539 break
2541 comments.append(self._comment_lines[i][-1])
2542 i -= 1
2544 return comments
2546 def pushback(self):
2547 assert not self._pushed_back
2548 self._pushed_back = True
2550 def __iter__(self):
2551 return self
2553 def next(self):
2554 return self.__next__()
2556 def __next__(self):
2557 try:
2558 while True:
2559 line = self.readline()
2560 if line is None:
2561 raise StopIteration()
2563 ignore = False
2564 for k in self._handlers:
2565 if line.upper().startswith(k):
2566 if self._handlers[k] is None:
2567 ignore = True
2568 break
2570 self.pushback()
2571 sec = self._handlers[k].read(self)
2572 if isinstance(sec, Stop):
2573 self._in_garbage = True
2574 else:
2575 self._in_garbage = False
2576 return sec
2578 if not self._in_garbage and not ignore:
2579 raise DeserializeError('unexpected line')
2581 except DeserializeError as e:
2582 e.set_context(
2583 self._current_lpos,
2584 self._current_line,
2585 self.version_dialect)
2586 raise
2589class Writer(object):
2591 def __init__(self, f, version='GSE2.1', dialect=None):
2592 self._f = f
2593 self.version_dialect = [version, dialect]
2595 def write(self, section):
2596 section.write(self)
2598 def writeline(self, line):
2599 self._f.write(line.rstrip())
2600 self._f.write(b'\n')
2603def write_string(sections):
2604 from io import BytesIO
2605 f = BytesIO()
2606 w = Writer(f)
2607 for section in sections:
2608 w.write(section)
2610 return f.getvalue()
2613def iload_fh(f, **kwargs):
2614 '''
2615 Load IMS/GSE2 records from open file handle.
2616 '''
2617 try:
2618 r = Reader(f, **kwargs)
2619 for section in r:
2620 yield section
2622 except DeserializeError as e:
2623 raise FileLoadError(e)
2626def iload_string(s, **kwargs):
2627 '''
2628 Read IMS/GSE2 sections from bytes string.
2629 '''
2631 from io import BytesIO
2632 f = BytesIO(s)
2633 return iload_fh(f, **kwargs)
2636iload_filename, iload_dirname, iload_glob, iload = util.make_iload_family(
2637 iload_fh, 'IMS/GSE2', ':py:class:`Section`')
2640def dump_fh(sections, f):
2641 '''
2642 Dump IMS/GSE2 sections to open file handle.
2643 '''
2644 try:
2645 w = Writer(f)
2646 for section in sections:
2647 w.write(section)
2649 except SerializeError as e:
2650 raise FileSaveError(e)
2653def dump_string(sections):
2654 '''
2655 Write IMS/GSE2 sections to string.
2656 '''
2658 from io import BytesIO
2659 f = BytesIO()
2660 dump_fh(sections, f)
2661 return f.getvalue()
2664if __name__ == '__main__':
2665 from optparse import OptionParser
2667 usage = 'python -m pyrocko.ims <filenames>'
2669 util.setup_logging('pyrocko.ims.__main__', 'warning')
2671 description = '''
2672 Read and print IMS/GSE2 records.
2673 '''
2675 parser = OptionParser(
2676 usage=usage,
2677 description=description,
2678 formatter=util.BetterHelpFormatter())
2680 parser.add_option(
2681 '--version',
2682 dest='version',
2683 choices=g_versions,
2684 help='inial guess for version')
2686 parser.add_option(
2687 '--dialect',
2688 dest='dialect',
2689 choices=g_dialects,
2690 help='inial guess for dialect')
2692 parser.add_option(
2693 '--load-data',
2694 dest='load_data',
2695 action='store_true',
2696 help='unpack data samples')
2698 parser.add_option(
2699 '--out-version',
2700 dest='out_version',
2701 choices=g_versions,
2702 help='output format version')
2704 parser.add_option(
2705 '--out-dialect',
2706 dest='out_dialect',
2707 choices=g_dialects,
2708 help='output format dialect')
2710 (options, args) = parser.parse_args(sys.argv[1:])
2712 for fn in args:
2713 with open(fn, 'rb') as f:
2715 r = Reader(f, load_data=options.load_data,
2716 version=options.version, dialect=options.dialect)
2718 w = None
2719 if options.out_version is not None:
2720 w = Writer(
2721 sys.stdout, version=options.out_version,
2722 dialect=options.out_dialect)
2724 for sec in r:
2725 if not w:
2726 print(sec)
2728 else:
2729 w.write(sec)
2731 if isinstance(sec, WID2Section) and options.load_data:
2732 tr = sec.pyrocko_trace(checksum_error='warn')