1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5from __future__ import division, absolute_import 

6 

7from struct import unpack 

8import os 

9import re 

10import math 

11import logging 

12 

13from pyrocko import trace 

14from pyrocko.util import reuse, ensuredirs 

15from .io_common import FileLoadError, FileSaveError 

16 

17logger = logging.getLogger('pyrocko.io.mseed') 

18 

19MSEED_HEADER_BYTES = 64 

20VALID_RECORD_LENGTHS = tuple(2**exp for exp in range(8, 20)) 

21 

22 

23class CodeTooLong(FileSaveError): 

24 pass 

25 

26 

27def iload(filename, load_data=True, offset=0, segment_size=0, nsegments=0): 

28 from pyrocko import mseed_ext 

29 

30 have_zero_rate_traces = False 

31 try: 

32 isegment = 0 

33 while isegment < nsegments or nsegments == 0: 

34 tr_tuples = mseed_ext.get_traces( 

35 filename, load_data, offset, segment_size) 

36 

37 if not tr_tuples: 

38 break 

39 

40 for tr_tuple in tr_tuples: 

41 network, station, location, channel = tr_tuple[1:5] 

42 tmin = float(tr_tuple[5])/float(mseed_ext.HPTMODULUS) 

43 tmax = float(tr_tuple[6])/float(mseed_ext.HPTMODULUS) 

44 try: 

45 deltat = reuse(1.0/float(tr_tuple[7])) 

46 except ZeroDivisionError: 

47 have_zero_rate_traces = True 

48 continue 

49 

50 ydata = tr_tuple[8] 

51 

52 tr = trace.Trace( 

53 network.strip(), 

54 station.strip(), 

55 location.strip(), 

56 channel.strip(), 

57 tmin, 

58 tmax, 

59 deltat, 

60 ydata) 

61 

62 tr.meta = { 

63 'offset_start': offset, 

64 'offset_end': tr_tuple[9], 

65 'last': tr_tuple[10], 

66 'segment_size': segment_size 

67 } 

68 

69 yield tr 

70 

71 if tr_tuple[10]: 

72 break 

73 

74 offset = tr_tuple[9] 

75 isegment += 1 

76 

77 except (OSError, mseed_ext.MSeedError) as e: 

78 raise FileLoadError(str(e)+' (file: %s)' % filename) 

79 

80 if have_zero_rate_traces: 

81 logger.warning( 

82 'Ignoring traces with sampling rate of zero in file %s ' 

83 '(maybe LOG traces)' % filename) 

84 

85 

86def as_tuple(tr, dataquality='D'): 

87 from pyrocko import mseed_ext 

88 itmin = int(round(tr.tmin*mseed_ext.HPTMODULUS)) 

89 itmax = int(round(tr.tmax*mseed_ext.HPTMODULUS)) 

90 srate = 1.0/tr.deltat 

91 return (tr.network, tr.station, tr.location, tr.channel, 

92 itmin, itmax, srate, dataquality, tr.get_ydata()) 

93 

94 

95def save(traces, filename_template, additional={}, overwrite=True, 

96 dataquality='D', record_length=4096, append=False, steim=1): 

97 from pyrocko import mseed_ext 

98 

99 assert record_length in VALID_RECORD_LENGTHS 

100 assert dataquality in ('D', 'E', 'C', 'O', 'T', 'L'), 'invalid dataquality' 

101 overwrite = True if append else overwrite 

102 

103 fn_tr = {} 

104 for tr in traces: 

105 for code, maxlen, val in zip( 

106 ['network', 'station', 'location', 'channel'], 

107 [2, 5, 2, 3], 

108 tr.nslc_id): 

109 

110 if len(val) > maxlen: 

111 raise CodeTooLong( 

112 '%s code too long to be stored in MSeed file: %s' % 

113 (code, val)) 

114 

115 fn = tr.fill_template(filename_template, **additional) 

116 if not overwrite and os.path.exists(fn): 

117 raise FileSaveError('File exists: %s' % fn) 

118 

119 if fn not in fn_tr: 

120 fn_tr[fn] = [] 

121 

122 fn_tr[fn].append(tr) 

123 

124 for fn, traces_thisfile in fn_tr.items(): 

125 trtups = [] 

126 traces_thisfile.sort(key=lambda a: a.full_id) 

127 for tr in traces_thisfile: 

128 trtups.append(as_tuple(tr, dataquality)) 

129 

130 ensuredirs(fn) 

131 try: 

132 mseed_ext.store_traces(trtups, fn, record_length, append, steim) 

133 except mseed_ext.MSeedError as e: 

134 raise FileSaveError( 

135 str(e) + ' (while storing traces to file \'%s\')' % fn) 

136 

137 return list(fn_tr.keys()) 

138 

139 

140tcs = {} 

141 

142 

143def get_bytes(traces, dataquality='D', record_length=4096, steim=1): 

144 from pyrocko import mseed_ext 

145 

146 assert record_length in VALID_RECORD_LENGTHS 

147 assert dataquality in ('D', 'E', 'C', 'O', 'T', 'L'), 'invalid dataquality' 

148 

149 nbytes_approx = 0 

150 rl = record_length 

151 trtups = [] 

152 for tr in traces: 

153 for code, maxlen, val in zip( 

154 ['network', 'station', 'location', 'channel'], 

155 [2, 5, 2, 3], 

156 tr.nslc_id): 

157 

158 if len(val) > maxlen: 

159 raise CodeTooLong( 

160 '%s code too long to be stored in MSeed file: %s' % 

161 (code, val)) 

162 

163 nbytes_approx += math.ceil( 

164 tr.ydata.nbytes / (rl-MSEED_HEADER_BYTES)) * rl 

165 trtups.append(as_tuple(tr, dataquality)) 

166 

167 return mseed_ext.mseed_bytes(trtups, nbytes_approx, record_length, steim) 

168 

169 

170def detect(first512): 

171 

172 if len(first512) < 256: 

173 return False 

174 

175 rec = first512 

176 

177 try: 

178 sequence_number = int(rec[:6]) 

179 except Exception: 

180 return False 

181 if sequence_number < 0: 

182 return False 

183 

184 type_code = rec[6:7] 

185 if type_code in b'DRQM': 

186 bads = [] 

187 for sex in '<>': 

188 bad = False 

189 fmt = sex + '6s1s1s5s2s3s2s10sH2h4Bl2H' 

190 vals = unpack(fmt, rec[:48]) 

191 fmt_btime = sex + 'HHBBBBH' 

192 tvals = unpack(fmt_btime, vals[7]) 

193 if tvals[1] < 1 or tvals[1] > 367 or tvals[2] > 23 or \ 

194 tvals[3] > 59 or tvals[4] > 60 or tvals[6] > 9999: 

195 bad = True 

196 

197 bads.append(bad) 

198 

199 if all(bads): 

200 return False 

201 

202 else: 

203 if type_code not in b'VAST': 

204 return False 

205 

206 continuation_code = rec[7:8] 

207 if continuation_code != b' ': 

208 return False 

209 

210 blockette_type = rec[8:8+3].decode() 

211 if not re.match(r'^\d\d\d$', blockette_type): 

212 return False 

213 

214 try: 

215 blockette_length = int(rec[11:11+4]) 

216 except Exception: 

217 return False 

218 

219 if blockette_length < 7: 

220 return False 

221 

222 return True