1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6from __future__ import absolute_import, print_function 

7 

8import hashlib 

9import numpy as num 

10from os import urandom 

11from base64 import urlsafe_b64encode 

12from collections import defaultdict 

13 

14from pyrocko import util 

15from pyrocko.guts import Object, String, Timestamp, Float, Int, Unicode, \ 

16 Tuple, List, StringChoice, Any 

17from pyrocko.model import Content 

18from pyrocko.response import FrequencyResponse, MultiplyResponse, \ 

19 IntegrationResponse, DifferentiationResponse, simplify_responses, \ 

20 FrequencyResponseCheckpoint 

21 

22from .error import ConversionError 

23 

24 

25guts_prefix = 'squirrel' 

26 

27separator = '\t' 

28 

29g_content_kinds = [ 

30 'undefined', 

31 'waveform', 

32 'station', 

33 'channel', 

34 'response', 

35 'event', 

36 'waveform_promise'] 

37 

38 

39g_content_kind_ids = ( 

40 UNDEFINED, WAVEFORM, STATION, CHANNEL, RESPONSE, EVENT, 

41 WAVEFORM_PROMISE) = range(len(g_content_kinds)) 

42 

43g_tmin, g_tmax = util.get_working_system_time_range()[:2] 

44 

45 

46try: 

47 g_tmin_queries = max(g_tmin, util.str_to_time_fillup('1900-01-01')) 

48except Exception: 

49 g_tmin_queries = g_tmin 

50 

51 

52def to_kind(kind_id): 

53 return g_content_kinds[kind_id] 

54 

55 

56def to_kinds(kind_ids): 

57 return [g_content_kinds[kind_id] for kind_id in kind_ids] 

58 

59 

60def to_kind_id(kind): 

61 return g_content_kinds.index(kind) 

62 

63 

64def to_kind_ids(kinds): 

65 return [g_content_kinds.index(kind) for kind in kinds] 

66 

67 

68g_kind_mask_all = 0xff 

69 

70 

71def to_kind_mask(kinds): 

72 if kinds: 

73 return sum(1 << kind_id for kind_id in to_kind_ids(kinds)) 

74 else: 

75 return g_kind_mask_all 

76 

77 

78def str_or_none(x): 

79 if x is None: 

80 return None 

81 else: 

82 return str(x) 

83 

84 

85def float_or_none(x): 

86 if x is None: 

87 return None 

88 else: 

89 return float(x) 

90 

91 

92def int_or_none(x): 

93 if x is None: 

94 return None 

95 else: 

96 return int(x) 

97 

98 

99def int_or_g_tmin(x): 

100 if x is None: 

101 return g_tmin 

102 else: 

103 return int(x) 

104 

105 

106def int_or_g_tmax(x): 

107 if x is None: 

108 return g_tmax 

109 else: 

110 return int(x) 

111 

112 

113def tmin_or_none(x): 

114 if x == g_tmin: 

115 return None 

116 else: 

117 return x 

118 

119 

120def tmax_or_none(x): 

121 if x == g_tmax: 

122 return None 

123 else: 

124 return x 

125 

126 

127def time_or_none_to_str(x): 

128 if x is None: 

129 return '...'.ljust(17) 

130 else: 

131 return util.time_to_str(x) 

132 

133 

134def codes_to_str_abbreviated(codes, indent=' '): 

135 codes = ['.'.join(x) for x in codes] 

136 

137 if len(codes) > 20: 

138 scodes = '\n' + util.ewrap(codes[:10], indent=indent) \ 

139 + '\n%s[%i more]\n' % (indent, len(codes) - 20) \ 

140 + util.ewrap(codes[-10:], indent=' ') 

141 else: 

142 scodes = '\n' + util.ewrap(codes, indent=indent) \ 

143 if codes else '<none>' 

144 

145 return scodes 

146 

147 

148g_offset_time_unit_inv = 1000000000 

149g_offset_time_unit = 1.0 / g_offset_time_unit_inv 

150 

151 

152def tsplit(t): 

153 if t is None: 

154 return None, 0.0 

155 

156 t = util.to_time_float(t) 

157 if type(t) is float: 

158 t = round(t, 5) 

159 else: 

160 t = round(t, 9) 

161 

162 seconds = num.floor(t) 

163 offset = t - seconds 

164 return int(seconds), int(round(offset * g_offset_time_unit_inv)) 

165 

166 

167def tjoin(seconds, offset): 

168 if seconds is None: 

169 return None 

170 

171 return util.to_time_float(seconds) \ 

172 + util.to_time_float(offset*g_offset_time_unit) 

173 

174 

175tscale_min = 1 

176tscale_max = 365 * 24 * 3600 # last edge is one above 

177tscale_logbase = 20 

178 

179tscale_edges = [tscale_min] 

180while True: 

181 tscale_edges.append(tscale_edges[-1]*tscale_logbase) 

182 if tscale_edges[-1] >= tscale_max: 

183 break 

184 

185 

186tscale_edges = num.array(tscale_edges) 

187 

188 

189def tscale_to_kscale(tscale): 

190 

191 # 0 <= x < tscale_edges[1]: 0 

192 # tscale_edges[1] <= x < tscale_edges[2]: 1 

193 # ... 

194 # tscale_edges[len(tscale_edges)-1] <= x: len(tscale_edges) 

195 

196 return int(num.searchsorted(tscale_edges, tscale)) 

197 

198 

199class WaveformPromise(Content): 

200 ''' 

201 Information about a waveform potentially available at a remote site. 

202 ''' 

203 

204 agency = String.T(default='', help='Agency code (2-5)') 

205 network = String.T(default='', help='Deployment/network code (1-8)') 

206 station = String.T(default='', help='Station code (1-5)') 

207 location = String.T(default='', help='Location code (0-2)') 

208 channel = String.T(default='', help='Channel code (3)') 

209 extra = String.T(default='', help='Extra/custom code') 

210 

211 tmin = Timestamp.T() 

212 tmax = Timestamp.T() 

213 

214 deltat = Float.T(optional=True) 

215 

216 source_hash = String.T() 

217 

218 @property 

219 def codes(self): 

220 return ( 

221 self.agency, self.network, self.station, self.location, 

222 self.channel, self.extra) 

223 

224 @property 

225 def time_span(self): 

226 return (self.tmin, self.tmax) 

227 

228 

229class InvalidWaveform(Exception): 

230 pass 

231 

232 

233class WaveformOrder(Object): 

234 source_id = String.T() 

235 codes = Tuple.T(None, String.T()) 

236 deltat = Float.T() 

237 tmin = Timestamp.T() 

238 tmax = Timestamp.T() 

239 gaps = List.T(Tuple.T(2, Timestamp.T())) 

240 

241 @property 

242 def client(self): 

243 return self.source_id.split(':')[1] 

244 

245 def describe(self, site='?'): 

246 return '%s:%s %s [%s - %s]' % ( 

247 self.client, site, '.'.join(self.codes), 

248 util.time_to_str(self.tmin), util.time_to_str(self.tmax)) 

249 

250 def validate(self, tr): 

251 if tr.ydata.size == 0: 

252 raise InvalidWaveform( 

253 'waveform with zero data samples') 

254 

255 if tr.deltat != self.deltat: 

256 raise InvalidWaveform( 

257 'incorrect sampling interval - waveform: %g s, ' 

258 'meta-data: %g s' % ( 

259 tr.deltat, self.deltat)) 

260 

261 if not num.all(num.isfinite(tr.ydata)): 

262 raise InvalidWaveform('waveform has NaN values') 

263 

264 

265def order_summary(orders): 

266 codes = sorted(set(order.codes[1:-1] for order in orders)) 

267 if len(codes) >= 2: 

268 return '%i orders, %s - %s' % ( 

269 len(orders), 

270 '.'.join(codes[0]), 

271 '.'.join(codes[-1])) 

272 

273 else: 

274 return '%i orders, %s' % ( 

275 len(orders), 

276 '.'.join(codes[0])) 

277 

278 

279class Station(Content): 

280 ''' 

281 A seismic station. 

282 ''' 

283 

284 agency = String.T(default='', help='Agency code (2-5)') 

285 network = String.T(default='', help='Deployment/network code (1-8)') 

286 station = String.T(default='', help='Station code (1-5)') 

287 location = String.T(default='', optional=True, help='Location code (0-2)') 

288 

289 tmin = Timestamp.T(optional=True) 

290 tmax = Timestamp.T(optional=True) 

291 

292 lat = Float.T() 

293 lon = Float.T() 

294 elevation = Float.T(optional=True) 

295 depth = Float.T(optional=True) 

296 

297 description = Unicode.T(optional=True) 

298 

299 @property 

300 def codes(self): 

301 return ( 

302 self.agency, self.network, self.station, 

303 self.location if self.location is not None else '*') 

304 

305 @property 

306 def time_span(self): 

307 return (self.tmin, self.tmax) 

308 

309 def get_pyrocko_station(self): 

310 from pyrocko import model 

311 return model.Station( 

312 network=self.network, 

313 station=self.station, 

314 location=self.location if self.location is not None else '*', 

315 lat=self.lat, 

316 lon=self.lon, 

317 elevation=self.elevation, 

318 depth=self.depth) 

319 

320 def _get_pyrocko_station_args(self): 

321 return ( 

322 '*', 

323 self.network, 

324 self.station, 

325 self.location if self.location is not None else '*', 

326 self.lat, 

327 self.lon, 

328 self.elevation, 

329 self.depth) 

330 

331 

332class Channel(Content): 

333 ''' 

334 A channel of a seismic station. 

335 ''' 

336 

337 agency = String.T(default='', help='Agency code (2-5)') 

338 network = String.T(default='', help='Deployment/network code (1-8)') 

339 station = String.T(default='', help='Station code (1-5)') 

340 location = String.T(default='', help='Location code (0-2)') 

341 channel = String.T(default='', help='Channel code (3)') 

342 extra = String.T(default='', help='Extra/custom code') 

343 

344 tmin = Timestamp.T(optional=True) 

345 tmax = Timestamp.T(optional=True) 

346 

347 lat = Float.T() 

348 lon = Float.T() 

349 elevation = Float.T(optional=True) 

350 depth = Float.T(optional=True) 

351 

352 dip = Float.T(optional=True) 

353 azimuth = Float.T(optional=True) 

354 deltat = Float.T(optional=True) 

355 

356 @property 

357 def codes(self): 

358 return ( 

359 self.agency, self.network, self.station, self.location, 

360 self.channel, self.extra) 

361 

362 def set_codes( 

363 self, agency=None, network=None, station=None, location=None, 

364 channel=None, extra=None): 

365 

366 if agency is not None: 

367 self.agency = agency 

368 if network is not None: 

369 self.network = network 

370 if station is not None: 

371 self.station = station 

372 if location is not None: 

373 self.location = location 

374 if channel is not None: 

375 self.channel = channel 

376 if extra is not None: 

377 self.extra = extra 

378 

379 @property 

380 def time_span(self): 

381 return (self.tmin, self.tmax) 

382 

383 def get_pyrocko_channel(self): 

384 from pyrocko import model 

385 return model.Channel( 

386 name=self.channel, 

387 azimuth=self.azimuth, 

388 dip=self.dip) 

389 

390 def _get_pyrocko_station_args(self): 

391 return ( 

392 self.channel, 

393 self.network, 

394 self.station, 

395 self.location, 

396 self.lat, 

397 self.lon, 

398 self.elevation, 

399 self.depth) 

400 

401 def _get_pyrocko_channel_args(self): 

402 return ( 

403 '*', 

404 self.channel, 

405 self.azimuth, 

406 self.dip) 

407 

408 def _get_sensor_args(self): 

409 return ( 

410 self.agency, 

411 self.network, 

412 self.station, 

413 self.location, 

414 self.channel[:-1] + '?', 

415 self.extra, 

416 self.lat, 

417 self.lon, 

418 self.elevation, 

419 self.depth, 

420 self.deltat, 

421 self.tmin, 

422 self.tmax) 

423 

424 

425class Sensor(Channel): 

426 ''' 

427 Representation of a channel group. 

428 ''' 

429 

430 def grouping(self, channel): 

431 return channel._get_sensor_args() 

432 

433 @classmethod 

434 def from_channels(cls, channels): 

435 groups = defaultdict(list) 

436 for channel in channels: 

437 groups[channel._get_sensor_args()].append(channel) 

438 

439 return [cls( 

440 agency=args[0], 

441 network=args[1], 

442 station=args[2], 

443 location=args[3], 

444 channel=args[4], 

445 extra=args[5], 

446 lat=args[6], 

447 lon=args[7], 

448 elevation=args[8], 

449 depth=args[9], 

450 deltat=args[10], 

451 tmin=args[11], 

452 tmax=args[12]) 

453 for args, _ in groups.items()] 

454 

455 

456observational_quantities = [ 

457 'acceleration', 'velocity', 'displacement', 'pressure', 'rotation', 

458 'temperature'] 

459 

460 

461technical_quantities = [ 

462 'voltage', 'counts'] 

463 

464 

465class QuantityType(StringChoice): 

466 choices = observational_quantities + technical_quantities 

467 

468 

469class ResponseStage(Object): 

470 input_quantity = QuantityType.T(optional=True) 

471 input_sample_rate = Float.T(optional=True) 

472 output_quantity = QuantityType.T(optional=True) 

473 output_sample_rate = Float.T(optional=True) 

474 elements = List.T(FrequencyResponse.T()) 

475 log = List.T(Tuple.T(3, String.T())) 

476 

477 @property 

478 def stage_type(self): 

479 if self.input_quantity in observational_quantities \ 

480 and self.output_quantity in observational_quantities: 

481 return 'conversion' 

482 

483 if self.input_quantity in observational_quantities \ 

484 and self.output_quantity == 'voltage': 

485 return 'sensor' 

486 

487 elif self.input_quantity == 'voltage' \ 

488 and self.output_quantity == 'voltage': 

489 return 'electronics' 

490 

491 elif self.input_quantity == 'voltage' \ 

492 and self.output_quantity == 'counts': 

493 return 'digitizer' 

494 

495 elif self.input_quantity == 'counts' \ 

496 and self.output_quantity == 'counts' \ 

497 and self.input_sample_rate != self.output_sample_rate: 

498 return 'decimation' 

499 

500 elif self.input_quantity in observational_quantities \ 

501 and self.output_quantity == 'counts': 

502 return 'combination' 

503 

504 else: 

505 return 'unknown' 

506 

507 @property 

508 def summary(self): 

509 irate = self.input_sample_rate 

510 orate = self.output_sample_rate 

511 factor = None 

512 if irate and orate: 

513 factor = irate / orate 

514 return 'ResponseStage, ' + ( 

515 '%s%s => %s%s%s' % ( 

516 self.input_quantity or '?', 

517 ' @ %g Hz' % irate if irate else '', 

518 self.output_quantity or '?', 

519 ' @ %g Hz' % orate if orate else '', 

520 ' :%g' % factor if factor else '') 

521 ) 

522 

523 def get_effective(self): 

524 return MultiplyResponse(responses=list(self.elements)) 

525 

526 

527D = 'displacement' 

528V = 'velocity' 

529A = 'acceleration' 

530 

531g_converters = { 

532 (V, D): IntegrationResponse(1), 

533 (A, D): IntegrationResponse(2), 

534 (D, V): DifferentiationResponse(1), 

535 (A, V): IntegrationResponse(1), 

536 (D, A): DifferentiationResponse(2), 

537 (V, A): DifferentiationResponse(1)} 

538 

539 

540def response_converters(input_quantity, output_quantity): 

541 if input_quantity is None or input_quantity == output_quantity: 

542 return [] 

543 

544 if output_quantity is None: 

545 raise ConversionError('Unspecified target quantity.') 

546 

547 try: 

548 return [g_converters[input_quantity, output_quantity]] 

549 

550 except KeyError: 

551 raise ConversionError('No rule to convert from "%s" to "%s".' % ( 

552 input_quantity, output_quantity)) 

553 

554 

555class Response(Content): 

556 ''' 

557 The instrument response of a seismic station channel. 

558 ''' 

559 

560 agency = String.T(default='', help='Agency code (2-5)') 

561 network = String.T(default='', help='Deployment/network code (1-8)') 

562 station = String.T(default='', help='Station code (1-5)') 

563 location = String.T(default='', help='Location code (0-2)') 

564 channel = String.T(default='', help='Channel code (3)') 

565 extra = String.T(default='', help='Extra/custom code') 

566 

567 tmin = Timestamp.T(optional=True) 

568 tmax = Timestamp.T(optional=True) 

569 

570 stages = List.T(ResponseStage.T()) 

571 checkpoints = List.T(FrequencyResponseCheckpoint.T()) 

572 

573 deltat = Float.T(optional=True) 

574 log = List.T(Tuple.T(3, String.T())) 

575 

576 @property 

577 def codes(self): 

578 return ( 

579 self.agency, self.network, self.station, self.location, 

580 self.channel, self.extra) 

581 

582 @property 

583 def time_span(self): 

584 return (self.tmin, self.tmax) 

585 

586 @property 

587 def nstages(self): 

588 return len(self.stages) 

589 

590 @property 

591 def input_quantity(self): 

592 return self.stages[0].input_quantity if self.stages else None 

593 

594 @property 

595 def output_quantity(self): 

596 return self.stages[-1].input_quantity if self.stages else None 

597 

598 @property 

599 def output_sample_rate(self): 

600 return self.stages[-1].output_sample_rate if self.stages else None 

601 

602 @property 

603 def stages_summary(self): 

604 def grouped(xs): 

605 xs = list(xs) 

606 g = [] 

607 for i in range(len(xs)): 

608 g.append(xs[i]) 

609 if i+1 < len(xs) and xs[i+1] != xs[i]: 

610 yield g 

611 g = [] 

612 

613 if g: 

614 yield g 

615 

616 return '+'.join( 

617 '%s%s' % (g[0], '(%i)' % len(g) if len(g) > 1 else '') 

618 for g in grouped(stage.stage_type for stage in self.stages)) 

619 

620 @property 

621 def summary(self): 

622 orate = self.output_sample_rate 

623 return Content.summary.fget(self) + ', ' + ', '.join(( 

624 '%s => %s' % ( 

625 self.input_quantity or '?', self.output_quantity or '?') 

626 + (' @ %g Hz' % orate if orate else ''), 

627 self.stages_summary, 

628 )) 

629 

630 def get_effective(self, input_quantity=None): 

631 elements = response_converters(input_quantity, self.input_quantity) 

632 

633 elements.extend( 

634 stage.get_effective() for stage in self.stages) 

635 

636 return MultiplyResponse(responses=simplify_responses(elements)) 

637 

638 

639class Event(Content): 

640 ''' 

641 A seismic event. 

642 ''' 

643 

644 name = String.T(optional=True) 

645 time = Timestamp.T() 

646 duration = Float.T(optional=True) 

647 

648 lat = Float.T() 

649 lon = Float.T() 

650 depth = Float.T(optional=True) 

651 

652 magnitude = Float.T(optional=True) 

653 

654 def get_pyrocko_event(self): 

655 from pyrocko import model 

656 model.Event( 

657 name=self.name, 

658 time=self.time, 

659 lat=self.lat, 

660 lon=self.lon, 

661 depth=self.depth, 

662 magnitude=self.magnitude, 

663 duration=self.duration) 

664 

665 @property 

666 def time_span(self): 

667 return (self.time, self.time) 

668 

669 

670def ehash(s): 

671 return hashlib.sha1(s.encode('utf8')).hexdigest() 

672 

673 

674def random_name(n=8): 

675 return urlsafe_b64encode(urandom(n)).rstrip(b'=').decode('ascii') 

676 

677 

678class Nut(Object): 

679 ''' 

680 Index entry referencing an elementary piece of content. 

681 

682 So-called *nuts* are used in Pyrocko's Squirrel framework to hold common 

683 meta-information about individual pieces of waveforms, stations, channels, 

684 etc. together with the information where it was found or generated. 

685 ''' 

686 

687 file_path = String.T(optional=True) 

688 file_format = String.T(optional=True) 

689 file_mtime = Timestamp.T(optional=True) 

690 file_size = Int.T(optional=True) 

691 

692 file_segment = Int.T(optional=True) 

693 file_element = Int.T(optional=True) 

694 

695 kind_id = Int.T() 

696 codes = String.T() 

697 

698 tmin_seconds = Timestamp.T() 

699 tmin_offset = Int.T(default=0, optional=True) 

700 tmax_seconds = Timestamp.T() 

701 tmax_offset = Int.T(default=0, optional=True) 

702 

703 deltat = Float.T(default=0.0) 

704 

705 content = Content.T(optional=True) 

706 

707 content_in_db = False 

708 

709 def __init__( 

710 self, 

711 file_path=None, 

712 file_format=None, 

713 file_mtime=None, 

714 file_size=None, 

715 file_segment=None, 

716 file_element=None, 

717 kind_id=0, 

718 codes='', 

719 tmin_seconds=None, 

720 tmin_offset=0, 

721 tmax_seconds=None, 

722 tmax_offset=0, 

723 deltat=None, 

724 content=None, 

725 tmin=None, 

726 tmax=None, 

727 values_nocheck=None): 

728 

729 if values_nocheck is not None: 

730 (self.file_path, self.file_format, self.file_mtime, 

731 self.file_size, 

732 self.file_segment, self.file_element, 

733 self.kind_id, self.codes, 

734 self.tmin_seconds, self.tmin_offset, 

735 self.tmax_seconds, self.tmax_offset, 

736 self.deltat) = values_nocheck 

737 

738 self.content = None 

739 else: 

740 if tmin is not None: 

741 tmin_seconds, tmin_offset = tsplit(tmin) 

742 

743 if tmax is not None: 

744 tmax_seconds, tmax_offset = tsplit(tmax) 

745 

746 self.kind_id = int(kind_id) 

747 self.codes = str(codes) 

748 self.tmin_seconds = int_or_g_tmin(tmin_seconds) 

749 self.tmin_offset = int(tmin_offset) 

750 self.tmax_seconds = int_or_g_tmax(tmax_seconds) 

751 self.tmax_offset = int(tmax_offset) 

752 self.deltat = float_or_none(deltat) 

753 self.file_path = str_or_none(file_path) 

754 self.file_segment = int_or_none(file_segment) 

755 self.file_element = int_or_none(file_element) 

756 self.file_format = str_or_none(file_format) 

757 self.file_mtime = float_or_none(file_mtime) 

758 self.file_size = int_or_none(file_size) 

759 self.content = content 

760 

761 Object.__init__(self, init_props=False) 

762 

763 def __eq__(self, other): 

764 return (isinstance(other, Nut) and 

765 self.equality_values == other.equality_values) 

766 

767 def hash(self): 

768 return ehash(','.join(str(x) for x in self.key)) 

769 

770 def __ne__(self, other): 

771 return not (self == other) 

772 

773 def get_io_backend(self): 

774 from . import io 

775 return io.get_backend(self.file_format) 

776 

777 def file_modified(self): 

778 return self.get_io_backend().get_stats(self.file_path) \ 

779 != (self.file_mtime, self.file_size) 

780 

781 @property 

782 def dkey(self): 

783 return (self.kind_id, self.tmin_seconds, self.tmin_offset, self.codes) 

784 

785 @property 

786 def key(self): 

787 return ( 

788 self.file_path, 

789 self.file_segment, 

790 self.file_element, 

791 self.file_mtime) 

792 

793 @property 

794 def equality_values(self): 

795 return ( 

796 self.file_segment, self.file_element, 

797 self.kind_id, self.codes, 

798 self.tmin_seconds, self.tmin_offset, 

799 self.tmax_seconds, self.tmax_offset, self.deltat) 

800 

801 @property 

802 def tmin(self): 

803 return tjoin(self.tmin_seconds, self.tmin_offset) 

804 

805 @property 

806 def tmax(self): 

807 return tjoin(self.tmax_seconds, self.tmax_offset) 

808 

809 @property 

810 def kscale(self): 

811 if self.tmin_seconds is None or self.tmax_seconds is None: 

812 return 0 

813 return tscale_to_kscale(self.tmax_seconds - self.tmin_seconds) 

814 

815 @property 

816 def waveform_kwargs(self): 

817 agency, network, station, location, channel, extra = \ 

818 self.codes.split(separator) 

819 

820 return dict( 

821 agency=agency, 

822 network=network, 

823 station=station, 

824 location=location, 

825 channel=channel, 

826 extra=extra, 

827 tmin=self.tmin, 

828 tmax=self.tmax, 

829 deltat=self.deltat) 

830 

831 @property 

832 def waveform_promise_kwargs(self): 

833 agency, network, station, location, channel, extra = \ 

834 self.codes.split(separator) 

835 

836 return dict( 

837 agency=agency, 

838 network=network, 

839 station=station, 

840 location=location, 

841 channel=channel, 

842 extra=extra, 

843 tmin=self.tmin, 

844 tmax=self.tmax, 

845 deltat=self.deltat) 

846 

847 @property 

848 def station_kwargs(self): 

849 agency, network, station, location = self.codes.split(separator) 

850 return dict( 

851 agency=agency, 

852 network=network, 

853 station=station, 

854 location=location if location != '*' else None, 

855 tmin=tmin_or_none(self.tmin), 

856 tmax=tmax_or_none(self.tmax)) 

857 

858 @property 

859 def channel_kwargs(self): 

860 agency, network, station, location, channel, extra \ 

861 = self.codes.split(separator) 

862 

863 return dict( 

864 agency=agency, 

865 network=network, 

866 station=station, 

867 location=location, 

868 channel=channel, 

869 extra=extra, 

870 tmin=tmin_or_none(self.tmin), 

871 tmax=tmax_or_none(self.tmax), 

872 deltat=self.deltat) 

873 

874 @property 

875 def response_kwargs(self): 

876 agency, network, station, location, channel, extra \ 

877 = self.codes.split(separator) 

878 

879 return dict( 

880 agency=agency, 

881 network=network, 

882 station=station, 

883 location=location, 

884 channel=channel, 

885 extra=extra, 

886 tmin=tmin_or_none(self.tmin), 

887 tmax=tmax_or_none(self.tmax), 

888 deltat=self.deltat) 

889 

890 @property 

891 def event_kwargs(self): 

892 return dict( 

893 name=self.codes, 

894 time=self.tmin, 

895 duration=(self.tmax - self.tmin) or None) 

896 

897 @property 

898 def trace_kwargs(self): 

899 agency, network, station, location, channel, extra = \ 

900 self.codes.split(separator) 

901 

902 return dict( 

903 network=network, 

904 station=station, 

905 location=location, 

906 channel=channel, 

907 extra=extra, 

908 tmin=self.tmin, 

909 tmax=self.tmax-self.deltat, 

910 deltat=self.deltat) 

911 

912 @property 

913 def dummy_trace(self): 

914 return DummyTrace(self) 

915 

916 @property 

917 def codes_tuple(self): 

918 return tuple(self.codes.split(separator)) 

919 

920 @property 

921 def summary(self): 

922 if self.tmin == self.tmax: 

923 ts = util.time_to_str(self.tmin) 

924 else: 

925 ts = '%s - %s' % ( 

926 util.time_to_str(self.tmin), 

927 util.time_to_str(self.tmax)) 

928 

929 return ' '.join(( 

930 ('%s,' % to_kind(self.kind_id)).ljust(9), 

931 ('%s,' % '.'.join(self.codes.split(separator))).ljust(18), 

932 ts)) 

933 

934 

935def make_waveform_nut( 

936 agency='', network='', station='', location='', channel='', extra='', 

937 **kwargs): 

938 

939 codes = separator.join( 

940 (agency, network, station, location, channel, extra)) 

941 

942 return Nut( 

943 kind_id=WAVEFORM, 

944 codes=codes, 

945 **kwargs) 

946 

947 

948def make_waveform_promise_nut( 

949 agency='', network='', station='', location='', channel='', extra='', 

950 **kwargs): 

951 

952 codes = separator.join( 

953 (agency, network, station, location, channel, extra)) 

954 

955 return Nut( 

956 kind_id=WAVEFORM_PROMISE, 

957 codes=codes, 

958 **kwargs) 

959 

960 

961def make_station_nut( 

962 agency='', network='', station='', location='', **kwargs): 

963 

964 codes = separator.join((agency, network, station, location)) 

965 

966 return Nut( 

967 kind_id=STATION, 

968 codes=codes, 

969 **kwargs) 

970 

971 

972def make_channel_nut( 

973 agency='', network='', station='', location='', channel='', extra='', 

974 **kwargs): 

975 

976 codes = separator.join( 

977 (agency, network, station, location, channel, extra)) 

978 

979 return Nut( 

980 kind_id=CHANNEL, 

981 codes=codes, 

982 **kwargs) 

983 

984 

985def make_response_nut( 

986 agency='', network='', station='', location='', channel='', extra='', 

987 **kwargs): 

988 

989 codes = separator.join( 

990 (agency, network, station, location, channel, extra)) 

991 

992 return Nut( 

993 kind_id=RESPONSE, 

994 codes=codes, 

995 **kwargs) 

996 

997 

998def make_event_nut(name='', **kwargs): 

999 

1000 codes = name 

1001 

1002 return Nut( 

1003 kind_id=EVENT, codes=codes, 

1004 **kwargs) 

1005 

1006 

1007def group_channels(nuts): 

1008 by_ansl = {} 

1009 for nut in nuts: 

1010 if nut.kind_id != CHANNEL: 

1011 continue 

1012 

1013 ansl = nut.codes[:4] 

1014 

1015 if ansl not in by_ansl: 

1016 by_ansl[ansl] = {} 

1017 

1018 group = by_ansl[ansl] 

1019 

1020 k = nut.codes[4][:-1], nut.deltat, nut.tmin, nut.tmax 

1021 

1022 if k not in group: 

1023 group[k] = set() 

1024 

1025 group.add(nut.codes[4]) 

1026 

1027 return by_ansl 

1028 

1029 

1030class DummyTrace(object): 

1031 

1032 def __init__(self, nut): 

1033 self.nut = nut 

1034 self._codes = None 

1035 self.meta = {} 

1036 

1037 @property 

1038 def tmin(self): 

1039 return self.nut.tmin 

1040 

1041 @property 

1042 def tmax(self): 

1043 return self.nut.tmax 

1044 

1045 @property 

1046 def deltat(self): 

1047 return self.nut.deltat 

1048 

1049 @property 

1050 def codes(self): 

1051 if self._codes is None: 

1052 self._codes = self.nut.codes_tuple 

1053 

1054 return self._codes 

1055 

1056 @property 

1057 def nslc_id(self): 

1058 return self.codes[1:5] 

1059 

1060 @property 

1061 def agency(self): 

1062 return self.codes[0] 

1063 

1064 @property 

1065 def network(self): 

1066 return self.codes[1] 

1067 

1068 @property 

1069 def station(self): 

1070 return self.codes[2] 

1071 

1072 @property 

1073 def location(self): 

1074 return self.codes[3] 

1075 

1076 @property 

1077 def channel(self): 

1078 return self.codes[4] 

1079 

1080 @property 

1081 def extra(self): 

1082 return self.codes[5] 

1083 

1084 def overlaps(self, tmin, tmax): 

1085 return not (tmax < self.nut.tmin or self.nut.tmax < tmin) 

1086 

1087 

1088class Coverage(Object): 

1089 kind_id = Int.T() 

1090 pattern = String.T() 

1091 codes = String.T() 

1092 deltat = Float.T(optional=True) 

1093 tmin = Timestamp.T(optional=True) 

1094 tmax = Timestamp.T(optional=True) 

1095 changes = List.T(Tuple.T(2, Any.T())) 

1096 

1097 @classmethod 

1098 def from_values(cls, args): 

1099 pattern, codes, deltat, tmin, tmax, changes, kind_id = args 

1100 return cls( 

1101 kind_id=kind_id, 

1102 pattern=pattern, 

1103 codes=codes, 

1104 deltat=deltat, 

1105 tmin=tmin, 

1106 tmax=tmax, 

1107 changes=changes) 

1108 

1109 @property 

1110 def summary(self): 

1111 ts = '%s - %s,' % ( 

1112 util.time_to_str(self.tmin), 

1113 util.time_to_str(self.tmax)) 

1114 

1115 srate = self.sample_rate 

1116 

1117 return ' '.join(( 

1118 ('%s,' % to_kind(self.kind_id)).ljust(9), 

1119 ('%s,' % '.'.join(self.codes.split(separator))).ljust(18), 

1120 ts, 

1121 '%10.3g,' % srate if srate else '', 

1122 '%4i' % len(self.changes))) 

1123 

1124 @property 

1125 def sample_rate(self): 

1126 if self.deltat is None: 

1127 return None 

1128 elif self.deltat == 0.0: 

1129 return 0.0 

1130 else: 

1131 return 1.0 / self.deltat 

1132 

1133 @property 

1134 def labels(self): 

1135 srate = self.sample_rate 

1136 return ( 

1137 ('%s' % '.'.join(self.codes.split(separator))), 

1138 '%.3g' % srate if srate else '') 

1139 

1140 

1141__all__ = [ 

1142 'separator', 

1143 'to_kind', 

1144 'to_kinds', 

1145 'to_kind_id', 

1146 'to_kind_ids', 

1147 'Content', 

1148 'WaveformPromise', 

1149 'Station', 

1150 'Channel', 

1151 'Nut', 

1152 'Coverage', 

1153]