1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5 

6import string 

7 

8import numpy as num 

9 

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

11 

12from pyrocko import cake, geometry, gf 

13from pyrocko.gui.qt_compat import qc, qw 

14from pyrocko.gui.talkie import TalkieRoot 

15 

16from pyrocko.gui.vtk_util import \ 

17 ArrowPipe, ColorbarPipe, PolygonPipe, ScatterPipe, OutlinesPipe 

18 

19from .. import state as vstate 

20from .. import common 

21from . import base 

22 

23guts_prefix = 'sparrow' 

24 

25 

26d2r = num.pi / 180. 

27 

28 

29map_anchor = { 

30 'center': (0.0, 0.0), 

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

32 'center_right': (1.0, 0.0), 

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

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

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

36 'bottom': (0.0, 1.0), 

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

38 'bottom_right': (1.0, 1.0)} 

39 

40 

41class ProxySource(TalkieRoot): 

42 pass 

43 

44 

45for source_cls in [gf.RectangularSource]: 

46 

47 cls_name = 'Proxy' + source_cls.__name__ 

48 

49 class proxy_source_cls(ProxySource): 

50 class_name = cls_name 

51 

52 def __init__(self, **kwargs): 

53 ProxySource.__init__(self) 

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

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

56 

57 if kwargs is not None: 

58 for it in kwargs.items(): 

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

60 

61 proxy_source_cls.__name__ = cls_name 

62 vars()[cls_name] = proxy_source_cls 

63 

64 for prop in source_cls.T.properties: 

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

66 

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

68 

69ProxyRectangularSource._name = 'RectangularSource' 

70 

71ProxyRectangularSource._ranges = { 

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

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

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

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

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

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

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

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

80 'nucleation_x': 

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

82 'nucleation_y': 

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

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

85 

86 

87class ProxyConfig(Object): 

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

89 deltat = Float.T(default=0.5) 

90 rho = Float.T(default=2800) 

91 vs = Float.T(default=3600) 

92 

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

94 points = kwargs.get('points') 

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

96 

97 

98class ProxyStore(Object): 

99 def __init__(self, **kwargs): 

100 config = ProxyConfig() 

101 if kwargs: 

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

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

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

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

106 

107 self.config = config 

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

109 self._f_data = None 

110 self._f_index = None 

111 

112 

113parameter_label = { 

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

115 

116 

117class SourceState(base.ElementState): 

118 visible = Bool.T(default=True) 

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

120 deltat = Float.T(default=0.5) 

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

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

123 

124 @classmethod 

125 def get_name(self): 

126 return 'Source' 

127 

128 def create(self): 

129 element = SourceElement() 

130 return element 

131 

132 

133class SourceElement(base.Element): 

134 

135 def __init__(self): 

136 base.Element.__init__(self) 

137 self._parent = None 

138 self._pipe = [] 

139 self._controls = None 

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

141 

142 self.cpt_handler = base.CPTHandler() 

143 

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

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

146 

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

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

149 

150 def bind_state(self, state): 

151 base.Element.bind_state(self, state) 

152 self.talkie_connect( 

153 state, 

154 ['visible', 'source_selection', 'deltat', 'display_parameter'], 

155 self.update) 

156 

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

158 

159 def unbind_state(self): 

160 self.cpt_handler.unbind_state() 

161 base.Element.unbind_state(self) 

162 

163 def get_name(self): 

164 return 'Source' 

165 

166 def set_parent(self, parent): 

167 self._parent = parent 

168 self._parent.add_panel( 

169 self.get_title_label(), 

170 self._get_controls(), 

171 visible=True, 

172 title_controls=[ 

173 self.get_title_control_remove(), 

174 self.get_title_control_visible()]) 

175 

176 self.update() 

177 

178 def unset_parent(self): 

179 self.unbind_state() 

180 if self._parent: 

181 if self._pipe: 

182 for pipe in self._pipe: 

183 if isinstance(pipe.actor, list): 

184 for act in pipe.actor: 

185 self._parent.remove_actor(act) 

186 else: 

187 self._parent.remove_actor(pipe.actor) 

188 self._pipe = [] 

189 

190 if self._controls: 

191 self._parent.remove_panel(self._controls) 

192 self._controls = None 

193 

194 self._parent.update_view() 

195 self._parent = None 

196 

197 def open_file_load_dialog(self): 

198 caption = 'Select one file to open' 

199 fns, _ = qw.QFileDialog.getOpenFileNames( 

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

201 

202 if fns: 

203 try: 

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

205 except gf.FileNotFoundError as e: 

206 raise e 

207 

208 else: 

209 return 

210 

211 def load_source_file(self, path): 

212 loaded_source = gf.load(filename=path) 

213 source = ProxyRectangularSource( 

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

215 for prop in loaded_source.T.propnames 

216 if getattr(loaded_source, prop)}) 

217 

218 self._parent.remove_panel(self._controls) 

219 self._controls = None 

220 self._state.source_selection = source 

221 self._parent.add_panel( 

222 self.get_title_label(), 

223 self._get_controls(), 

224 visible=True, 

225 title_controls=[ 

226 self.get_title_control_remove(), 

227 self.get_title_control_visible()]) 

228 

229 self.update() 

230 

231 def open_file_save_dialog(self, fn=None): 

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

233 if not fn: 

234 fn, _ = qw.QFileDialog.getSaveFileName( 

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

236 if fn: 

237 self.save_file(str(fn)) 

238 

239 def save_file(self, path): 

240 source = self._state.source_selection 

241 source2dump = gf.RectangularSource( 

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

243 

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

245 source2dump.dump_xml(filename=path) 

246 else: 

247 source2dump.dump(filename=path) 

248 

249 def update_loc(self, *args): 

250 pstate = self._parent.state 

251 state = self._state 

252 

253 source = state.source_selection 

254 source.lat = pstate.lat 

255 source.lon = pstate.lon 

256 

257 self._state.source_selection.source = source 

258 

259 self.update() 

260 

261 def update_source(self, store): 

262 state = self._state 

263 

264 source = state.source_selection 

265 source_list = gf.source_classes 

266 

267 for i, a in enumerate(source_list): 

268 if a.__name__ is source._name: 

269 fault = a( 

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

271 for prop in source.T.propnames}) 

272 

273 source_geom = fault.geometry(store) 

274 

275 self._update_outlines(source_geom) 

276 self._update_scatter(source, fault) 

277 self._update_raster(source_geom, state.display_parameter) 

278 self._update_rake_arrow(fault) 

279 

280 def _update_outlines(self, source_geom): 

281 

282 if source_geom.outlines: 

283 self._pipe.append(OutlinesPipe( 

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

285 self._parent.add_actor( 

286 self._pipe[-1].actor) 

287 

288 self._pipe.append(OutlinesPipe( 

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

290 self._parent.add_actor( 

291 self._pipe[-1].actor) 

292 

293 def _update_scatter(self, source, fault): 

294 for point, color in zip( 

295 ((source.nucleation_x, 

296 source.nucleation_y), 

297 map_anchor[source.anchor]), 

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

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

300 

301 points = geometry.latlondepth2xyz( 

302 fault.xy_to_coord( 

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

304 cs='latlondepth'), 

305 planetradius=cake.earthradius) 

306 

307 vertices = geometry.arr_vertices(points) 

308 p = ScatterPipe(vertices) 

309 p.set_symbol('sphere') 

310 p.set_colors(color) 

311 self._pipe.append(p) 

312 self._parent.add_actor(p.actor) 

313 

314 def _update_raster(self, source_geom, param): 

315 vertices = geometry.arr_vertices( 

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

317 

318 faces = source_geom.get_faces() 

319 

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

321 source_geom.has_property('t_arrival'): 

322 

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

324 cbar_title = 'T arr [s]' 

325 

326 self.cpt_handler.update_cpt() 

327 

328 poly_pipe = PolygonPipe( 

329 vertices, faces, 

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

331 

332 self._pipe.append(poly_pipe) 

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

334 

335 if cbar_title is not None: 

336 cbar_pipe = ColorbarPipe( 

337 parent_pipe=poly_pipe, cbar_title=cbar_title, 

338 lut=self.cpt_handler._lookuptable) 

339 

340 self._pipe.append(cbar_pipe) 

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

342 

343 def _update_rake_arrow(self, fault): 

344 source = self._state.source_selection 

345 rake = source.rake * d2r 

346 

347 nucl_x = source.nucleation_x 

348 nucl_y = source.nucleation_y 

349 

350 wd_ln = source.width / source.length 

351 

352 endpoint = [None] * 2 

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

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

355 

356 points = geometry.latlondepth2xyz( 

357 fault.xy_to_coord( 

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

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

360 cs='latlondepth'), 

361 planetradius=cake.earthradius) 

362 vertices = geometry.arr_vertices(points) 

363 

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

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

366 

367 def update(self, *args): 

368 state = self._state 

369 

370 store = ProxyStore( 

371 deltat=state.deltat) 

372 store.config.deltas = num.array( 

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

374 

375 if self._pipe: 

376 for pipe in self._pipe: 

377 self._parent.remove_actor(pipe.actor) 

378 

379 self._pipe = [] 

380 

381 if state.visible: 

382 self.update_source(store) 

383 

384 self._parent.update_view() 

385 

386 def _get_controls(self): 

387 if not self._controls: 

388 from ..state import \ 

389 state_bind_slider, state_bind_combobox 

390 from pyrocko import gf 

391 

392 source = self._state.source_selection 

393 

394 frame = qw.QFrame() 

395 layout = qw.QGridLayout() 

396 frame.setLayout(layout) 

397 

398 def state_to_lineedit(state, attribute, widget): 

399 sel = getattr(state, attribute) 

400 

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

402 # if sel: 

403 # widget.selectAll() 

404 

405 def lineedit_to_state(widget, state, attribute): 

406 s = float(widget.text()) 

407 try: 

408 setattr(state, attribute, s) 

409 except Exception: 

410 raise ValueError( 

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

412 % string.capwords(attribute)) 

413 

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

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

416 

417 layout.addWidget(qw.QLabel( 

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

419 

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

421 slider.setSizePolicy( 

422 qw.QSizePolicy( 

423 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) 

424 slider.setMinimum( 

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

426 slider.setMaximum( 

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

428 slider.setSingleStep( 

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

430 slider.setPageStep( 

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

432 

433 layout.addWidget(slider, il, 1) 

434 try: 

435 state_bind_slider( 

436 self, self._state.source_selection, label, slider, 

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

438 except Exception: 

439 state_bind_slider( 

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

441 

442 le = qw.QLineEdit() 

443 layout.addWidget(le, il, 2) 

444 

445 self._state_bind_source( 

446 [label], lineedit_to_state, le, 

447 [le.editingFinished, le.returnPressed], 

448 state_to_lineedit, attribute=label) 

449 

450 for label, name in zip( 

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

452 il += 1 

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

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

455 slider.setSizePolicy( 

456 qw.QSizePolicy( 

457 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) 

458 slider.setMinimum(1) 

459 slider.setMaximum(1000) 

460 slider.setSingleStep(1) 

461 slider.setPageStep(1) 

462 layout.addWidget(slider, il, 1) 

463 state_bind_slider( 

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

465 

466 le = qw.QLineEdit() 

467 layout.addWidget(le, il, 2) 

468 

469 self._state_bind_store( 

470 [name], lineedit_to_state, le, 

471 [le.editingFinished, le.returnPressed], 

472 state_to_lineedit, attribute=name) 

473 

474 il += 1 

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

476 

477 cb = qw.QComboBox() 

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

479 cb.insertItem(i, s) 

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

481 state_bind_combobox( 

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

483 

484 il += 1 

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

486 

487 cb = qw.QComboBox() 

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

489 cb.insertItem(i, s) 

490 layout.addWidget(cb, il, 1) 

491 state_bind_combobox( 

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

493 

494 self.cpt_handler.cpt_controls( 

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

496 

497 il = layout.rowCount() + 1 

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

499 layout.addWidget(pb, il, 0) 

500 pb.clicked.connect(self.update_loc) 

501 

502 pb = qw.QPushButton('Load') 

503 layout.addWidget(pb, il, 1) 

504 pb.clicked.connect(self.open_file_load_dialog) 

505 

506 pb = qw.QPushButton('Save') 

507 layout.addWidget(pb, il, 2) 

508 pb.clicked.connect(self.open_file_save_dialog) 

509 

510 il += 1 

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

512 

513 self._controls = frame 

514 

515 self.cpt_handler._update_cpt_combobox() 

516 self.cpt_handler._update_cptscale_lineedit() 

517 

518 return self._controls 

519 

520 

521__all__ = [ 

522 'SourceElement', 

523 'SourceState', 

524]