1import logging 

2import math 

3import re 

4import numpy as num 

5import os.path as op 

6from string import Template 

7from pyrocko.guts import Object, String, Float, Unicode, StringPattern, Bool 

8from pyrocko import util, gf 

9 

10guts_prefix = 'grond' 

11 

12 

13try: 

14 newstr = unicode 

15except NameError: 

16 newstr = str 

17 

18 

19logger = logging.getLogger('grond.meta') 

20km = 1e3 

21 

22classes_with_have_get_plot_classes = [] 

23 

24 

25def has_get_plot_classes(cls): 

26 classes_with_have_get_plot_classes.append(cls) 

27 return cls 

28 

29 

30class StringID(StringPattern): 

31 pattern = r'^[A-Za-z][A-Za-z0-9._-]{0,64}$' 

32 

33 

34StringID.regex = re.compile(StringID.pattern) 

35 

36 

37def xjoin(basepath, path): 

38 if path is None and basepath is not None: 

39 return basepath 

40 elif op.isabs(path) or basepath is None: 

41 return path 

42 else: 

43 return op.join(basepath, path) 

44 

45 

46def xrelpath(path, start): 

47 if op.isabs(path): 

48 return path 

49 else: 

50 return op.relpath(path, start) 

51 

52 

53def ordersort(x): 

54 isort = num.argsort(x) 

55 iorder = num.empty(isort.size, dtype=int) 

56 iorder[isort] = num.arange(isort.size) 

57 return iorder 

58 

59 

60def nextpow2(i): 

61 return 2**int(math.ceil(math.log(i) / math.log(2.))) 

62 

63 

64def gather(seq, key, sort=None, filter=None): 

65 d = {} 

66 for x in seq: 

67 if filter is not None and not filter(x): 

68 continue 

69 

70 k = key(x) 

71 if k not in d: 

72 d[k] = [] 

73 

74 d[k].append(x) 

75 

76 if sort is not None: 

77 for v in d.values(): 

78 v.sort(key=sort) 

79 

80 return d 

81 

82 

83def str_dist(dist): 

84 if dist < 10.0: 

85 return '%g m' % dist 

86 elif 10. <= dist < 1. * km: 

87 return '%.0f m' % dist 

88 elif 1. * km <= dist < 10. * km: 

89 return '%.1f km' % (dist / km) 

90 else: 

91 return '%.0f km' % (dist / km) 

92 

93 

94def str_duration(t): 

95 s = '' 

96 if t < 0.: 

97 s = '-' 

98 

99 t = abs(t) 

100 

101 if t < 10.0: 

102 return s + '%.2g s' % t 

103 elif 10.0 <= t < 3600.: 

104 return s + util.time_to_str(t, format='%M:%S min') 

105 elif 3600. <= t < 24 * 3600.: 

106 return s + util.time_to_str(t, format='%H:%M h') 

107 else: 

108 return s + '%.1f d' % (t / (24. * 3600.)) 

109 

110 

111try: 

112 nanmedian = num.nanmedian 

113except AttributeError: 

114 def nanmedian(a, axis=None): 

115 if axis is None: 

116 return num.median(a[num.isfinite(a)]) 

117 else: 

118 shape_out = list(a.shape) 

119 shape_out.pop(axis) 

120 out = num.empty(shape_out, dtype=a.dtype) 

121 out[...] = num.nan 

122 for iout in num.ndindex(tuple(shape_out)): 

123 iin = list(iout) 

124 iin[axis:axis] = [slice(0, a.shape[axis])] 

125 b = a[tuple(iin)] 

126 out[iout] = num.median(b[num.isfinite(b)]) 

127 

128 return out 

129 

130 

131class Forbidden(Exception): 

132 pass 

133 

134 

135class GrondError(Exception): 

136 pass 

137 

138 

139class PhaseMissContext(Object): 

140 store_id = gf.StringID.T() 

141 timing = gf.Timing.T() 

142 source = gf.Source.T() 

143 target = gf.Target.T() 

144 

145 

146class PhaseMissError(GrondError): 

147 def __init__(self, store_id, timing, source, target): 

148 GrondError.__init__(self) 

149 self._context = PhaseMissContext( 

150 store_id=store_id, 

151 timing=timing, 

152 source=source, 

153 target=target) 

154 

155 def __str__(self): 

156 return 'Phase not available for given geometry. Context:\n%s' \ 

157 % self._context 

158 

159 

160def store_t(store, tdef, source, target): 

161 tt = store.t(tdef, source, target) 

162 if tt is None: 

163 raise PhaseMissError(store.config.id, tdef, source, target) 

164 

165 return tt 

166 

167 

168def expand_template(template, d): 

169 try: 

170 return Template(template).substitute(d) 

171 except KeyError as e: 

172 raise GrondError( 

173 'invalid placeholder "%s" in template: "%s"' % (str(e), template)) 

174 except ValueError: 

175 raise GrondError( 

176 'malformed placeholder in template: "%s"' % template) 

177 

178 

179class ADict(dict): 

180 def __getattr__(self, k): 

181 return self[k] 

182 

183 def __setattr__(self, k, v): 

184 self[k] = v 

185 

186 

187class Parameter(Object): 

188 name__ = String.T() 

189 unit = Unicode.T(optional=True) 

190 scale_factor = Float.T(default=1., optional=True) 

191 scale_unit = Unicode.T(optional=True) 

192 label = Unicode.T(optional=True) 

193 optional = Bool.T(default=True, optional=True) 

194 

195 def __init__(self, *args, **kwargs): 

196 if len(args) >= 1: 

197 kwargs['name'] = args[0] 

198 if len(args) >= 2: 

199 kwargs['unit'] = newstr(args[1]) 

200 

201 self.groups = [None] 

202 self._name = None 

203 

204 Object.__init__(self, **kwargs) 

205 

206 def get_label(self, with_unit=True): 

207 lbl = [self.label or self.name] 

208 if with_unit: 

209 unit = self.get_unit_label() 

210 if unit: 

211 lbl.append('[%s]' % unit) 

212 

213 return ' '.join(lbl) 

214 

215 def set_groups(self, groups): 

216 if not isinstance(groups, list): 

217 raise AttributeError('Groups must be a list of strings.') 

218 self.groups = groups 

219 

220 def _get_name(self): 

221 if None not in self.groups: 

222 return '%s.%s' % ('.'.join(self.groups), self._name) 

223 return self._name 

224 

225 def _set_name(self, value): 

226 self._name = value 

227 

228 name = property(_get_name, _set_name) 

229 

230 @property 

231 def name_nogroups(self): 

232 return self._name 

233 

234 def get_value_label(self, value, format='%(value)g%(unit)s'): 

235 value = self.scaled(value) 

236 unit = self.get_unit_suffix() 

237 return format % dict(value=value, unit=unit) 

238 

239 def get_unit_label(self): 

240 if self.scale_unit is not None: 

241 return self.scale_unit 

242 elif self.unit: 

243 return self.unit 

244 else: 

245 return None 

246 

247 def get_unit_suffix(self): 

248 unit = self.get_unit_label() 

249 if not unit: 

250 return '' 

251 else: 

252 return ' %s' % unit 

253 

254 def scaled(self, x): 

255 if isinstance(x, tuple): 

256 return tuple(v/self.scale_factor for v in x) 

257 if isinstance(x, list): 

258 return list(v/self.scale_factor for v in x) 

259 else: 

260 return x/self.scale_factor 

261 

262 def inv_scaled(self, x): 

263 if isinstance(x, tuple): 

264 return tuple(v*self.scale_factor for v in x) 

265 if isinstance(x, list): 

266 return list(v*self.scale_factor for v in x) 

267 else: 

268 return x*self.scale_factor 

269 

270 

271class Path(String): 

272 pass 

273 

274 

275class HasPaths(Object): 

276 path_prefix = Path.T(optional=True) 

277 

278 def __init__(self, *args, **kwargs): 

279 Object.__init__(self, *args, **kwargs) 

280 self._basepath = None 

281 self._parent_path_prefix = None 

282 

283 def set_basepath(self, basepath, parent_path_prefix=None): 

284 self._basepath = basepath 

285 self._parent_path_prefix = parent_path_prefix 

286 for (prop, val) in self.T.ipropvals(self): 

287 if isinstance(val, HasPaths): 

288 val.set_basepath( 

289 basepath, self.path_prefix or self._parent_path_prefix) 

290 

291 def get_basepath(self): 

292 assert self._basepath is not None 

293 return self._basepath 

294 

295 def change_basepath(self, new_basepath, parent_path_prefix=None): 

296 assert self._basepath is not None 

297 

298 self._parent_path_prefix = parent_path_prefix 

299 if self.path_prefix or not self._parent_path_prefix: 

300 

301 self.path_prefix = op.normpath(xjoin(xrelpath( 

302 self._basepath, new_basepath), self.path_prefix)) 

303 

304 for val in self.T.ivals(self): 

305 if isinstance(val, HasPaths): 

306 val.change_basepath( 

307 new_basepath, self.path_prefix or self._parent_path_prefix) 

308 

309 self._basepath = new_basepath 

310 

311 def expand_path(self, path, extra=None): 

312 assert self._basepath is not None 

313 

314 if extra is None: 

315 def extra(path): 

316 return path 

317 

318 path_prefix = self.path_prefix or self._parent_path_prefix 

319 

320 if path is None: 

321 return None 

322 elif isinstance(path, str): 

323 return extra( 

324 op.normpath(xjoin(self._basepath, xjoin(path_prefix, path)))) 

325 else: 

326 return [ 

327 extra( 

328 op.normpath(xjoin(self._basepath, xjoin(path_prefix, p)))) 

329 for p in path] 

330 

331 def rel_path(self, path): 

332 return xrelpath(path, self.get_basepath()) 

333 

334 

335def nslc_to_pattern(s): 

336 toks = s.split('.') 

337 if len(toks) == 1: 

338 return '*.%s.*.*' % s 

339 elif len(toks) == 2: 

340 return '%s.*.*' % s 

341 elif len(toks) == 3: 

342 return '%s.*' % s 

343 elif len(toks) == 4: 

344 return s 

345 else: 

346 raise GrondError('Invalid net.sta.loc.cha pattern: %s' % s) 

347 

348 

349def nslcs_to_patterns(seq): 

350 return [nslc_to_pattern(s) for s in seq] 

351 

352 

353class SelectionError(GrondError): 

354 pass 

355 

356 

357# --select="magnitude_min:5 tag_contains:a,b " 

358 

359g_conditions = {} 

360 

361selected_operators_1_1 = { 

362 'min': lambda data, key, value: data[key] >= value, 

363 'max': lambda data, key, value: data[key] <= value, 

364 'is': lambda data, key, value: data[key] == value} 

365 

366selected_operators_1_n = { 

367 'in': lambda data, key, values: 

368 data[key] in values} 

369 

370selected_operators_n_1 = {} 

371 

372selected_operators_n_n = { 

373 'contains': lambda data, key, values: 

374 any(d in values for d in data[key]), 

375 'notcontains': lambda data, key, values: 

376 not any(d in values for d in data[key])} 

377 

378 

379selected_operators = set() 

380 

381for s in (selected_operators_1_1, selected_operators_1_n, 

382 selected_operators_n_1, selected_operators_n_n): 

383 for k in s: 

384 selected_operators.add(k) 

385 

386 

387def _parse_selected_expression(expression): 

388 for condition in expression.split(): 

389 condition = condition.strip() 

390 if condition not in g_conditions: 

391 try: 

392 argument, value = condition.split(':', 1) 

393 except ValueError: 

394 raise SelectionError( 

395 'Invalid condition in selection expression: ' 

396 '"%s", must be "ARGUMENT:VALUE"' % condition) 

397 

398 argument = argument.strip() 

399 

400 try: 

401 key, operator = argument.rsplit('_', 1) 

402 except ValueError: 

403 raise SelectionError( 

404 'Invalid argument in selection expression: ' 

405 '"%s", must be "KEY_OPERATOR"' % argument) 

406 

407 if operator not in selected_operators: 

408 raise SelectionError( 

409 'Invalid operator in selection expression: ' 

410 '"%s", available: %s' % ( 

411 operator, ', '.join('"%s"' % s for s in sorted( 

412 list(selected_operators))))) 

413 

414 g_conditions[condition] = key, operator, value 

415 

416 yield g_conditions[condition] 

417 

418 

419def selected(expression, data, types): 

420 results = [] 

421 for (key, operator, value) in _parse_selected_expression(expression): 

422 if key not in data: 

423 raise SelectionError( 

424 'Invalid key in selection expression: ' 

425 '"%s", available:\n %s' % ( 

426 key, '\n '.join(sorted(data.keys())))) 

427 

428 typ = types[key] 

429 if not isinstance(typ, tuple): 

430 if operator in selected_operators_1_1: 

431 results.append( 

432 selected_operators_1_1[operator](data, key, typ(value))) 

433 elif operator in selected_operators_1_n: 

434 values = list(typ(v) for v in value.split(',')) 

435 results.append( 

436 selected_operators_1_n[operator](data, key, values)) 

437 else: 

438 raise SelectionError( 

439 'Cannot use operator "%s" with argument "%s".' % ( 

440 operator, 

441 key)) 

442 

443 else: 

444 if operator in selected_operators_n_1: 

445 results.append( 

446 selected_operators_n_1[operator](data, key, typ[1](value))) 

447 elif operator in selected_operators_n_n: 

448 values = typ[0](typ[1](v) for v in value.split(',')) 

449 results.append( 

450 selected_operators_n_n[operator](data, key, values)) 

451 else: 

452 raise SelectionError( 

453 'Cannot use operator "%s" with argument "%s".' % ( 

454 operator, 

455 key)) 

456 

457 return all(results) 

458 

459 

460__all__ = ''' 

461 Forbidden 

462 GrondError 

463 PhaseMissContext 

464 PhaseMissError 

465 store_t 

466 Path 

467 HasPaths 

468 Parameter 

469 StringID 

470'''.split()