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 

9 

10import sys 

11import re 

12import logging 

13 

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) 

19 

20try: 

21 range = xrange 

22except NameError: 

23 pass 

24 

25logger = logging.getLogger('pyrocko.io.ims') 

26 

27km = 1000. 

28nm_per_s = 1.0e-9 

29 

30g_versions = ('GSE2.0', 'GSE2.1', 'IMS1.0') 

31g_dialects = ('NOR_NDC', 'USA_DMC') 

32 

33 

34class SerializeError(Exception): 

35 ''' 

36 Raised when serialization of an IMS/GSE2 object fails. 

37 ''' 

38 pass 

39 

40 

41class DeserializeError(Exception): 

42 ''' 

43 Raised when deserialization of an IMS/GSE2 object fails. 

44 ''' 

45 

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 

53 

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 

58 

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 ***')) 

69 

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] 

75 

76 lst.append(' ' * self._position[0] + '^' * length) 

77 

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 

91 

92 j += 1 

93 

94 lst.append(''.join(f)) 

95 

96 return '\n'.join(lst) 

97 

98 

99def float_or_none(x): 

100 if x.strip(): 

101 return float(x) 

102 else: 

103 return None 

104 

105 

106def int_or_none(x): 

107 if x.strip(): 

108 return int(x) 

109 else: 

110 return None 

111 

112 

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 

119 

120 def func(v): 

121 if v is None: 

122 return blank 

123 

124 for pfmt in pfmts: 

125 s = pfmt % v 

126 if len(s) == ln: 

127 return s.encode('ascii') 

128 

129 raise SerializeError('format="%s", value=%s' % (pfmt, repr(v))) 

130 

131 return func 

132 

133 

134def int_to_string(fmt): 

135 assert fmt[0] == 'i' 

136 pfmt = '%'+fmt[1:]+'i' 

137 ln = int(fmt[1:]) 

138 blank = b' ' * ln 

139 

140 def func(v): 

141 if v is None: 

142 return blank 

143 

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))) 

149 

150 return func 

151 

152 

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')) 

163 

164 return func 

165 

166 

167def serialize_string(fmt): 

168 if fmt.endswith('+'): 

169 more_ok = True 

170 else: 

171 more_ok = False 

172 

173 fmt = fmt.rstrip('?+') 

174 

175 assert fmt[0] == 'a' 

176 ln = int(fmt[1:]) 

177 

178 def func(v): 

179 if v is None: 

180 v = b'' 

181 else: 

182 v = v.encode('ascii') 

183 

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) 

189 

190 return func 

191 

192 

193def rstrip_string(v): 

194 return v.rstrip() 

195 

196 

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 

204 

205 def string(s): 

206 return expect 

207 

208 return parse, string 

209 

210 func.width = len(expect) 

211 func.help_type = 'Keyword: %s' % expect 

212 return func 

213 

214 

215def x_scaled(fmt, factor): 

216 def func(): 

217 to_string = float_to_string(fmt) 

218 

219 def parse(s): 

220 x = float_or_none(s) 

221 if x is None: 

222 return None 

223 else: 

224 return x * factor 

225 

226 def string(v): 

227 if v is None: 

228 return to_string(None) 

229 else: 

230 return to_string(v/factor) 

231 

232 return parse, string 

233 

234 func.width = int(fmt[1:].split('.')[0]) 

235 func.help_type = 'float' 

236 return func 

237 

238 

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') 

245 

246 return float_or_none, string 

247 

248 

249x_int_angle.width = 3 

250x_int_angle.help_type = 'int [0, 360]' 

251 

252 

253def x_substitute(value): 

254 def func(): 

255 def parse(s): 

256 assert s == b'' 

257 return value 

258 

259 def string(s): 

260 return b'' 

261 

262 return parse, string 

263 

264 func.width = 0 

265 func.help_type = 'Not present in this file version.' 

266 return func 

267 

268 

269def fillup_zeros(s, fmt): 

270 s = s.rstrip() 

271 if not s: 

272 return s 

273 

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):] 

278 

279 return s 

280 

281 

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) 

288 

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 

294 

295 elif fmt[6:8] == '%Y' and s[6:10] in ('2599', '2045'): 

296 return None 

297 

298 raise DeserializeError('expected date, value="%s"' % s) 

299 

300 def string(s): 

301 return util.time_to_str(s, format=fmt).encode('ascii') 

302 

303 return parse, string 

304 

305 

306x_date_time.width = 23 

307x_date_time.help_type = 'YYYY/MM/DD HH:MM:SS.FFF' 

308 

309 

310def x_date(): 

311 return x_date_time(fmt='%Y/%m/%d') 

312 

313 

314x_date.width = 10 

315x_date.help_type = 'YYYY/MM/DD' 

316 

317 

318def x_date_iris(): 

319 return x_date_time(fmt='%m/%d/%Y') 

320 

321 

322x_date_iris.width = 10 

323x_date_iris.help_type = 'MM/DD/YYYY' 

324 

325 

326def x_date_time_no_seconds(): 

327 return x_date_time(fmt='%Y/%m/%d %H:%M') 

328 

329 

330x_date_time_no_seconds.width = 16 

331x_date_time_no_seconds.help_type = 'YYYY/MM/DD HH:MM' 

332 

333 

334def x_date_time_2frac(): 

335 return x_date_time(fmt='%Y/%m/%d %H:%M:%S.2FRAC') 

336 

337 

338x_date_time_2frac.width = 22 

339x_date_time_2frac.help_type = 'YYYY/MM/DD HH:MM:SS.FF' 

340 

341 

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') 

350 

351 def string(b): 

352 return [b'n', b'y'][int(b)] 

353 

354 return parse, string 

355 

356 

357x_yesno.width = 1 

358x_yesno.help_type = 'yes/no' 

359 

360 

361def optional(x_func): 

362 

363 def func(): 

364 parse, string = x_func() 

365 

366 def parse_optional(s): 

367 if s.strip(): 

368 return parse(s) 

369 else: 

370 return None 

371 

372 def string_optional(s): 

373 if s is None: 

374 return b' ' * x_func.width 

375 else: 

376 return string(s) 

377 

378 return parse_optional, string_optional 

379 

380 func.width = x_func.width 

381 func.help_type = 'optional %s' % x_func.help_type 

382 

383 return func 

384 

385 

386class E(object): 

387 def __init__(self, begin, end, fmt, dummy=False): 

388 self.advance = 1 

389 if dummy: 

390 self.advance = 0 

391 

392 self.position = begin - 1 

393 if end is not None: 

394 self.length = end - begin + 1 

395 else: 

396 self.length = None 

397 

398 self.end = end 

399 

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 

419 

420 assert self.length is None or ln == self.length, \ 

421 'inconsistent length for pos=%i, fmt=%s' \ 

422 % (self.position, fmt) 

423 

424 else: 

425 self.parse, self.string = fmt() 

426 self.help_type = fmt.help_type 

427 

428 

429def end_section(line, extra=None): 

430 if line is None: 

431 return True 

432 

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)) 

436 

437 

438class Section(Object): 

439 ''' 

440 Base class for top level sections in IMS/GSE2 files. 

441 

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 ''' 

446 

447 handlers = {} # filled after section have been defined below 

448 

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) 

455 

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) 

461 

462 @classmethod 

463 def read_table(cls, reader, expected_header, block_cls, end=end_section): 

464 

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)) 

470 

471 while True: 

472 line = reader.readline() 

473 reader.pushback() 

474 if end(line): 

475 break 

476 

477 yield block_cls.read(reader) 

478 

479 def write_table(self, writer, header, blocks): 

480 writer.writeline(header) 

481 for block in blocks: 

482 block.write(writer) 

483 

484 

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 

492 

493 

494class Block(Object): 

495 ''' 

496 Base class for IMS/GSE2 data blocks / lines. 

497 

498 Blocks as understood by this implementation usually correspond to 

499 individual logical lines in the IMS/GSE2 file. 

500 ''' 

501 

502 def values(self): 

503 return list(self.T.ivals(self)) 

504 

505 @classmethod 

506 def format(cls, version_dialect): 

507 return get_versioned(cls._format, version_dialect) 

508 

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 

517 

518 out.sort() 

519 

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) 

526 

527 return b''.join(slist) 

528 

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]) 

536 

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)) 

547 

548 return values 

549 

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)) 

557 

558 return obj 

559 

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)) 

567 

568 return obj 

569 

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))) 

574 

575 @classmethod 

576 def read(cls, reader): 

577 line = reader.readline() 

578 return cls.deserialize(line, reader.version_dialect) 

579 

580 def write(self, writer): 

581 s = self.serialize(writer.version_dialect) 

582 writer.writeline(s) 

583 

584 

585class FreeFormatLine(Block): 

586 ''' 

587 Base class for IMS/GSE2 free format lines. 

588 ''' 

589 

590 @classmethod 

591 def deserialize_values(cls, line, version_dialect): 

592 format = cls.format(version_dialect) 

593 values = line.split(None, len(format)-1) 

594 

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())) 

601 

602 else: 

603 if isinstance(x, tuple): 

604 x, (parse, _) = x 

605 v = parse(v) 

606 

607 values_weeded.append((x, v)) 

608 

609 values_weeded.sort() 

610 return [str(xv[1].decode('ascii')) for xv in values_weeded] 

611 

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) 

618 

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]) 

632 

633 if v is None: 

634 break 

635 

636 out.append(props[x-1].to_save(v)) 

637 

638 return ' '.join(out).encode('ascii') 

639 

640 

641class DataType(Block): 

642 ''' 

643 Representation of a DATA_TYPE line. 

644 ''' 

645 

646 type = String.T() 

647 subtype = String.T(optional=True) 

648 format = String.T() 

649 subformat = String.T(optional=True) 

650 

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') 

657 

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'))) 

663 

664 def serialize(self, version_dialect): 

665 s = self.type 

666 if self.subtype: 

667 s += ':' + self.subtype 

668 

669 f = self.format 

670 if self.subformat: 

671 f += ':' + self.subformat 

672 

673 return ('DATA_TYPE %s %s' % (s, f)).encode('ascii') 

674 

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 

681 

682 def write(self, writer): 

683 s = self.serialize(writer.version_dialect) 

684 writer.version_dialect[0] = self.format 

685 writer.writeline(s) 

686 

687 

688class FTPFile(FreeFormatLine): 

689 ''' 

690 Representation of an FTP_FILE line. 

691 ''' 

692 

693 _format = [b'FTP_FILE', 1, 2, 3, 4] 

694 

695 net_address = String.T() 

696 login_mode = StringChoice.T(choices=('USER', 'GUEST'), ignore_case=True) 

697 directory = String.T() 

698 file = String.T() 

699 

700 

701class WaveformSubformat(StringChoice): 

702 choices = ['INT', 'CM6', 'CM8', 'AU6', 'AU8'] 

703 ignore_case = True 

704 

705 

706class WID2(Block): 

707 ''' 

708 Representation of a WID2 line. 

709 ''' 

710 

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 ] 

726 

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]') 

751 

752 

753class OUT2(Block): 

754 ''' 

755 Representation of an OUT2 line. 

756 ''' 

757 

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 ] 

766 

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() 

774 

775 

776class DLY2(Block): 

777 ''' 

778 Representation of a DLY2 line. 

779 ''' 

780 

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 ] 

789 

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]') 

797 

798 

799class DAT2(Block): 

800 ''' 

801 Representation of a DAT2 line. 

802 ''' 

803 

804 _format = [ 

805 E(1, 4, x_fixed(b'DAT2'), dummy=True) 

806 ] 

807 

808 raw_data = List.T(String.T()) 

809 

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()) 

822 

823 return dat2 

824 

825 def write(self, writer): 

826 Block.write(self, writer) 

827 for line in self.raw_data: 

828 writer.writeline(line) 

829 

830 

831class STA2(Block): 

832 ''' 

833 Representation of a STA2 line. 

834 ''' 

835 

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 ] 

845 

846 # the standard requires lat, lon, elevation and depth, we define them as 

847 # optional, however 

848 

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]') 

855 

856 

857class CHK2(Block): 

858 ''' 

859 Representation of a CHK2 line. 

860 ''' 

861 

862 _format = [ 

863 E(1, 4, x_fixed(b'CHK2'), dummy=True), 

864 E(6, 13, 'i8') 

865 ] 

866 

867 checksum = Int.T() 

868 

869 

870class EID2(Block): 

871 ''' 

872 Representation of an EID2 line. 

873 ''' 

874 

875 _format = [ 

876 E(1, 4, x_fixed(b'EID2'), dummy=True), 

877 E(6, 13, 'a8'), 

878 E(15, 23, 'a9'), 

879 ] 

880 

881 event_id = String.T(help='event ID (8 characters)') 

882 bulletin_type = String.T(help='bulletin type (9 characters)') 

883 

884 

885class BEA2(Block): 

886 ''' 

887 Representation of a BEA2 line. 

888 ''' 

889 

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')] 

895 

896 beam_id = String.T(help='beam ID (12 characters)') 

897 azimuth = Float.T() 

898 slowness = Float.T() 

899 

900 

901class Network(Block): 

902 ''' 

903 Representation of an entry in a NETWORK section. 

904 ''' 

905 

906 _format = [ 

907 E(1, 9, 'a9'), 

908 E(11, None, 'a64+')] 

909 

910 network = String.T(help='network code (9 characters)') 

911 description = String.T(help='description') 

912 

913 

914class Station(Block): 

915 ''' 

916 Representation of an entry in a STATION section. 

917 ''' 

918 

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))]} 

941 

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))] 

946 

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) 

959 

960 

961class Channel(Block): 

962 ''' 

963 Representation of an entry in a CHANNEL section. 

964 ''' 

965 

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))]} 

999 

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))] 

1005 

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))] 

1011 

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) 

1034 

1035 

1036class BeamGroup(Block): 

1037 ''' 

1038 Representation of an entry in a BEAM group table. 

1039 ''' 

1040 

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')] 

1048 

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)') 

1062 

1063 

1064class BeamType(StringChoice): 

1065 choices = ['inc', 'coh'] 

1066 ignore_case = True 

1067 

1068 

1069class FilterType(StringChoice): 

1070 choices = ['BP', 'LP', 'HP', 'BR'] 

1071 ignore_case = True 

1072 

1073 

1074class BeamParameters(Block): 

1075 ''' 

1076 Representation of an entry in a BEAM parameters table. 

1077 ''' 

1078 

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))] 

1094 

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') 

1121 

1122 

1123class OutageReportPeriod(Block): 

1124 ''' 

1125 Representation of a the report period of an OUTAGE section. 

1126 ''' 

1127 

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)] 

1133 

1134 tmin = Timestamp.T() 

1135 tmax = Timestamp.T() 

1136 

1137 

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+')] 

1151 

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() 

1162 

1163 

1164class CAL2(Block): 

1165 ''' 

1166 Representation of a CAL2 line. 

1167 ''' 

1168 

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))]} 

1192 

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)) 

1208 

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 

1220 

1221 obj.append_dataline(line, reader.version_dialect) 

1222 

1223 obj.comments.extend(reader.get_comments_after(lstart)) 

1224 return obj 

1225 

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')) 

1231 

1232 

1233class Units(StringChoice): 

1234 choices = ['V', 'A', 'C'] 

1235 ignore_case = True 

1236 

1237 

1238class Stage(Block): 

1239 ''' 

1240 Base class for IMS/GSE2 response stages. 

1241 

1242 Available response stages are :py:class:`PAZ2`, :py:class:`FAP2`, 

1243 :py:class:`GEN2`, :py:class:`DIG2`, and :py:class:`FIR2`. 

1244 

1245 ''' 

1246 

1247 stage_number = Int.T(help='stage sequence number') 

1248 

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) 

1254 

1255 while True: 

1256 line = reader.readline() 

1257 if line is None or not line.startswith(b' '): 

1258 reader.pushback() 

1259 break 

1260 

1261 obj.append_dataline(line, reader.version_dialect) 

1262 

1263 obj.comments.extend(reader.get_comments_after(lstart)) 

1264 

1265 return obj 

1266 

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')) 

1273 

1274 def write_datalines(self, writer): 

1275 pass 

1276 

1277 

1278class PAZ2Data(Block): 

1279 ''' 

1280 Representation of the complex numbers in PAZ2 sections. 

1281 ''' 

1282 

1283 _format = [ 

1284 E(2, 16, 'e15.8'), 

1285 E(18, 32, 'e15.8')] 

1286 

1287 real = Float.T() 

1288 imag = Float.T() 

1289 

1290 

1291class PAZ2(Stage): 

1292 ''' 

1293 Representation of a PAZ2 line. 

1294 ''' 

1295 

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+')]} 

1317 

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') 

1326 

1327 poles = List.T(Complex.T()) 

1328 zeros = List.T(Complex.T()) 

1329 

1330 comments = List.T(String.T(optional=True)) 

1331 

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) 

1336 

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') 

1344 

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) 

1350 

1351 

1352class FAP2Data(Block): 

1353 ''' 

1354 Representation of the data tuples in FAP2 section. 

1355 ''' 

1356 

1357 _format = [ 

1358 E(2, 11, 'f10.5'), 

1359 E(13, 27, 'e15.8'), 

1360 E(29, 32, 'i4')] 

1361 

1362 frequency = Float.T() 

1363 amplitude = Float.T() 

1364 phase = Float.T() 

1365 

1366 

1367class FAP2(Stage): 

1368 ''' 

1369 Representation of a FAP2 line. 

1370 ''' 

1371 

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')] 

1380 

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') 

1387 

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]') 

1392 

1393 comments = List.T(String.T(optional=True)) 

1394 

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) 

1400 

1401 def write_datalines(self, writer): 

1402 for frequency, amplitude, phase in zip( 

1403 self.frequencies, self.amplitudes, self.phases): 

1404 

1405 FAP2Data( 

1406 frequency=frequency, 

1407 amplitude=amplitude, 

1408 phase=phase).write(writer) 

1409 

1410 

1411class GEN2Data(Block): 

1412 ''' 

1413 Representation of a data tuple in GEN2 section. 

1414 ''' 

1415 

1416 _format = [ 

1417 E(2, 12, 'f11.5'), 

1418 E(14, 19, 'f6.3')] 

1419 

1420 corner = Float.T(help='corner frequency [Hz]') 

1421 slope = Float.T(help='slope above corner [dB/decate]') 

1422 

1423 

1424class GEN2(Stage): 

1425 ''' 

1426 Representation of a GEN2 line. 

1427 ''' 

1428 

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')] 

1439 

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') 

1450 

1451 corners = List.T(Float.T(), help='corner frequencies [Hz]') 

1452 slopes = List.T(Float.T(), help='slopes above corners [dB/decade]') 

1453 

1454 comments = List.T(String.T(optional=True)) 

1455 

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) 

1460 

1461 def write_datalines(self, writer): 

1462 for corner, slope in zip(self.corners, self.slopes): 

1463 GEN2Data(corner=corner, slope=slope).write(writer) 

1464 

1465 

1466class DIG2(Stage): 

1467 ''' 

1468 Representation of a DIG2 line. 

1469 ''' 

1470 

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+')] 

1477 

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') 

1481 

1482 comments = List.T(String.T(optional=True)) 

1483 

1484 

1485class SymmetryFlag(StringChoice): 

1486 choices = ['A', 'B', 'C'] 

1487 ignore_case = True 

1488 

1489 

1490class FIR2Data(Block): 

1491 ''' 

1492 Representation of a line of coefficients in a FIR2 section. 

1493 ''' 

1494 

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')] 

1501 

1502 factors = List.T(Float.T()) 

1503 

1504 def values(self): 

1505 return self.factors + [None]*(5-len(self.factors)) 

1506 

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) 

1512 

1513 

1514class FIR2(Stage): 

1515 ''' 

1516 Representation of a FIR2 line. 

1517 ''' 

1518 

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+')] 

1528 

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') 

1537 

1538 comments = List.T(String.T(optional=True)) 

1539 

1540 factors = List.T(Float.T()) 

1541 

1542 def append_dataline(self, line, version_dialect): 

1543 d = FIR2Data.deserialize(line, version_dialect) 

1544 self.factors.extend(d.factors) 

1545 

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 

1551 

1552 

1553class Begin(FreeFormatLine): 

1554 ''' 

1555 Representation of a BEGIN line. 

1556 ''' 

1557 

1558 _format = [b'BEGIN', 1] 

1559 version = String.T(optional=True) 

1560 

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 

1567 

1568 def write(self, writer): 

1569 FreeFormatLine.write(self, writer) 

1570 writer.version_dialect[0] = self.version 

1571 

1572 

1573class MessageType(StringChoice): 

1574 choices = ['REQUEST', 'DATA', 'SUBSCRIPTION'] 

1575 ignore_case = True 

1576 

1577 

1578class MsgType(FreeFormatLine): 

1579 _format = [b'MSG_TYPE', 1] 

1580 type = MessageType.T() 

1581 

1582 

1583class MsgID(FreeFormatLine): 

1584 ''' 

1585 Representation of a MSG_ID line. 

1586 ''' 

1587 

1588 _format = [b'MSG_ID', 1, 2] 

1589 msg_id_string = String.T() 

1590 msg_id_source = String.T(optional=True) 

1591 

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 

1598 

1599 return obj 

1600 

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 

1605 

1606 

1607class RefID(FreeFormatLine): 

1608 ''' 

1609 Representation of a REF_ID line. 

1610 ''' 

1611 

1612 _format = { 

1613 None: [b'REF_ID', 1, 2, 'PART', 3, 'OF', 4], 

1614 'GSE2.0': [b'REF_ID', 1]} 

1615 

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) 

1620 

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)]) 

1629 

1630 return ' '.join(out).encode('ascii') 

1631 

1632 

1633class LogSection(Section): 

1634 ''' 

1635 Representation of a DATA_TYPE LOG section. 

1636 ''' 

1637 

1638 keyword = b'LOG' 

1639 lines = List.T(String.T()) 

1640 

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'))) 

1652 

1653 return cls(lines=lines) 

1654 

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 

1661 

1662 writer.writeline(str.encode(line)) 

1663 

1664 

1665class ErrorLogSection(LogSection): 

1666 ''' 

1667 Representation of a DATA_TYPE ERROR_LOG section. 

1668 ''' 

1669 

1670 keyword = b'ERROR_LOG' 

1671 

1672 

1673class FTPLogSection(Section): 

1674 ''' 

1675 Representation of a DATA_TYPE FTP_LOG section. 

1676 ''' 

1677 

1678 keyword = b'FTP_LOG' 

1679 ftp_file = FTPFile.T() 

1680 

1681 @classmethod 

1682 def read(cls, reader): 

1683 DataType.read(reader) 

1684 ftp_file = FTPFile.read(reader) 

1685 return cls(ftp_file=ftp_file) 

1686 

1687 def write(self, writer): 

1688 self.write_datatype(writer) 

1689 self.ftp_file.write(writer) 

1690 

1691 

1692class WID2Section(Section): 

1693 ''' 

1694 Representation of a WID2/STA2/EID2/BEA2/DAT2/CHK2 group. 

1695 ''' 

1696 

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() 

1703 

1704 @classmethod 

1705 def read(cls, reader): 

1706 blocks = dict(eid2s=[]) 

1707 expect = [(b'WID2 ', WID2, 1)] 

1708 

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)) 

1714 

1715 expect.extend([ 

1716 (b'EID2 ', EID2, 0), 

1717 (b'BEA2 ', BEA2, 0), 

1718 (b'DAT2', DAT2, 1), 

1719 (b'CHK2 ', CHK2, 1)]) 

1720 

1721 for k, handler, required in expect: 

1722 line = reader.readline() 

1723 reader.pushback() 

1724 

1725 if line is None: 

1726 raise DeserializeError('incomplete waveform section') 

1727 

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 

1739 

1740 return cls(**blocks) 

1741 

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) 

1750 

1751 self.dat2.write(writer) 

1752 self.chk2.write(writer) 

1753 

1754 def pyrocko_trace(self, checksum_error='raise'): 

1755 from pyrocko import ims_ext, trace 

1756 assert checksum_error in ('raise', 'warn', 'ignore') 

1757 

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 

1769 

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) 

1779 

1780 tmax = None 

1781 else: 

1782 tmax = tmin + (nsamples - 1) * deltat 

1783 ydata = None 

1784 

1785 return trace.Trace( 

1786 net, sta, loc, cha, tmin=tmin, tmax=tmax, 

1787 deltat=deltat, 

1788 ydata=ydata) 

1789 

1790 @classmethod 

1791 def from_pyrocko_trace(cls, tr, 

1792 lat=None, lon=None, elevation=None, depth=None): 

1793 

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))) 

1816 

1817 

1818class OUT2Section(Section): 

1819 ''' 

1820 Representation of a OUT2/STA2 group. 

1821 ''' 

1822 

1823 out2 = OUT2.T() 

1824 sta2 = STA2.T() 

1825 

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 

1837 

1838 return cls(out2=out2, sta2=sta2) 

1839 

1840 def write(self, writer): 

1841 self.out2.write(writer) 

1842 if self.sta2 is not None: 

1843 self.sta2.write(writer) 

1844 

1845 

1846class DLY2Section(Section): 

1847 ''' 

1848 Representation of a DLY2/STA2 group. 

1849 ''' 

1850 

1851 dly2 = DLY2.T() 

1852 sta2 = STA2.T() 

1853 

1854 @classmethod 

1855 def read(cls, reader): 

1856 dly2 = DLY2.read(reader) 

1857 sta2 = STA2.read(reader) 

1858 return cls(dly2=dly2, sta2=sta2) 

1859 

1860 def write(self, writer): 

1861 self.dly2.write(writer) 

1862 self.sta2.write(writer) 

1863 

1864 

1865class WaveformSection(Section): 

1866 ''' 

1867 Representation of a DATA_TYPE WAVEFORM line. 

1868 

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 ''' 

1873 

1874 keyword = b'WAVEFORM' 

1875 

1876 datatype = DataType.T() 

1877 

1878 @classmethod 

1879 def read(cls, reader): 

1880 datatype = DataType.read(reader) 

1881 return cls(datatype=datatype) 

1882 

1883 def write(self, writer): 

1884 self.datatype.write(writer) 

1885 

1886 

1887class TableSection(Section): 

1888 ''' 

1889 Base class for table style sections. 

1890 ''' 

1891 

1892 has_data_type_header = True 

1893 

1894 @classmethod 

1895 def read(cls, reader): 

1896 if cls.has_data_type_header: 

1897 DataType.read(reader) 

1898 

1899 ts = cls.table_setup 

1900 

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}) 

1905 

1906 def write(self, writer): 

1907 if self.has_data_type_header: 

1908 self.write_datatype(writer) 

1909 

1910 ts = self.table_setup 

1911 header = get_versioned(ts['header'], writer.version_dialect) 

1912 self.write_table(writer, header, getattr(self, ts['attribute'])) 

1913 

1914 

1915class NetworkSection(TableSection): 

1916 ''' 

1917 Representation of a DATA_TYPE NETWORK section. 

1918 ''' 

1919 

1920 keyword = b'NETWORK' 

1921 table_setup = dict( 

1922 header=b'Net Description', 

1923 attribute='networks', 

1924 cls=Network) 

1925 

1926 networks = List.T(Network.T()) 

1927 

1928 

1929class StationSection(TableSection): 

1930 ''' 

1931 Representation of a DATA_TYPE STATION section. 

1932 ''' 

1933 

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) 

1945 

1946 stations = List.T(Station.T()) 

1947 

1948 

1949class ChannelSection(TableSection): 

1950 ''' 

1951 Representation of a DATA_TYPE CHANNEL section. 

1952 ''' 

1953 

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) 

1967 

1968 channels = List.T(Channel.T()) 

1969 

1970 

1971class BeamSection(Section): 

1972 ''' 

1973 Representation of a DATA_TYPE BEAM section. 

1974 ''' 

1975 

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()) 

1983 

1984 @classmethod 

1985 def read(cls, reader): 

1986 DataType.read(reader) 

1987 

1988 def end(line): 

1989 return line.upper().startswith(b'BEAMID') 

1990 

1991 group = list(cls.read_table(reader, cls.beam_group_header, BeamGroup, 

1992 end)) 

1993 

1994 parameters = list(cls.read_table(reader, cls.beam_parameters_header, 

1995 BeamParameters)) 

1996 

1997 return cls(group=group, parameters=parameters) 

1998 

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) 

2004 

2005 

2006class CAL2Section(Section): 

2007 ''' 

2008 Representation of a CAL2 + stages group in a response section. 

2009 ''' 

2010 

2011 cal2 = CAL2.T() 

2012 stages = List.T(Stage.T()) 

2013 

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} 

2024 

2025 while True: 

2026 line = reader.readline() 

2027 reader.pushback() 

2028 if end_section(line, b'CAL2'): 

2029 break 

2030 

2031 k = line[:4].upper() 

2032 if k in handlers: 

2033 stages.append(handlers[k].read(reader)) 

2034 else: 

2035 raise DeserializeError('unexpected line') 

2036 

2037 return cls(cal2=cal2, stages=stages) 

2038 

2039 def write(self, writer): 

2040 self.cal2.write(writer) 

2041 for stage in self.stages: 

2042 stage.write(writer) 

2043 

2044 

2045class ResponseSection(Section): 

2046 ''' 

2047 Representation of a DATA_TYPE RESPONSE line. 

2048 

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 ''' 

2053 

2054 keyword = b'RESPONSE' 

2055 

2056 datatype = DataType.T() 

2057 

2058 @classmethod 

2059 def read(cls, reader): 

2060 datatype = DataType.read(reader) 

2061 return cls(datatype=datatype) 

2062 

2063 def write(self, writer): 

2064 self.datatype.write(writer) 

2065 

2066 

2067class OutageSection(Section): 

2068 ''' 

2069 Representation of a DATA_TYPE OUTAGE section. 

2070 ''' 

2071 

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()) 

2077 

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)) 

2085 

2086 return cls( 

2087 report_period=report_period, 

2088 outages=outages) 

2089 

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) 

2094 

2095 

2096class BulletinTitle(Block): 

2097 

2098 _format = [ 

2099 E(1, 136, 'a136')] 

2100 

2101 title = String.T() 

2102 

2103 

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='??',) 

2121 

2122 

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')] 

2148 

2149 time = Timestamp.T( 

2150 help='epicenter date and time') 

2151 

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') 

2157 

2158 time_error = Float.T( 

2159 optional=True, 

2160 help='origin time error [seconds], ``None`` if fixed origin time') 

2161 

2162 residual = Float.T( 

2163 optional=True, 

2164 help='root mean square of time residuals [seconds]') 

2165 

2166 lat = Float.T( 

2167 help='latitude') 

2168 

2169 lon = Float.T( 

2170 help='longitude') 

2171 

2172 lat_lon_fixed = StringChoice.T( 

2173 choices=['f'], optional=True, 

2174 help='fixed flag, ``"f"`` if fixed epicenter solution, ' 

2175 '``None`` if not') 

2176 

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') 

2181 

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') 

2186 

2187 ellipse_strike = Float.T( 

2188 optional=True, 

2189 help='strike of 90% c. i. ellipse [0-360], ``None`` if fixed') 

2190 

2191 depth = Float.T( 

2192 help='depth [m]') 

2193 

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') 

2198 

2199 depth_error = Float.T( 

2200 optional=True, 

2201 help='depth error [m], 90% c. i., ``None`` if fixed depth') 

2202 

2203 nphases = Int.T( 

2204 optional=True, 

2205 help='number of defining phases') 

2206 

2207 nstations = Int.T( 

2208 optional=True, 

2209 help='number of defining stations') 

2210 

2211 azimuthal_gap = Float.T( 

2212 optional=True, 

2213 help='gap in azimuth coverage [deg]') 

2214 

2215 distance_min = Float.T( 

2216 optional=True, 

2217 help='distance to closest station [deg]') 

2218 

2219 distance_max = Float.T( 

2220 optional=True, 

2221 help='distance to furthest station [deg]') 

2222 

2223 analysis_type = StringChoice.T( 

2224 optional=True, 

2225 choices=['a', 'm', 'g'], 

2226 help='analysis type, ``"a"`` automatic, ``"m"`` manual, ``"g"`` guess') 

2227 

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') 

2233 

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()))) 

2240 

2241 author = String.T(help='author of the origin') 

2242 origin_id = String.T(help='origin identification') 

2243 

2244 

2245class OriginSection(TableSection): 

2246 has_data_type_header = False 

2247 

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) 

2257 

2258 origins = List.T(Origin.T()) 

2259 

2260 

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')] 

2266 

2267 event_id = String.T() 

2268 region = String.T() 

2269 

2270 

2271class EventSection(Section): 

2272 ''' 

2273 Groups Event, Arrival, ... 

2274 ''' 

2275 

2276 event_title = EventTitle.T() 

2277 origin_section = OriginSection.T() 

2278 

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) 

2286 

2287 def write(self, writer): 

2288 self.event_title.write(writer) 

2289 self.origin_section.write(writer) 

2290 

2291 

2292class EventsSection(Section): 

2293 ''' 

2294 Representation of a DATA_TYPE EVENT section. 

2295 ''' 

2296 

2297 keyword = b'EVENT' 

2298 

2299 bulletin_title = BulletinTitle.T() 

2300 event_sections = List.T(EventSection.T()) 

2301 

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 

2312 

2313 if line.upper().startswith(b'EVENT'): 

2314 event_sections.append(EventSection.read(reader)) 

2315 

2316 return cls( 

2317 bulletin_title=bulletin_title, 

2318 event_sections=event_sections, 

2319 ) 

2320 

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) 

2326 

2327 

2328class BulletinSection(EventsSection): 

2329 ''' 

2330 Representation of a DATA_TYPE BULLETIN section. 

2331 ''' 

2332 

2333 keyword = b'BULLETIN' 

2334 

2335 

2336for sec in ( 

2337 LogSection, ErrorLogSection, FTPLogSection, WaveformSection, 

2338 NetworkSection, StationSection, ChannelSection, BeamSection, 

2339 ResponseSection, OutageSection, EventsSection, BulletinSection): 

2340 

2341 Section.handlers[sec.keyword] = sec 

2342 

2343del sec 

2344 

2345 

2346class MessageHeader(Section): 

2347 ''' 

2348 Representation of a BEGIN/MSG_TYPE/MSG_ID/REF_ID group. 

2349 ''' 

2350 

2351 version = String.T() 

2352 type = String.T() 

2353 msg_id = MsgID.T(optional=True) 

2354 ref_id = RefID.T(optional=True) 

2355 

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} 

2363 

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 

2373 

2374 if not ok: 

2375 break 

2376 

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)) 

2382 

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) 

2390 

2391 

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], '' 

2398 

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') 

2402 

2403 

2404def string_ff_date_time(t): 

2405 return util.time_to_str(t, format='%Y/%m/%d %H:%M:%S.3FRAC') 

2406 

2407 

2408class TimeStamp(FreeFormatLine): 

2409 ''' 

2410 Representation of a TIME_STAMP line. 

2411 ''' 

2412 

2413 _format = [b'TIME_STAMP', 1] 

2414 

2415 value = Timestamp.T() 

2416 

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)) 

2421 

2422 def serialize(self, line, version_dialect): 

2423 return ( 

2424 'TIME_STAMP %s' % string_ff_date_time(self.value)).encode('ascii') 

2425 

2426 

2427class Stop(FreeFormatLine): 

2428 ''' 

2429 Representation of a STOP line. 

2430 ''' 

2431 

2432 _format = [b'STOP'] 

2433 

2434 dummy = String.T(optional=True) 

2435 

2436 

2437class XW01(FreeFormatLine): 

2438 ''' 

2439 Representation of a XW01 line (which is a relict from GSE1). 

2440 ''' 

2441 

2442 _format = [b'XW01'] 

2443 

2444 dummy = String.T(optional=True) 

2445 

2446 

2447re_comment = re.compile(br'^(%(.+)\s*| \((#?)(.+)\)\s*)$') 

2448re_comment_usa_dmc = re.compile(br'^(%(.+)\s*| ?\((#?)(.+)\)\s*)$') 

2449 

2450 

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 

2476 

2477 def tell(self): 

2478 return self._current_fpos 

2479 

2480 def current_line_number(self): 

2481 return self._current_lpos - int(self._pushed_back) 

2482 

2483 def readline(self): 

2484 if self._pushed_back: 

2485 self._pushed_back = False 

2486 return self._current_line 

2487 

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 

2496 

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')) 

2503 

2504 self._current_line = b''.join(lines) 

2505 

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) 

2510 

2511 if not self._current_line.strip(): 

2512 pass 

2513 

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' 

2520 

2521 comment = m_comment.group(2) or m_comment.group(4) 

2522 

2523 self._comment_lines.append( 

2524 (self._current_lpos, comment_type, 

2525 str(comment.decode('ascii')))) 

2526 

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)) 

2531 

2532 else: 

2533 return self._current_line 

2534 

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 

2541 

2542 comments.append(self._comment_lines[i][-1]) 

2543 i -= 1 

2544 

2545 return comments 

2546 

2547 def pushback(self): 

2548 assert not self._pushed_back 

2549 self._pushed_back = True 

2550 

2551 def __iter__(self): 

2552 return self 

2553 

2554 def next(self): 

2555 return self.__next__() 

2556 

2557 def __next__(self): 

2558 try: 

2559 while True: 

2560 line = self.readline() 

2561 if line is None: 

2562 raise StopIteration() 

2563 

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 

2570 

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 

2578 

2579 if not self._in_garbage and not ignore: 

2580 raise DeserializeError('unexpected line') 

2581 

2582 except DeserializeError as e: 

2583 e.set_context( 

2584 self._current_lpos, 

2585 self._current_line, 

2586 self.version_dialect) 

2587 raise 

2588 

2589 

2590class Writer(object): 

2591 

2592 def __init__(self, f, version='GSE2.1', dialect=None): 

2593 self._f = f 

2594 self.version_dialect = [version, dialect] 

2595 

2596 def write(self, section): 

2597 section.write(self) 

2598 

2599 def writeline(self, line): 

2600 self._f.write(line.rstrip()) 

2601 self._f.write(b'\n') 

2602 

2603 

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) 

2610 

2611 return f.getvalue() 

2612 

2613 

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 

2622 

2623 except DeserializeError as e: 

2624 raise FileLoadError(e) 

2625 

2626 

2627def iload_string(s, **kwargs): 

2628 ''' 

2629 Read IMS/GSE2 sections from bytes string. 

2630 ''' 

2631 

2632 from io import BytesIO 

2633 f = BytesIO(s) 

2634 return iload_fh(f, **kwargs) 

2635 

2636 

2637iload_filename, iload_dirname, iload_glob, iload = util.make_iload_family( 

2638 iload_fh, 'IMS/GSE2', ':py:class:`Section`') 

2639 

2640 

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) 

2649 

2650 except SerializeError as e: 

2651 raise FileSaveError(e) 

2652 

2653 

2654def dump_string(sections): 

2655 ''' 

2656 Write IMS/GSE2 sections to string. 

2657 ''' 

2658 

2659 from io import BytesIO 

2660 f = BytesIO() 

2661 dump_fh(sections, f) 

2662 return f.getvalue() 

2663 

2664 

2665if __name__ == '__main__': 

2666 from optparse import OptionParser 

2667 

2668 usage = 'python -m pyrocko.ims <filenames>' 

2669 

2670 util.setup_logging('pyrocko.ims.__main__', 'warning') 

2671 

2672 description = ''' 

2673 Read and print IMS/GSE2 records. 

2674 ''' 

2675 

2676 parser = OptionParser( 

2677 usage=usage, 

2678 description=description, 

2679 formatter=util.BetterHelpFormatter()) 

2680 

2681 parser.add_option( 

2682 '--version', 

2683 dest='version', 

2684 choices=g_versions, 

2685 help='inial guess for version') 

2686 

2687 parser.add_option( 

2688 '--dialect', 

2689 dest='dialect', 

2690 choices=g_dialects, 

2691 help='inial guess for dialect') 

2692 

2693 parser.add_option( 

2694 '--load-data', 

2695 dest='load_data', 

2696 action='store_true', 

2697 help='unpack data samples') 

2698 

2699 parser.add_option( 

2700 '--out-version', 

2701 dest='out_version', 

2702 choices=g_versions, 

2703 help='output format version') 

2704 

2705 parser.add_option( 

2706 '--out-dialect', 

2707 dest='out_dialect', 

2708 choices=g_dialects, 

2709 help='output format dialect') 

2710 

2711 (options, args) = parser.parse_args(sys.argv[1:]) 

2712 

2713 for fn in args: 

2714 with open(fn, 'rb') as f: 

2715 

2716 r = Reader(f, load_data=options.load_data, 

2717 version=options.version, dialect=options.dialect) 

2718 

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) 

2724 

2725 for sec in r: 

2726 if not w: 

2727 print(sec) 

2728 

2729 else: 

2730 w.write(sec) 

2731 

2732 if isinstance(sec, WID2Section) and options.load_data: 

2733 tr = sec.pyrocko_trace(checksum_error='warn')