1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5from __future__ import absolute_import, print_function, division 

6 

7import string 

8 

9import numpy as num 

10 

11from pyrocko.guts import Bool, Float, Object, String 

12 

13from pyrocko import cake, geometry, gf 

14from pyrocko.gui.qt_compat import qc, qw 

15from pyrocko.gui.talkie import TalkieRoot 

16 

17from pyrocko.gui.vtk_util import \ 

18 ArrowPipe, ColorbarPipe, PolygonPipe, ScatterPipe, OutlinesPipe 

19 

20from .. import state as vstate 

21from .. import common 

22from . import base 

23 

24guts_prefix = 'sparrow' 

25 

26 

27d2r = num.pi / 180. 

28 

29 

30map_anchor = { 

31 'center': (0.0, 0.0), 

32 'center_left': (-1.0, 0.0), 

33 'center_right': (1.0, 0.0), 

34 'top': (0.0, -1.0), 

35 'top_left': (-1.0, -1.0), 

36 'top_right': (1.0, -1.0), 

37 'bottom': (0.0, 1.0), 

38 'bottom_left': (-1.0, 1.0), 

39 'bottom_right': (1.0, 1.0)} 

40 

41 

42class ProxySource(TalkieRoot): 

43 pass 

44 

45 

46for source_cls in [gf.RectangularSource]: 

47 

48 cls_name = 'Proxy' + source_cls.__name__ 

49 

50 class proxy_source_cls(ProxySource): 

51 class_name = cls_name 

52 

53 def __init__(self, **kwargs): 

54 ProxySource.__init__(self) 

55 for key, value in self._ranges.items(): 

56 setattr(self, key, value['ini']) 

57 

58 if kwargs is not None: 

59 for it in kwargs.items(): 

60 setattr(self, it[0], it[1]) 

61 

62 proxy_source_cls.__name__ = cls_name 

63 vars()[cls_name] = proxy_source_cls 

64 

65 for prop in source_cls.T.properties: 

66 proxy_source_cls.T.add_property(prop.name, prop) 

67 

68ProxyRectangularSource = vars()['ProxyRectangularSource'] # silence flake8 

69 

70ProxyRectangularSource._name = 'RectangularSource' 

71 

72ProxyRectangularSource._ranges = { 

73 'lat': {'min': -90., 'max': 90., 'step': 1, 'ini': 0.}, 

74 'lon': {'min': -180., 'max': 180., 'step': 1, 'ini': 0.}, 

75 'depth': {'min': 0., 'max': 600000., 'step': 1000, 'ini': 10000.}, 

76 'width': {'min': 0.1, 'max': 500000., 'step': 1000, 'ini': 10000.}, 

77 'length': {'min': 0.1, 'max': 1000000., 'step': 1000, 'ini': 50000.}, 

78 'strike': {'min': -180., 'max': 180., 'step': 1, 'ini': 0.}, 

79 'dip': {'min': 0., 'max': 90., 'step': 1, 'ini': 45.}, 

80 'rake': {'min': -180., 'max': 180., 'step': 1, 'ini': 0.}, 

81 'nucleation_x': 

82 {'min': -100., 'max': 100., 'step': 1, 'ini': 0., 'fac': .01}, 

83 'nucleation_y': 

84 {'min': -100., 'max': 100., 'step': 1, 'ini': 0., 'fac': .01}, 

85 'slip': {'min': 0., 'max': 1000., 'step': 1, 'ini': 1., 'fac': .01}} 

86 

87 

88class ProxyConfig(Object): 

89 deltas = num.array([1000., 1000.]) 

90 deltat = Float.T(default=0.5) 

91 rho = Float.T(default=2800) 

92 vs = Float.T(default=3600) 

93 

94 def get_shear_moduli(self, *args, **kwargs): 

95 points = kwargs.get('points') 

96 return num.ones(len(points)) * num.power(self.vs, 2) * self.rho 

97 

98 

99class ProxyStore(Object): 

100 def __init__(self, **kwargs): 

101 config = ProxyConfig() 

102 if kwargs: 

103 config.deltas = kwargs.get('deltas', config.deltas) 

104 config.deltat = kwargs.get('deltat', config.deltat) 

105 config.rho = kwargs.get('rho', config.rho) 

106 config.vs = kwargs.get('vs', config.vs) 

107 

108 self.config = config 

109 self.mode = String.T(default='r') 

110 self._f_data = None 

111 self._f_index = None 

112 

113 

114parameter_label = { 

115 'time (s)': 'times'} 

116 

117 

118class SourceState(base.ElementState): 

119 visible = Bool.T(default=True) 

120 source_selection = ProxySource.T(default=ProxyRectangularSource()) # noqa 

121 deltat = Float.T(default=0.5) 

122 display_parameter = String.T(default='time (s)') 

123 cpt = base.CPTState.T(default=base.CPTState.D()) 

124 

125 @classmethod 

126 def get_name(self): 

127 return 'Source' 

128 

129 def create(self): 

130 element = SourceElement() 

131 return element 

132 

133 

134class SourceElement(base.Element): 

135 

136 def __init__(self): 

137 base.Element.__init__(self) 

138 self._parent = None 

139 self._pipe = [] 

140 self._controls = None 

141 self._points = num.array([]) 

142 

143 self.cpt_handler = base.CPTHandler() 

144 

145 def _state_bind_source(self, *args, **kwargs): 

146 vstate.state_bind(self, self._state.source_selection, *args, **kwargs) 

147 

148 def _state_bind_store(self, *args, **kwargs): 

149 vstate.state_bind(self, self._state, *args, **kwargs) 

150 

151 def bind_state(self, state): 

152 base.Element.bind_state(self, state) 

153 for var in ['visible', 'source_selection', 'deltat', 

154 'display_parameter']: 

155 self.register_state_listener3(self.update, state, var) 

156 

157 self.cpt_handler.bind_state(state.cpt, self.update) 

158 

159 def unbind_state(self): 

160 self._listeners = [] 

161 self._state = None 

162 self.cpt_handler.unbind_state() 

163 

164 def get_name(self): 

165 return 'Source' 

166 

167 def set_parent(self, parent): 

168 self._parent = parent 

169 self._parent.add_panel( 

170 self.get_name(), 

171 self._get_controls(), 

172 visible=True, 

173 title_controls=[ 

174 self.get_title_control_remove(), 

175 self.get_title_control_visible()]) 

176 

177 self.update() 

178 

179 def unset_parent(self): 

180 self.unbind_state() 

181 if self._parent: 

182 if self._pipe: 

183 for pipe in self._pipe: 

184 if isinstance(pipe.actor, list): 

185 for act in pipe.actor: 

186 self._parent.remove_actor(act) 

187 else: 

188 self._parent.remove_actor(pipe.actor) 

189 self._pipe = [] 

190 

191 if self._controls: 

192 self._parent.remove_panel(self._controls) 

193 self._controls = None 

194 

195 self._parent.update_view() 

196 self._parent = None 

197 

198 def open_file_load_dialog(self): 

199 caption = 'Select one file to open' 

200 fns, _ = qw.QFileDialog.getOpenFileNames( 

201 self._parent, caption, options=common.qfiledialog_options) 

202 

203 if fns: 

204 try: 

205 self.load_file(str(fns[0])) 

206 except gf.FileNotFoundError as e: 

207 raise e 

208 

209 else: 

210 return 

211 

212 def load_source_file(self, path): 

213 loaded_source = gf.load(filename=path) 

214 source = ProxyRectangularSource( 

215 **{prop: getattr(loaded_source, prop) 

216 for prop in loaded_source.T.propnames 

217 if getattr(loaded_source, prop)}) 

218 

219 self._parent.remove_panel(self._controls) 

220 self._controls = None 

221 self._state.source_selection = source 

222 self._parent.add_panel( 

223 self.get_name(), 

224 self._get_controls(), 

225 visible=True, 

226 title_controls=[ 

227 self.get_title_control_remove(), 

228 self.get_title_control_visible()]) 

229 

230 self.update() 

231 

232 def open_file_save_dialog(self, fn=None): 

233 caption = 'Choose a file name to write source' 

234 if not fn: 

235 fn, _ = qw.QFileDialog.getSaveFileName( 

236 self._parent, caption, options=common.qfiledialog_options) 

237 if fn: 

238 self.save_file(str(fn)) 

239 

240 def save_file(self, path): 

241 source = self._state.source_selection 

242 source2dump = gf.RectangularSource( 

243 **{prop: getattr(source, prop) for prop in source.T.propnames}) 

244 

245 if path.split('.')[-1].lower() in ['xml']: 

246 source2dump.dump_xml(filename=path) 

247 else: 

248 source2dump.dump(filename=path) 

249 

250 def update_loc(self, *args): 

251 pstate = self._parent.state 

252 state = self._state 

253 

254 source = state.source_selection 

255 source.lat = pstate.lat 

256 source.lon = pstate.lon 

257 

258 self._state.source_selection.source = source 

259 

260 self.update() 

261 

262 def update_source(self, store): 

263 state = self._state 

264 

265 source = state.source_selection 

266 source_list = gf.source_classes 

267 

268 for i, a in enumerate(source_list): 

269 if a.__name__ is source._name: 

270 fault = a( 

271 **{prop: source.__dict__[prop] 

272 for prop in source.T.propnames}) 

273 

274 source_geom = fault.geometry(store) 

275 

276 self._update_outlines(source_geom) 

277 self._update_scatter(source, fault) 

278 self._update_raster(source_geom, state.display_parameter) 

279 self._update_rake_arrow(fault) 

280 

281 def _update_outlines(self, source_geom): 

282 

283 if source_geom.outlines: 

284 self._pipe.append(OutlinesPipe( 

285 source_geom, color=(1., 1., 1.), cs='latlondepth')) 

286 self._parent.add_actor( 

287 self._pipe[-1].actor) 

288 

289 self._pipe.append(OutlinesPipe( 

290 source_geom, color=(0.6, 0.6, 0.6), cs='latlon')) 

291 self._parent.add_actor( 

292 self._pipe[-1].actor) 

293 

294 def _update_scatter(self, source, fault): 

295 for point, color in zip( 

296 ((source.nucleation_x, 

297 source.nucleation_y), 

298 map_anchor[source.anchor]), 

299 (num.array([[1., 0., 0.]]), 

300 num.array([[0., 0., 1.]]))): 

301 

302 points = geometry.latlondepth2xyz( 

303 fault.xy_to_coord( 

304 x=[point[0]], y=[point[1]], 

305 cs='latlondepth'), 

306 planetradius=cake.earthradius) 

307 

308 vertices = geometry.arr_vertices(points) 

309 p = ScatterPipe(vertices) 

310 p.set_symbol('sphere') 

311 p.set_colors(color) 

312 self._pipe.append(p) 

313 self._parent.add_actor(p.actor) 

314 

315 def _update_raster(self, source_geom, param): 

316 vertices = geometry.arr_vertices( 

317 source_geom.get_vertices(col='xyz')) 

318 

319 faces = source_geom.get_faces() 

320 

321 if parameter_label[param] == 'times' and \ 

322 source_geom.has_property('t_arrival'): 

323 

324 self.cpt_handler._values = source_geom.get_property('t_arrival') 

325 cbar_title = 'T arr [s]' 

326 

327 self.cpt_handler.update_cpt() 

328 

329 poly_pipe = PolygonPipe( 

330 vertices, faces, 

331 values=self.cpt_handler._values, lut=self.cpt_handler._lookuptable) 

332 

333 self._pipe.append(poly_pipe) 

334 self._parent.add_actor(self._pipe[-1].actor) 

335 

336 if cbar_title is not None: 

337 cbar_pipe = ColorbarPipe( 

338 parent_pipe=poly_pipe, cbar_title=cbar_title, 

339 lut=self.cpt_handler._lookuptable) 

340 

341 self._pipe.append(cbar_pipe) 

342 self._parent.add_actor(self._pipe[-1].actor) 

343 

344 def _update_rake_arrow(self, fault): 

345 source = self._state.source_selection 

346 rake = source.rake * d2r 

347 

348 nucl_x = source.nucleation_x 

349 nucl_y = source.nucleation_y 

350 

351 wd_ln = source.width / source.length 

352 

353 endpoint = [None] * 2 

354 endpoint[0] = nucl_x + num.cos(rake) * wd_ln 

355 endpoint[1] = nucl_y + num.sin(-rake) 

356 

357 points = geometry.latlondepth2xyz( 

358 fault.xy_to_coord( 

359 x=[nucl_x, endpoint[0]], 

360 y=[nucl_y, endpoint[1]], 

361 cs='latlondepth'), 

362 planetradius=cake.earthradius) 

363 vertices = geometry.arr_vertices(points) 

364 

365 self._pipe.append(ArrowPipe(vertices[0], vertices[1])) 

366 self._parent.add_actor(self._pipe[-1].actor) 

367 

368 def update(self, *args): 

369 state = self._state 

370 

371 store = ProxyStore( 

372 deltat=state.deltat) 

373 store.config.deltas = num.array( 

374 [(store.config.deltat * store.config.vs) + 1] * 2) 

375 

376 if self._pipe: 

377 for pipe in self._pipe: 

378 self._parent.remove_actor(pipe.actor) 

379 

380 self._pipe = [] 

381 

382 if state.visible: 

383 self.update_source(store) 

384 

385 self._parent.update_view() 

386 

387 def _get_controls(self): 

388 if not self._controls: 

389 from ..state import \ 

390 state_bind_slider, state_bind_combobox 

391 from pyrocko import gf 

392 

393 source = self._state.source_selection 

394 

395 frame = qw.QFrame() 

396 layout = qw.QGridLayout() 

397 frame.setLayout(layout) 

398 

399 def state_to_lineedit(state, attribute, widget): 

400 sel = getattr(state, attribute) 

401 

402 widget.setText('%g' % sel) 

403 # if sel: 

404 # widget.selectAll() 

405 

406 def lineedit_to_state(widget, state, attribute): 

407 s = float(widget.text()) 

408 try: 

409 setattr(state, attribute, s) 

410 except Exception: 

411 raise ValueError( 

412 'Value of %s needs to be a float or integer' 

413 % string.capwords(attribute)) 

414 

415 for il, label in enumerate(source.T.propnames): 

416 if label in source._ranges.keys(): 

417 

418 layout.addWidget(qw.QLabel( 

419 string.capwords(label) + ':'), il, 0) 

420 

421 slider = qw.QSlider(qc.Qt.Horizontal) 

422 slider.setSizePolicy( 

423 qw.QSizePolicy( 

424 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) 

425 slider.setMinimum( 

426 int(round(source._ranges[label]['min']))) 

427 slider.setMaximum( 

428 int(round(source._ranges[label]['max']))) 

429 slider.setSingleStep( 

430 int(round(source._ranges[label]['step']))) 

431 slider.setPageStep( 

432 int(round(source._ranges[label]['step']))) 

433 

434 layout.addWidget(slider, il, 1) 

435 try: 

436 state_bind_slider( 

437 self, self._state.source_selection, label, slider, 

438 factor=source._ranges[label]['fac']) 

439 except Exception: 

440 state_bind_slider( 

441 self, self._state.source_selection, label, slider) 

442 

443 le = qw.QLineEdit() 

444 layout.addWidget(le, il, 2) 

445 

446 self._state_bind_source( 

447 [label], lineedit_to_state, le, 

448 [le.editingFinished, le.returnPressed], 

449 state_to_lineedit, attribute=label) 

450 

451 for label, name in zip( 

452 ['GF dt:'], ['deltat']): 

453 il += 1 

454 layout.addWidget(qw.QLabel(label), il, 0) 

455 slider = qw.QSlider(qc.Qt.Horizontal) 

456 slider.setSizePolicy( 

457 qw.QSizePolicy( 

458 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) 

459 slider.setMinimum(1) 

460 slider.setMaximum(1000) 

461 slider.setSingleStep(1) 

462 slider.setPageStep(1) 

463 layout.addWidget(slider, il, 1) 

464 state_bind_slider( 

465 self, self._state, name, slider, factor=0.01) 

466 

467 le = qw.QLineEdit() 

468 layout.addWidget(le, il, 2) 

469 

470 self._state_bind_store( 

471 [name], lineedit_to_state, le, 

472 [le.editingFinished, le.returnPressed], 

473 state_to_lineedit, attribute=name) 

474 

475 il += 1 

476 layout.addWidget(qw.QLabel('Anchor:'), il, 0) 

477 

478 cb = qw.QComboBox() 

479 for i, s in enumerate(gf.RectangularSource.anchor.choices): 

480 cb.insertItem(i, s) 

481 layout.addWidget(cb, il, 1, 1, 2) 

482 state_bind_combobox( 

483 self, self._state.source_selection, 'anchor', cb) 

484 

485 il += 1 

486 layout.addWidget(qw.QLabel('Display Param.:'), il, 0) 

487 

488 cb = qw.QComboBox() 

489 for i, s in enumerate(parameter_label.keys()): 

490 cb.insertItem(i, s) 

491 layout.addWidget(cb, il, 1) 

492 state_bind_combobox( 

493 self, self._state, 'display_parameter', cb) 

494 

495 self.cpt_handler.cpt_controls( 

496 self._parent, self._state.cpt, layout) 

497 

498 il = layout.rowCount() + 1 

499 pb = qw.QPushButton('Move Source Here') 

500 layout.addWidget(pb, il, 0) 

501 pb.clicked.connect(self.update_loc) 

502 

503 pb = qw.QPushButton('Load') 

504 layout.addWidget(pb, il, 1) 

505 pb.clicked.connect(self.open_file_load_dialog) 

506 

507 pb = qw.QPushButton('Save') 

508 layout.addWidget(pb, il, 2) 

509 pb.clicked.connect(self.open_file_save_dialog) 

510 

511 il += 1 

512 layout.addWidget(qw.QFrame(), il, 0, 1, 3) 

513 

514 self._controls = frame 

515 

516 self.cpt_handler._update_cpt_combobox() 

517 self.cpt_handler._update_cptscale_lineedit() 

518 

519 return self._controls 

520 

521 

522__all__ = [ 

523 'SourceElement', 

524 'SourceState', 

525]