1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5from __future__ import absolute_import, print_function, division
7import string
9import numpy as num
11from pyrocko.guts import Bool, Float, Object, String
13from pyrocko import cake, geometry, gf
14from pyrocko.gui.qt_compat import qc, qw
15from pyrocko.gui.talkie import TalkieRoot
17from pyrocko.gui.vtk_util import \
18 ArrowPipe, ColorbarPipe, PolygonPipe, ScatterPipe, OutlinesPipe
20from .. import state as vstate
21from .. import common
22from . import base
24guts_prefix = 'sparrow'
27d2r = num.pi / 180.
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)}
42class ProxySource(TalkieRoot):
43 pass
46for source_cls in [gf.RectangularSource]:
48 cls_name = 'Proxy' + source_cls.__name__
50 class proxy_source_cls(ProxySource):
51 class_name = cls_name
53 def __init__(self, **kwargs):
54 ProxySource.__init__(self)
55 for key, value in self._ranges.items():
56 setattr(self, key, value['ini'])
58 if kwargs is not None:
59 for it in kwargs.items():
60 setattr(self, it[0], it[1])
62 proxy_source_cls.__name__ = cls_name
63 vars()[cls_name] = proxy_source_cls
65 for prop in source_cls.T.properties:
66 proxy_source_cls.T.add_property(prop.name, prop)
68ProxyRectangularSource = vars()['ProxyRectangularSource'] # silence flake8
70ProxyRectangularSource._name = 'RectangularSource'
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}}
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)
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
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)
108 self.config = config
109 self.mode = String.T(default='r')
110 self._f_data = None
111 self._f_index = None
114parameter_label = {
115 'time (s)': 'times'}
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())
125 @classmethod
126 def get_name(self):
127 return 'Source'
129 def create(self):
130 element = SourceElement()
131 return element
134class SourceElement(base.Element):
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([])
143 self.cpt_handler = base.CPTHandler()
145 def _state_bind_source(self, *args, **kwargs):
146 vstate.state_bind(self, self._state.source_selection, *args, **kwargs)
148 def _state_bind_store(self, *args, **kwargs):
149 vstate.state_bind(self, self._state, *args, **kwargs)
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)
157 self.cpt_handler.bind_state(state.cpt, self.update)
159 def unbind_state(self):
160 self._listeners = []
161 self._state = None
162 self.cpt_handler.unbind_state()
164 def get_name(self):
165 return 'Source'
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()])
177 self.update()
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 = []
191 if self._controls:
192 self._parent.remove_panel(self._controls)
193 self._controls = None
195 self._parent.update_view()
196 self._parent = None
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)
203 if fns:
204 try:
205 self.load_file(str(fns[0]))
206 except gf.FileNotFoundError as e:
207 raise e
209 else:
210 return
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)})
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()])
230 self.update()
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))
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})
245 if path.split('.')[-1].lower() in ['xml']:
246 source2dump.dump_xml(filename=path)
247 else:
248 source2dump.dump(filename=path)
250 def update_loc(self, *args):
251 pstate = self._parent.state
252 state = self._state
254 source = state.source_selection
255 source.lat = pstate.lat
256 source.lon = pstate.lon
258 self._state.source_selection.source = source
260 self.update()
262 def update_source(self, store):
263 state = self._state
265 source = state.source_selection
266 source_list = gf.source_classes
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})
274 source_geom = fault.geometry(store)
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)
281 def _update_outlines(self, source_geom):
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)
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)
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.]]))):
302 points = geometry.latlondepth2xyz(
303 fault.xy_to_coord(
304 x=[point[0]], y=[point[1]],
305 cs='latlondepth'),
306 planetradius=cake.earthradius)
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)
315 def _update_raster(self, source_geom, param):
316 vertices = geometry.arr_vertices(
317 source_geom.get_vertices(col='xyz'))
319 faces = source_geom.get_faces()
321 if parameter_label[param] == 'times' and \
322 source_geom.has_property('t_arrival'):
324 self.cpt_handler._values = source_geom.get_property('t_arrival')
325 cbar_title = 'T arr [s]'
327 self.cpt_handler.update_cpt()
329 poly_pipe = PolygonPipe(
330 vertices, faces,
331 values=self.cpt_handler._values, lut=self.cpt_handler._lookuptable)
333 self._pipe.append(poly_pipe)
334 self._parent.add_actor(self._pipe[-1].actor)
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)
341 self._pipe.append(cbar_pipe)
342 self._parent.add_actor(self._pipe[-1].actor)
344 def _update_rake_arrow(self, fault):
345 source = self._state.source_selection
346 rake = source.rake * d2r
348 nucl_x = source.nucleation_x
349 nucl_y = source.nucleation_y
351 wd_ln = source.width / source.length
353 endpoint = [None] * 2
354 endpoint[0] = nucl_x + num.cos(rake) * wd_ln
355 endpoint[1] = nucl_y + num.sin(-rake)
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)
365 self._pipe.append(ArrowPipe(vertices[0], vertices[1]))
366 self._parent.add_actor(self._pipe[-1].actor)
368 def update(self, *args):
369 state = self._state
371 store = ProxyStore(
372 deltat=state.deltat)
373 store.config.deltas = num.array(
374 [(store.config.deltat * store.config.vs) + 1] * 2)
376 if self._pipe:
377 for pipe in self._pipe:
378 self._parent.remove_actor(pipe.actor)
380 self._pipe = []
382 if state.visible:
383 self.update_source(store)
385 self._parent.update_view()
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
393 source = self._state.source_selection
395 frame = qw.QFrame()
396 layout = qw.QGridLayout()
397 frame.setLayout(layout)
399 def state_to_lineedit(state, attribute, widget):
400 sel = getattr(state, attribute)
402 widget.setText('%g' % sel)
403 # if sel:
404 # widget.selectAll()
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))
415 for il, label in enumerate(source.T.propnames):
416 if label in source._ranges.keys():
418 layout.addWidget(qw.QLabel(
419 string.capwords(label) + ':'), il, 0)
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'])))
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)
443 le = qw.QLineEdit()
444 layout.addWidget(le, il, 2)
446 self._state_bind_source(
447 [label], lineedit_to_state, le,
448 [le.editingFinished, le.returnPressed],
449 state_to_lineedit, attribute=label)
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)
467 le = qw.QLineEdit()
468 layout.addWidget(le, il, 2)
470 self._state_bind_store(
471 [name], lineedit_to_state, le,
472 [le.editingFinished, le.returnPressed],
473 state_to_lineedit, attribute=name)
475 il += 1
476 layout.addWidget(qw.QLabel('Anchor:'), il, 0)
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)
485 il += 1
486 layout.addWidget(qw.QLabel('Display Param.:'), il, 0)
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)
495 self.cpt_handler.cpt_controls(
496 self._parent, self._state.cpt, layout)
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)
503 pb = qw.QPushButton('Load')
504 layout.addWidget(pb, il, 1)
505 pb.clicked.connect(self.open_file_load_dialog)
507 pb = qw.QPushButton('Save')
508 layout.addWidget(pb, il, 2)
509 pb.clicked.connect(self.open_file_save_dialog)
511 il += 1
512 layout.addWidget(qw.QFrame(), il, 0, 1, 3)
514 self._controls = frame
516 self.cpt_handler._update_cpt_combobox()
517 self.cpt_handler._update_cptscale_lineedit()
519 return self._controls
522__all__ = [
523 'SourceElement',
524 'SourceState',
525]