1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6import string
8import numpy as num
10from pyrocko.guts import Bool, Float, Object, String
12from pyrocko import cake, geometry, gf
13from pyrocko.gui.qt_compat import qc, qw
14from pyrocko.gui.talkie import TalkieRoot
16from pyrocko.gui.vtk_util import \
17 ArrowPipe, ColorbarPipe, PolygonPipe, ScatterPipe, OutlinesPipe
19from .. import state as vstate
20from .. import common
21from . import base
23guts_prefix = 'sparrow'
26d2r = num.pi / 180.
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)}
41class ProxySource(TalkieRoot):
42 pass
45for source_cls in [gf.RectangularSource]:
47 cls_name = 'Proxy' + source_cls.__name__
49 class proxy_source_cls(ProxySource):
50 class_name = cls_name
52 def __init__(self, **kwargs):
53 ProxySource.__init__(self)
54 for key, value in self._ranges.items():
55 setattr(self, key, value['ini'])
57 if kwargs is not None:
58 for it in kwargs.items():
59 setattr(self, it[0], it[1])
61 proxy_source_cls.__name__ = cls_name
62 vars()[cls_name] = proxy_source_cls
64 for prop in source_cls.T.properties:
65 proxy_source_cls.T.add_property(prop.name, prop)
67ProxyRectangularSource = vars()['ProxyRectangularSource'] # silence flake8
69ProxyRectangularSource._name = 'RectangularSource'
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}}
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)
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
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)
107 self.config = config
108 self.mode = String.T(default='r')
109 self._f_data = None
110 self._f_index = None
113parameter_label = {
114 'time (s)': 'times'}
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())
124 @classmethod
125 def get_name(self):
126 return 'Source'
128 def create(self):
129 element = SourceElement()
130 return element
133class SourceElement(base.Element):
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([])
142 self.cpt_handler = base.CPTHandler()
144 def _state_bind_source(self, *args, **kwargs):
145 vstate.state_bind(self, self._state.source_selection, *args, **kwargs)
147 def _state_bind_store(self, *args, **kwargs):
148 vstate.state_bind(self, self._state, *args, **kwargs)
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)
157 self.cpt_handler.bind_state(state.cpt, self.update)
159 def unbind_state(self):
160 self.cpt_handler.unbind_state()
161 base.Element.unbind_state(self)
163 def get_name(self):
164 return 'Source'
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()])
176 self.update()
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 = []
190 if self._controls:
191 self._parent.remove_panel(self._controls)
192 self._controls = None
194 self._parent.update_view()
195 self._parent = None
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)
202 if fns:
203 try:
204 self.load_file(str(fns[0]))
205 except gf.FileNotFoundError as e:
206 raise e
208 else:
209 return
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)})
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()])
229 self.update()
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))
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})
244 if path.split('.')[-1].lower() in ['xml']:
245 source2dump.dump_xml(filename=path)
246 else:
247 source2dump.dump(filename=path)
249 def update_loc(self, *args):
250 pstate = self._parent.state
251 state = self._state
253 source = state.source_selection
254 source.lat = pstate.lat
255 source.lon = pstate.lon
257 self._state.source_selection.source = source
259 self.update()
261 def update_source(self, store):
262 state = self._state
264 source = state.source_selection
265 source_list = gf.source_classes
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})
273 source_geom = fault.geometry(store)
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)
280 def _update_outlines(self, source_geom):
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)
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)
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.]]))):
301 points = geometry.latlondepth2xyz(
302 fault.xy_to_coord(
303 x=[point[0]], y=[point[1]],
304 cs='latlondepth'),
305 planetradius=cake.earthradius)
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)
314 def _update_raster(self, source_geom, param):
315 vertices = geometry.arr_vertices(
316 source_geom.get_vertices(col='xyz'))
318 faces = source_geom.get_faces()
320 if parameter_label[param] == 'times' and \
321 source_geom.has_property('t_arrival'):
323 self.cpt_handler._values = source_geom.get_property('t_arrival')
324 cbar_title = 'T arr [s]'
326 self.cpt_handler.update_cpt()
328 poly_pipe = PolygonPipe(
329 vertices, faces,
330 values=self.cpt_handler._values, lut=self.cpt_handler._lookuptable)
332 self._pipe.append(poly_pipe)
333 self._parent.add_actor(self._pipe[-1].actor)
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)
340 self._pipe.append(cbar_pipe)
341 self._parent.add_actor(self._pipe[-1].actor)
343 def _update_rake_arrow(self, fault):
344 source = self._state.source_selection
345 rake = source.rake * d2r
347 nucl_x = source.nucleation_x
348 nucl_y = source.nucleation_y
350 wd_ln = source.width / source.length
352 endpoint = [None] * 2
353 endpoint[0] = nucl_x + num.cos(rake) * wd_ln
354 endpoint[1] = nucl_y + num.sin(-rake)
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)
364 self._pipe.append(ArrowPipe(vertices[0], vertices[1]))
365 self._parent.add_actor(self._pipe[-1].actor)
367 def update(self, *args):
368 state = self._state
370 store = ProxyStore(
371 deltat=state.deltat)
372 store.config.deltas = num.array(
373 [(store.config.deltat * store.config.vs) + 1] * 2)
375 if self._pipe:
376 for pipe in self._pipe:
377 self._parent.remove_actor(pipe.actor)
379 self._pipe = []
381 if state.visible:
382 self.update_source(store)
384 self._parent.update_view()
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
392 source = self._state.source_selection
394 frame = qw.QFrame()
395 layout = qw.QGridLayout()
396 frame.setLayout(layout)
398 def state_to_lineedit(state, attribute, widget):
399 sel = getattr(state, attribute)
401 widget.setText('%g' % sel)
402 # if sel:
403 # widget.selectAll()
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))
414 for il, label in enumerate(source.T.propnames):
415 if label in source._ranges.keys():
417 layout.addWidget(qw.QLabel(
418 string.capwords(label) + ':'), il, 0)
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'])))
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)
442 le = qw.QLineEdit()
443 layout.addWidget(le, il, 2)
445 self._state_bind_source(
446 [label], lineedit_to_state, le,
447 [le.editingFinished, le.returnPressed],
448 state_to_lineedit, attribute=label)
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)
466 le = qw.QLineEdit()
467 layout.addWidget(le, il, 2)
469 self._state_bind_store(
470 [name], lineedit_to_state, le,
471 [le.editingFinished, le.returnPressed],
472 state_to_lineedit, attribute=name)
474 il += 1
475 layout.addWidget(qw.QLabel('Anchor:'), il, 0)
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)
484 il += 1
485 layout.addWidget(qw.QLabel('Display Param.:'), il, 0)
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)
494 self.cpt_handler.cpt_controls(
495 self._parent, self._state.cpt, layout)
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)
502 pb = qw.QPushButton('Load')
503 layout.addWidget(pb, il, 1)
504 pb.clicked.connect(self.open_file_load_dialog)
506 pb = qw.QPushButton('Save')
507 layout.addWidget(pb, il, 2)
508 pb.clicked.connect(self.open_file_save_dialog)
510 il += 1
511 layout.addWidget(qw.QFrame(), il, 0, 1, 3)
513 self._controls = frame
515 self.cpt_handler._update_cpt_combobox()
516 self.cpt_handler._update_cptscale_lineedit()
518 return self._controls
521__all__ = [
522 'SourceElement',
523 'SourceState',
524]