├── mopad
├── setup.py
├── README.md
├── LICENSE.txt
└── mopad.py
/mopad:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | python -m mopad "$@"
4 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 |
3 | setup(
4 | name = 'mopad',
5 | version = '0.9b',
6 | description = 'Moment tensor Plotting and Decomposition tool',
7 | py_modules = ['mopad'],
8 | scripts = ['mopad'],
9 | author = 'Lars Krieger',
10 | author_email = 'lars.krieger@zmaw.de',
11 | url = 'http://geophysics.github.com/MoPaD/',
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MoPaD
2 | =====
3 |
4 | the MoPaD repository
5 |
6 | Put to GITHUB in version 09b
7 |
8 | Python source:
9 |
10 | mopad.py
11 |
12 |
13 | License:
14 |
15 | MoPaD is an open source program.
16 | It has been developed under the LGPL license.
17 | The license agreement should always be kept together with the code.
18 | (License.txt)
19 |
20 | Copyright by Lars Krieger and Sebastian Heimann 2010.
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/mopad.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from cStringIO import StringIO
4 | import optparse
5 | import math
6 | import numpy as np
7 | import os
8 | import os.path
9 | import sys
10 | import re
11 |
12 | MOPAD_VERSION = 1.0
13 |
14 |
15 | # constants:
16 | dynecm = 1e-7
17 | pi = np.pi
18 |
19 | epsilon = 1e-13
20 |
21 | rad2deg = 180. / pi
22 |
23 |
24 | def wrap(text, line_length=80):
25 | '''Paragraph and list-aware wrapping of text.'''
26 |
27 | text = text.strip('\n')
28 | at_lineend = re.compile(r' *\n')
29 | at_para = re.compile(r'((^|(\n\s*)?\n)(\s+[*] )|\n\s*\n)')
30 |
31 | paragraphs = at_para.split(text)[::5]
32 | listindents = at_para.split(text)[4::5]
33 | newlist = at_para.split(text)[3::5]
34 |
35 | listindents[0:0] = [None]
36 | listindents.append(True)
37 | newlist.append(None)
38 |
39 | det_indent = re.compile(r'^ *')
40 |
41 | outlines = []
42 | for ip, p in enumerate(paragraphs):
43 | if not p:
44 | continue
45 |
46 | if listindents[ip] is None:
47 | _indent = det_indent.findall(p)[0]
48 | findent = _indent
49 | else:
50 | findent = listindents[ip]
51 | _indent = ' ' * len(findent)
52 |
53 | ll = line_length - len(_indent)
54 | llf = ll
55 |
56 | oldlines = [s.strip() for s in at_lineend.split(p.rstrip())]
57 | p1 = ' '.join(oldlines)
58 | possible = re.compile(r'(^.{1,%i}|.{1,%i})( |$)' % (llf, ll))
59 | for imatch, match in enumerate(possible.finditer(p1)):
60 | parout = match.group(1)
61 | if imatch == 0:
62 | outlines.append(findent + parout)
63 | else:
64 | outlines.append(_indent + parout)
65 |
66 | if ip != len(paragraphs) - 1 and (
67 | listindents[ip] is None or
68 | newlist[ip] is not None or
69 | listindents[ip + 1] is None):
70 |
71 | outlines.append('')
72 |
73 | return outlines
74 |
75 |
76 | def basis_switcher(in_system, out_system):
77 | from_ned = {
78 | 'NED': np.matrix([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
79 | dtype=np.float),
80 | 'USE': np.matrix([[0., -1., 0.], [0., 0., 1.], [-1., 0., 0.]],
81 | dtype=np.float).I,
82 | 'XYZ': np.matrix([[0., 1., 0.], [1., 0., 0.], [0., 0., -1.]],
83 | dtype=np.float).I,
84 | 'NWU': np.matrix([[1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
85 | dtype=np.float).I}
86 |
87 | return from_ned[in_system].I * from_ned[out_system]
88 |
89 |
90 | def basis_transform_matrix(m, in_system, out_system):
91 | r = basis_switcher(in_system, out_system)
92 | return np.dot(r, np.dot(m, r.I))
93 |
94 |
95 | def basis_transform_vector(v, in_system, out_system):
96 | r = basis_switcher(in_system, out_system)
97 | return np.dot(r, v)
98 |
99 |
100 | class MopadHelpFormatter(optparse.IndentedHelpFormatter):
101 |
102 | def format_option(self, option):
103 | '''From IndentedHelpFormatter but using a different wrap method.'''
104 |
105 | result = []
106 | opts = self.option_strings[option]
107 | opt_width = self.help_position - self.current_indent - 2
108 | if len(opts) > opt_width:
109 | opts = "%*s%s\n" % (self.current_indent, "", opts)
110 | indent_first = self.help_position
111 | else: # start help on same line as opts
112 | opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
113 | indent_first = 0
114 | result.append(opts)
115 | if option.help:
116 | help_text = self.expand_default(option)
117 | help_lines = wrap(help_text, self.help_width)
118 | if len(help_lines) > 1:
119 | help_lines.append('')
120 | result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
121 | result.extend(["%*s%s\n" % (self.help_position, "", line)
122 | for line in help_lines[1:]])
123 | elif opts[-1] != "\n":
124 | result.append("\n")
125 | return "".join(result)
126 |
127 | def format_description(self, description):
128 | if not description:
129 | return ""
130 | desc_width = self.width - self.current_indent
131 | return '\n'.join(wrap(description, desc_width)) + "\n"
132 |
133 |
134 | class MTError(Exception):
135 | pass
136 |
137 |
138 | def euler_to_matrix(alpha, beta, gamma):
139 | '''Given the euler angles alpha,beta,gamma, create rotation matrix
140 |
141 | Given coordinate system (x,y,z) and rotated system (xs,ys,zs)
142 | the line of nodes is the intersection between the x-y and the xs-ys
143 | planes.
144 | alpha is the angle between the z-axis and the zs-axis.
145 | beta is the angle between the x-axis and the line of nodes.
146 | gamma is the angle between the line of nodes and the xs-axis.
147 |
148 | Usage for moment tensors:
149 | m_unrot = numpy.matrix([[0,0,-1],[0,0,0],[-1,0,0]])
150 | rotmat = euler_to_matrix(dip,strike,-rake)
151 | m = rotmat.T * m_unrot * rotmat'''
152 |
153 | ca = math.cos(alpha)
154 | cb = math.cos(beta)
155 | cg = math.cos(gamma)
156 | sa = math.sin(alpha)
157 | sb = math.sin(beta)
158 | sg = math.sin(gamma)
159 |
160 | mat = np.matrix(
161 | [[cb * cg - ca * sb * sg, sb * cg + ca * cb * sg, sa * sg],
162 | [-cb * sg - ca * sb * cg, -sb *
163 | sg + ca * cb * cg, sa * cg],
164 | [sa * sb, -sa * cb, ca]], dtype=np.float)
165 | return mat
166 |
167 |
168 | class MomentTensor:
169 |
170 | _m_unrot = np.matrix(
171 | [[0., 0., -1.], [0., 0., 0.], [-1., 0., 0.]], dtype=np.float)
172 |
173 | def __init__(self, M=None, in_system='NED', out_system='NED', debug=0):
174 |
175 | """
176 | Creates a moment tensor object on the basis of a provided mechanism M.
177 |
178 | If M is a non symmetric 3x3-matrix, the upper right triangle
179 | of the matrix is taken as reference. M is symmetrisised
180 | w.r.t. these entries. If M is provided as a 3-,4-,6-,7-tuple
181 | or array, it is converted into a matrix internally according
182 | to standard conventions (Aki & Richards).
183 |
184 | 'system' may be chosen as 'NED','USE','NWU', or 'XYZ'.
185 |
186 | 'debug' enables output on the shell at the intermediate steps.
187 | """
188 |
189 | self._original_M = M[:]
190 |
191 | self._input_basis = in_system.upper()
192 | self._output_basis = out_system.upper()
193 |
194 | # bring M to symmetric matrix form
195 | self._M = self._setup_M(M, self._input_basis)
196 |
197 | # decomposition:
198 | self._decomposition_key = 1
199 |
200 | # eigenvector / principal-axes system:
201 | self._eigenvalues = None
202 | self._eigenvectors = None
203 | self._null_axis = None
204 | self._t_axis = None
205 | self._p_axis = None
206 | self._rotation_matrix = None
207 |
208 | # optional - maybe set afterwards by external application - for later
209 | # plotting:
210 | self._best_faultplane = None
211 | self._auxiliary_plane = None
212 |
213 | # initialise decomposition components
214 | self._DC = None
215 | self._DC_percentage = None
216 | self._DC2 = None
217 | self._DC2_percentage = None
218 | self._DC3 = None
219 | self._DC3_percentage = None
220 |
221 | self._iso = None
222 | self._iso_percentage = None
223 | self._devi = None
224 | self._devi_percentage = None
225 | self._CLVD = None
226 | self._CLVD_percentage = None
227 |
228 | self._isotropic = None
229 | self._deviatoric = None
230 | self._seismic_moment = None
231 | self._moment_magnitude = None
232 |
233 | self._decomp_attrib_map_keys = ('in', 'out', 'type',
234 | 'full',
235 | 'iso', 'iso_perc',
236 | 'dev', 'devi', 'devi_perc',
237 | 'dc', 'dc_perc',
238 | 'dc2', 'dc2_perc',
239 | 'dc3', 'dc3_perc',
240 | 'clvd', 'clvd_perc',
241 | 'mom', 'mag',
242 | 'eigvals', 'eigvecs',
243 | 't', 'n', 'p')
244 |
245 | self._decomp_attrib_map = dict(zip(self._decomp_attrib_map_keys,
246 | ('input_system', 'output_system',
247 | 'decomp_type', 'M',
248 | 'iso', 'iso_percentage',
249 | 'devi', 'devi', 'devi_percentage',
250 | 'DC', 'DC_percentage',
251 | 'DC2', 'DC2_percentage',
252 | 'DC3', 'DC3_percentage',
253 | 'CLVD', 'CLVD_percentage',
254 | 'moment', 'mag',
255 | 'eigvals', 'eigvecs',
256 | 't_axis', 'null_axis', 'p_axis')
257 | ))
258 |
259 | # carry out the MT decomposition - results are in basis NED
260 | self._decompose_M()
261 |
262 | # set the appropriate principal axis system:
263 | self._M_to_principal_axis_system()
264 |
265 | def _setup_M(self, mech, input_basis):
266 | """
267 | Brings the provided mechanism into symmetric 3x3 matrix form.
268 |
269 | The source mechanism may be provided in different forms:
270 |
271 | * as 3x3 matrix - symmetry is checked - one basis system has to be
272 | chosen, or NED as default is taken
273 | * as 3-element tuple or array - interpreted as strike, dip, slip-rake
274 | angles in degree
275 | * as 4-element tuple or array - interpreted as strike, dip, slip-rake
276 | angles in degree + seismic scalar moment in Nm
277 | * as 6-element tuple or array - interpreted as the 6 independent
278 | entries of the moment tensor
279 | * as 7-element tuple or array - interpreted as the 6 independent
280 | entries of the moment tensor + seismic scalar moment in Nm
281 | * as 9-element tuple or array - interpreted as the 9 entries of the
282 | moment tensor - checked for symmetry
283 | * as a nesting of one of the upper types (e.g. a list of n-tuples);
284 | first element of outer nesting is taken
285 | """
286 | # set source mechanism to matrix form
287 |
288 | if mech is None:
289 | raise MTError('Please provide a mechanism')
290 |
291 | # if some stupid nesting occurs
292 | if len(mech) == 1:
293 | mech = mech[0]
294 |
295 | # all 9 elements are given
296 | if np.prod(np.shape(mech)) == 9:
297 | if np.shape(mech)[0] == 3:
298 | # assure symmetry:
299 | mech[1, 0] = mech[0, 1]
300 | mech[2, 0] = mech[0, 2]
301 | mech[2, 1] = mech[1, 2]
302 | new_M = mech
303 | else:
304 | new_M = np.array(mech).reshape(3, 3).copy()
305 | new_M[1, 0] = new_M[0, 1]
306 | new_M[2, 0] = new_M[0, 2]
307 | new_M[2, 1] = new_M[1, 2]
308 |
309 | # mechanism given as 6- or 7-tuple, list or array
310 | elif len(mech) == 6 or len(mech) == 7:
311 | M = mech
312 | new_M = np.matrix(
313 | np.array([M[0], M[3], M[4],
314 | M[3], M[1], M[5],
315 | M[4], M[5], M[2]]).reshape(3, 3))
316 |
317 | if len(mech) == 7:
318 | new_M = M[6] * new_M
319 |
320 | # if given as strike, dip, rake, conventions from Jost & Herrmann hold
321 | # - resulting matrix is in NED-basis:
322 | elif len(mech) == 3 or len(mech) == 4:
323 | strike, dip, rake = mech[:3]
324 | scalar_moment = 1.0
325 | if len(mech) == 4:
326 | scalar_moment = mech[3]
327 |
328 | rotmat1 = euler_to_matrix(
329 | dip / rad2deg, strike / rad2deg, -rake / rad2deg)
330 | new_M = rotmat1.T * MomentTensor._m_unrot * rotmat1 * scalar_moment
331 |
332 | # to assure right basis system - others are meaningless, provided
333 | # these angles
334 | input_basis = 'NED'
335 |
336 | return basis_transform_matrix(np.matrix(new_M), input_basis, 'NED')
337 |
338 | def _decompose_M(self):
339 | """
340 | Running the decomposition of the moment tensor object.
341 |
342 | the standard decompositions M = Isotropic + DC + (CLVD or 2nd DC) are
343 | supported (C.f. Jost & Herrmann, Aki & Richards)
344 | """
345 | k = self._decomposition_key
346 | d = MomentTensor.decomp_dict
347 | if k in d:
348 | d[k][1](self)
349 |
350 | else:
351 | raise MTError('Invalid decomposition key: %i' % k)
352 |
353 | def print_decomposition(self):
354 | for arg in self._decomp_attrib_map_keys:
355 | getter = getattr(self, 'get_' + self._decomp_attrib_map[arg])
356 | print getter(style='y', system=self._output_basis)
357 |
358 | def _standard_decomposition(self):
359 | """
360 | Decomposition according Aki & Richards and Jost & Herrmann into
361 |
362 | isotropic + deviatoric
363 | = isotropic + DC + CLVD
364 |
365 | parts of the input moment tensor.
366 |
367 | results are given as attributes, callable via the get_* function:
368 |
369 | DC, CLVD, DC_percentage, seismic_moment, moment_magnitude
370 | """
371 |
372 | M = self._M
373 |
374 | # isotropic part
375 | M_iso = np.diag(np.array([1. / 3 * np.trace(M),
376 | 1. / 3 * np.trace(M),
377 | 1. / 3 * np.trace(M)]))
378 |
379 | M0_iso = abs(1. / 3 * np.trace(M))
380 |
381 | # deviatoric part
382 | M_devi = M - M_iso
383 |
384 | self._isotropic = M_iso
385 | self._deviatoric = M_devi
386 |
387 | #eigenvalues and -vectors
388 | eigenwtot, eigenvtot = np.linalg.eig(M_devi)
389 |
390 | # eigenvalues and -vectors of the deviatoric part
391 | eigenw1, eigenv1 = np.linalg.eig(M_devi)
392 |
393 | # eigenvalues in ascending order:
394 | eigenw = np.real(np.take(eigenw1, np.argsort(abs(eigenwtot))))
395 | eigenv = np.real(np.take(eigenv1, np.argsort(abs(eigenwtot)), 1))
396 |
397 | # eigenvalues in ascending order in absolute value!!:
398 | eigenw_devi = np.real(np.take(eigenw1, np.argsort(abs(eigenw1))))
399 | #eigenv_devi = np.real(np.take(eigenv1, np.argsort(abs(eigenw1)), 1))
400 |
401 | M0_devi = max(abs(eigenw_devi))
402 |
403 | # named according to Jost & Herrmann:
404 | #a1 = eigenv[:, 0]
405 | a2 = eigenv[:, 1]
406 | a3 = eigenv[:, 2]
407 |
408 | # if only isotropic part exists:
409 | if M0_devi < epsilon:
410 | F = 0.5
411 | else:
412 | F = -eigenw_devi[0] / eigenw_devi[2]
413 |
414 | M_DC = np.matrix(np.zeros((9), float)).reshape(3, 3)
415 | M_CLVD = np.matrix(np.zeros((9), float)).reshape(3, 3)
416 |
417 | M_DC = eigenw[2] * (1 - 2 * F) * (np.outer(a3, a3) - np.outer(a2, a2))
418 | M_CLVD = M_devi - M_DC
419 |
420 | # according to Bowers & Hudson:
421 | M0 = M0_iso + M0_devi
422 |
423 | M_iso_percentage = int(round(M0_iso / M0 * 100, 6))
424 | self._iso_percentage = M_iso_percentage
425 |
426 | M_DC_percentage = int(round((1 - 2 * abs(F)) *
427 | (1 - M_iso_percentage / 100.) * 100, 6))
428 |
429 | self._DC = M_DC
430 | self._CLVD = M_CLVD
431 | self._DC_percentage = M_DC_percentage
432 |
433 | self._seismic_moment = M0
434 | self._moment_magnitude = np.log10(
435 | self._seismic_moment * 1.0e7) / 1.5 - 10.7
436 |
437 | def _decomposition_w_2DC(self):
438 | """
439 | Decomposition according Aki & Richards and Jost & Herrmann into
440 |
441 | isotropic + deviatoric
442 | = isotropic + DC + DC2
443 |
444 | parts of the input moment tensor.
445 |
446 | results are given as attributes, callable via the get_* function:
447 |
448 | DC1, DC2, DC_percentage, seismic_moment, moment_magnitude
449 | """
450 | M = self._M
451 |
452 | # isotropic part
453 | M_iso = np.diag(np.array([1. / 3 * np.trace(M),
454 | 1. / 3 * np.trace(M),
455 | 1. / 3 * np.trace(M)]))
456 |
457 | M0_iso = abs(1. / 3 * np.trace(M))
458 |
459 | # deviatoric part
460 | M_devi = M - M_iso
461 |
462 | self._isotropic = M_iso
463 | self._deviatoric = M_devi
464 |
465 | # eigenvalues and -vectors of the deviatoric part
466 | eigenw1, eigenv1 = np.linalg.eig(M_devi)
467 |
468 | # eigenvalues in ascending order of their absolute values:
469 | eigenw = np.real(
470 | np.take(eigenw1, np.argsort(abs(eigenw1))))
471 | eigenv = np.real(
472 | np.take(eigenv1, np.argsort(abs(eigenw1)), 1))
473 |
474 | M0_devi = max(abs(eigenw))
475 |
476 | # named according to Jost & Herrmann:
477 | a1 = eigenv[:, 0]
478 | a2 = eigenv[:, 1]
479 | a3 = eigenv[:, 2]
480 |
481 | M_DC = np.matrix(np.zeros((9), float)).reshape(3, 3)
482 | M_DC2 = np.matrix(np.zeros((9), float)).reshape(3, 3)
483 |
484 | M_DC = eigenw[2] * (np.outer(a3, a3) - np.outer(a2, a2))
485 | M_DC2 = eigenw[0] * (np.outer(a1, a1) - np.outer(a2, a2))
486 |
487 | M_DC_percentage = abs(eigenw[2] / (abs(eigenw[2]) + abs(eigenw[0])))
488 |
489 | self._DC = M_DC
490 | self._DC2 = M_DC2
491 | self._DC_percentage = M_DC_percentage
492 |
493 | # according to Bowers & Hudson:
494 | M0 = M0_iso + M0_devi
495 |
496 | M_iso_percentage = int(M0_iso / M0 * 100)
497 | self._iso_percentage = M_iso_percentage
498 |
499 | #self._seismic_moment = np.sqrt(1./2*np.sum(eigenw**2) )
500 | self._seismic_moment = M0
501 | self._moment_magnitude = np.log10(
502 | self._seismic_moment * 1.0e7) / 1.5 - 10.7
503 |
504 | def _decomposition_w_CLVD_2DC(self):
505 | """
506 | Decomposition according to Dahm (1993) into
507 |
508 | - isotropic
509 | - CLVD
510 | - strike-slip
511 | - dip-slip
512 |
513 | parts of the input moment tensor.
514 |
515 | results are given as attributes, callable via the get_* function:
516 |
517 | iso, CLVD, DC1, DC2, iso_percentage, DC_percentage, DC1_percentage,
518 | DC2_percentage, CLVD_percentage, seismic_moment, moment_magnitude
519 | """
520 | M = self._M
521 |
522 | # isotropic part
523 | M_iso = np.diag(
524 | np.array([1. / 3 * np.trace(M),
525 | 1. / 3 * np.trace(M),
526 | 1. / 3 * np.trace(M)]))
527 |
528 | #M0_iso = abs(1. / 3 * np.trace(M))
529 |
530 | # deviatoric part
531 | M_devi = M - M_iso
532 |
533 | self._isotropic = M_iso
534 | self._deviatoric = M_devi
535 |
536 | M_DC1 = np.matrix(np.zeros((9), float)).reshape(3, 3)
537 | M_DC2 = np.matrix(np.zeros((9), float)).reshape(3, 3)
538 | M_CLVD = np.matrix(np.zeros((9), float)).reshape(3, 3)
539 |
540 | M_DC1[0, 0] = -0.5 * (M[1, 1] - M[0, 0])
541 | M_DC1[1, 1] = 0.5 * (M[1, 1] - M[0, 0])
542 | M_DC1[0, 1] = M_DC1[1, 0] = M[0, 1]
543 |
544 | M_DC2[0, 2] = M_DC2[2, 0] = M[0, 2]
545 | M_DC2[1, 2] = M_DC2[2, 1] = M[1, 2]
546 |
547 | M_CLVD = 1. / 3. * \
548 | (0.5 * (M[1, 1] + M[0, 0]) - M[2, 2]) * \
549 | np.diag(np.array([1., 1., -2.]))
550 |
551 | M_DC = M_DC1 + M_DC2
552 |
553 | self._DC = M_DC
554 | self._DC1 = M_DC1
555 | self._DC2 = M_DC2
556 |
557 | # according to Bowers & Hudson:
558 | eigvals_M, dummy_vecs = np.linalg.eig(M)
559 | eigvals_M_devi, dummy_vecs = np.linalg.eig(M_devi)
560 | eigvals_M_iso, dummy_iso = np.linalg.eig(M_iso)
561 | eigvals_M_clvd, dummy_vecs = np.linalg.eig(M_CLVD)
562 | eigvals_M_dc1, dummy_vecs = np.linalg.eig(M_DC1)
563 | eigvals_M_dc2, dummy_vecs = np.linalg.eig(M_DC2)
564 |
565 | #M0_M = np.max(np.abs(eigvals_M - 1./3*np.sum(eigvals_M) ))
566 | M0_M_iso = np.max(
567 | np.abs(eigvals_M_iso - 1. / 3 * np.sum(eigvals_M)))
568 | M0_M_clvd = np.max(
569 | np.abs(eigvals_M_clvd - 1. / 3 * np.sum(eigvals_M)))
570 | M0_M_dc1 = np.max(
571 | np.abs(eigvals_M_dc1 - 1. / 3 * np.sum(eigvals_M)))
572 | M0_M_dc2 = np.max(
573 | np.abs(eigvals_M_dc2 - 1. / 3 * np.sum(eigvals_M)))
574 |
575 | M0_M_dc = M0_M_dc1 + M0_M_dc2
576 | M0_M_devi = M0_M_clvd + M0_M_dc
577 | M0_M = M0_M_iso + M0_M_devi
578 |
579 | self._iso_percentage = int(M0_M_iso / M0_M * 100)
580 | self._DC_percentage = int(M0_M_dc / M0_M * 100)
581 | self._DC1_percentage = int(M0_M_dc1 / M0_M * 100)
582 | self._DC2_percentage = int(M0_M_dc2 / M0_M * 100)
583 |
584 | #self._seismic_moment = np.sqrt(1./2*np.sum(eigenw**2) )
585 | self._seismic_moment = M0_M
586 | self._moment_magnitude = np.log10(
587 | self._seismic_moment * 1.0e7) / 1.5 - 10.7
588 |
589 | def _decomposition_w_3DC(self):
590 | """
591 | Decomposition according Aki & Richards and Jost & Herrmann into
592 |
593 | - isotropic
594 | - deviatoric
595 | - 3 DC
596 |
597 | parts of the input moment tensor.
598 |
599 | results are given as attributes, callable via the get_* function:
600 |
601 | DC1, DC2, DC3, DC_percentage, seismic_moment, moment_magnitude
602 | """
603 | M = self._M
604 |
605 | # isotropic part
606 | M_iso = np.diag(np.array([1. / 3 * np.trace(M),
607 | 1. / 3 * np.trace(M),
608 | 1. / 3 * np.trace(M)]))
609 | M0_iso = abs(1. / 3 * np.trace(M))
610 |
611 | # deviatoric part
612 | M_devi = M - M_iso
613 |
614 | self._isotropic = M_iso
615 | self._deviatoric = M_devi
616 |
617 | # eigenvalues and -vectors of the deviatoric part
618 | eigenw1, eigenv1 = np.linalg.eig(M_devi)
619 | M0_devi = max(abs(eigenw1))
620 |
621 | # eigenvalues and -vectors of the full M !!!!!!!!
622 | eigenw1, eigenv1 = np.linalg.eig(M)
623 |
624 | # eigenvalues in ascending order of their absolute values:
625 | eigenw = np.real(
626 | np.take(eigenw1, np.argsort(abs(eigenw1))))
627 | eigenv = np.real(
628 | np.take(eigenv1, np.argsort(abs(eigenw1)), 1))
629 |
630 | # named according to Jost & Herrmann:
631 | a1 = eigenv[:, 0]
632 | a2 = eigenv[:, 1]
633 | a3 = eigenv[:, 2]
634 |
635 | M_DC1 = np.matrix(np.zeros((9), float)).reshape(3, 3)
636 | M_DC2 = np.matrix(np.zeros((9), float)).reshape(3, 3)
637 | M_DC3 = np.matrix(np.zeros((9), float)).reshape(3, 3)
638 |
639 | M_DC1 = 1. / 3. * \
640 | (eigenw[0] - eigenw[1]) * (np.outer(a1, a1) - np.outer(a2, a2))
641 | M_DC2 = 1. / 3. * \
642 | (eigenw[1] - eigenw[2]) * (np.outer(a2, a2) - np.outer(a3, a3))
643 | M_DC3 = 1. / 3. * \
644 | (eigenw[2] - eigenw[0]) * (np.outer(a3, a3) - np.outer(a1, a1))
645 |
646 | M_DC1_perc = int(100 * abs((eigenw[0] - eigenw[1])) /
647 | (abs((eigenw[1] - eigenw[2])) +
648 | abs((eigenw[1] - eigenw[2])) +
649 | abs((eigenw[2] - eigenw[0]))))
650 | M_DC2_perc = int(100 * abs((eigenw[1] - eigenw[2])) /
651 | (abs((eigenw[1] - eigenw[2])) +
652 | abs((eigenw[1] - eigenw[2])) +
653 | abs((eigenw[2] - eigenw[0]))))
654 |
655 | self._DC = M_DC1
656 | self._DC2 = M_DC2
657 | self._DC3 = M_DC3
658 |
659 | self._DC_percentage = M_DC1_perc
660 | self._DC2_percentage = M_DC2_perc
661 |
662 | # according to Bowers & Hudson:
663 | M0 = M0_iso + M0_devi
664 |
665 | M_iso_percentage = int(M0_iso / M0 * 100)
666 | self._iso_percentage = M_iso_percentage
667 |
668 | #self._seismic_moment = np.sqrt(1./2*np.sum(eigenw**2) )
669 | self._seismic_moment = M0
670 | self._moment_magnitude = np.log10(
671 | self._seismic_moment * 1.0e7) / 1.5 - 10.7
672 |
673 | def _M_to_principal_axis_system(self):
674 | """
675 | Read in Matrix M and set up eigenvalues (EW) and eigenvectors
676 | (EV) for setting up the principal axis system.
677 |
678 | The internal convention is the 'HNS'-system: H is the
679 | eigenvector for the smallest absolute eigenvalue, S is the
680 | eigenvector for the largest absolute eigenvalue, N is the null
681 | axis.
682 |
683 | Naming due to the geometry: a CLVD is
684 | Symmetric to the S-axis,
685 | Null-axis is common sense, and the third (auxiliary) axis
686 | Helps to construct the R³.
687 |
688 | Additionally builds matrix for basis transformation back to NED system.
689 |
690 | The eigensystem setup defines the colouring order for a later
691 | plotting in the BeachBall class. This order is set by the
692 | '_plot_clr_order' attribute.
693 | """
694 |
695 | M = self._M
696 | M_devi = self._deviatoric
697 |
698 | # working in framework of 3 principal axes:
699 | # eigenvalues (EW) are in order from high to low
700 | # - neutral axis N, belongs to middle EW
701 | # - symmetry axis S ('sigma') belongs to EW with largest absolute value
702 | # (P- or T-axis)
703 | # - auxiliary axis H ('help') belongs to remaining EW (T- or P-axis)
704 | # EW sorting from lowest to highest value
705 | EW_devi, EV_devi = np.linalg.eigh(M_devi)
706 | EW_order = np.argsort(EW_devi)
707 |
708 | # print 'order',EW_order
709 |
710 | if 1: # self._plot_isotropic_part:
711 | trace_M = np.trace(M)
712 | if abs(trace_M) < epsilon:
713 | trace_M = 0
714 | EW, EV = np.linalg.eigh(M)
715 | for i, ew in enumerate(EW):
716 | if abs(EW[i]) < epsilon:
717 | EW[i] = 0
718 | else:
719 | trace_M = np.trace(M_devi)
720 | if abs(trace_M) < epsilon:
721 | trace_M = 0
722 |
723 | EW, EV = np.linalg.eigh(M_devi)
724 | for i, ew in enumerate(EW):
725 | if abs(EW[i]) < epsilon:
726 | EW[i] = 0
727 |
728 | EW1_devi = EW_devi[EW_order[0]]
729 | EW2_devi = EW_devi[EW_order[1]]
730 | EW3_devi = EW_devi[EW_order[2]]
731 | EV1_devi = EV_devi[:, EW_order[0]]
732 | EV2_devi = EV_devi[:, EW_order[1]]
733 | EV3_devi = EV_devi[:, EW_order[2]]
734 |
735 | EW1 = EW[EW_order[0]]
736 | EW2 = EW[EW_order[1]]
737 | EW3 = EW[EW_order[2]]
738 | EV1 = EV[:, EW_order[0]]
739 | EV2 = EV[:, EW_order[1]]
740 | EV3 = EV[:, EW_order[2]]
741 |
742 | chng_basis_tmp = np.asmatrix(np.zeros((3, 3)))
743 | chng_basis_tmp[:, 0] = EV1_devi
744 | chng_basis_tmp[:, 1] = EV2_devi
745 | chng_basis_tmp[:, 2] = EV3_devi
746 |
747 | symmetry_around_tension = 1
748 | clr = 1
749 |
750 | if abs(EW2_devi) < epsilon:
751 | EW2_devi = 0
752 |
753 | # implosion
754 | if EW1 < 0 and EW2 < 0 and EW3 < 0:
755 | symmetry_around_tension = 0
756 | # logger.debug( 'IMPLOSION - symmetry around pressure axis \n\n')
757 | clr = 1
758 | # explosion
759 | elif EW1 > 0 and EW2 > 0 and EW3 > 0:
760 | symmetry_around_tension = 1
761 | if abs(EW1_devi) > abs(EW3_devi):
762 | symmetry_around_tension = 0
763 | # logger.debug( 'EXPLOSION - symmetry around tension axis \n\n')
764 | clr = -1
765 | # net-implosion
766 | elif EW2 < 0 and sum([EW1, EW2, EW3]) < 0:
767 | if abs(EW1_devi) < abs(EW3_devi):
768 | symmetry_around_tension = 1
769 | clr = 1
770 | else:
771 | symmetry_around_tension = 1
772 | clr = 1
773 | # net-implosion
774 | elif EW2_devi >= 0 and sum([EW1, EW2, EW3]) < 0:
775 | symmetry_around_tension = 0
776 | clr = -1
777 | if abs(EW1_devi) < abs(EW3_devi):
778 | symmetry_around_tension = 1
779 | clr = 1
780 | # net-explosion
781 | elif EW2_devi < 0 and sum([EW1, EW2, EW3]) > 0:
782 | symmetry_around_tension = 1
783 | clr = 1
784 | if abs(EW1_devi) > abs(EW3_devi):
785 | symmetry_around_tension = 0
786 | clr = -1
787 | # net-explosion
788 | elif EW2_devi >= 0 and sum([EW1, EW2, EW3]) > 0:
789 | symmetry_around_tension = 0
790 | clr = -1
791 | else:
792 | # TODO check: this point should never be reached !!
793 | pass
794 |
795 | if abs(EW1_devi) < abs(EW3_devi):
796 | symmetry_around_tension = 1
797 | clr = 1
798 | if 0: # EW2 > 0 :#or (EW2 > 0 and EW2_devi > 0) :
799 | symmetry_around_tension = 0
800 | clr = -1
801 |
802 | if abs(EW1_devi) >= abs(EW3_devi):
803 | symmetry_around_tension = 0
804 | clr = -1
805 | if 0: # EW2 < 0 :
806 | symmetry_around_tension = 1
807 | clr = 1
808 | if (EW3 < 0 and np.trace(self._M) >= 0):
809 | # reaching this point means, we have a serious problem, likely of
810 | # numerical nature
811 | print 'Houston, we have had a problem - check M !!!!!! \n' + \
812 | '( Trace(M) > 0, but largest eigenvalue is still negative)'
813 | raise MTError(' !! ')
814 |
815 | if trace_M == 0:
816 | # print 'pure deviatoric'
817 | if EW2 == 0:
818 | # print 'pure shear'
819 | symmetry_around_tension = 1
820 | clr = 1
821 |
822 | elif 2 * abs(EW2) == abs(EW1) or 2 * abs(EW2) == abs(EW3):
823 | # print 'pure clvd'
824 | if abs(EW1) < EW3:
825 | # print 'CLVD: symmetry around tension'
826 | symmetry_around_tension = 1
827 | clr = 1
828 | else:
829 | # print 'CLVD: symmetry around pressure'
830 | symmetry_around_tension = 0
831 | clr = -1
832 | else:
833 | # print 'mix of DC and CLVD'
834 | if abs(EW1) < EW3:
835 | # print 'symmetry around tension'
836 | symmetry_around_tension = 1
837 | clr = 1
838 | else:
839 | # print 'symmetry around pressure'
840 | symmetry_around_tension = 0
841 | clr = -1
842 |
843 | # define order of eigenvectors and values according to symmetry axis
844 | if symmetry_around_tension == 1:
845 | EWs = EW3.copy()
846 | EVs = EV3.copy()
847 | EWh = EW1.copy()
848 | EVh = EV1.copy()
849 |
850 | else:
851 | EWs = EW1.copy()
852 | EVs = EV1.copy()
853 | EWh = EW3.copy()
854 | EVh = EV3.copy()
855 |
856 | EWn = EW2
857 | EVn = EV2
858 |
859 | # build the basis system change matrix:
860 | chng_basis = np.asmatrix(np.zeros((3, 3)))
861 |
862 | # order of eigenvector's basis: (H,N,S)
863 | chng_basis[:, 0] = EVh
864 | chng_basis[:, 1] = EVn
865 | chng_basis[:, 2] = EVs
866 |
867 | # matrix for basis transformation
868 | self._rotation_matrix = chng_basis
869 |
870 | # collections of eigenvectors and eigenvalues
871 | self._eigenvectors = [EVh, EVn, EVs]
872 | self._eigenvalues = [EWh, EWn, EWs]
873 |
874 | # principal axes
875 | self._null_axis = EVn
876 | self._t_axis = EV1
877 | self._p_axis = EV3
878 |
879 | # plotting order flag - important for plot in BeachBall class
880 | self._plot_clr_order = clr
881 |
882 | # collection of the faultplanes, given in strike, dip, slip-rake
883 | self._faultplanes = self._find_faultplanes()
884 |
885 | def _find_faultplanes(self):
886 | """
887 | Sets the two angle-triples, describing the faultplanes of the
888 | Double Couple, defined by the eigenvectors P and T of the
889 | moment tensor object.
890 |
891 | Define a reference Double Couple with strike = dip =
892 | slip-rake = 0, the moment tensor object's DC is transformed
893 | (rotated) w.r.t. this orientation. The respective rotation
894 | matrix yields the first fault plane angles as the Euler
895 | angles. After flipping the first reference plane by
896 | multiplying the appropriate flip-matrix, one gets the second fault
897 | plane's geometry.
898 |
899 | All output angles are in degree
900 |
901 | (
902 | to check:
903 | using Sebastian's conventions:
904 |
905 | rotationsmatrix1 =
906 | EV Matrix of M, but in order TNP (not as here PNT!!!)
907 |
908 | reference-DC with strike, dip, rake = 0,0,0
909 | in NED - form: M = 0,0,0,0,-1,0
910 |
911 | the eigenvectors of this into a Matrix:
912 |
913 | trafo-matrix2 = EV Matrix of Reference-DC in order TNP
914 |
915 | effective Rotation matrix = (rotation_matrix1 * trafo-matrix2.T).T
916 |
917 | by checking for det <0, make sure, if Matrix must be multiplied by -1
918 |
919 | flip_matrix = 0,0,-1,0,-1,0,-1,0,0
920 |
921 | other DC orientation obtained by flip * effective Rotation matrix
922 |
923 | both matrices in matrix_2_euler
924 | )
925 | """
926 | # reference Double Couple (in NED basis)
927 | # it has strike, dip, slip-rake = 0,0,0
928 | refDC = np.matrix([[0., 0., -1.], [0., 0., 0.], [-1., 0., 0.]],
929 | dtype=np.float)
930 | refDC_evals, refDC_evecs = np.linalg.eigh(refDC)
931 |
932 | # matrix which is turning from one fault plane to the other
933 | flip_dc = np.matrix([[0., 0., -1.], [0., -1., 0.], [-1., 0., 0.]],
934 | dtype=np.float)
935 |
936 | # euler-tools need matrices of EV sorted in PNT:
937 | pnt_sorted_EV_matrix = self._rotation_matrix.copy()
938 |
939 | # resort only necessary, if abs(p) <= abs(t)
940 | # print self._plot_clr_order
941 | if self._plot_clr_order < 0:
942 | pnt_sorted_EV_matrix[:, 0] = self._rotation_matrix[:, 2]
943 | pnt_sorted_EV_matrix[:, 2] = self._rotation_matrix[:, 0]
944 |
945 | # rotation matrix, describing the rotation of the eigenvector
946 | # system of the input moment tensor into the eigenvector
947 | # system of the reference Double Couple
948 | rot_matrix_fp1 = (np.dot(pnt_sorted_EV_matrix, refDC_evecs.T)).T
949 |
950 | # check, if rotation has right orientation
951 | if np.linalg.det(rot_matrix_fp1) < 0.:
952 | rot_matrix_fp1 *= -1.
953 |
954 | # adding a rotation into the ambiguous system of the second fault plane
955 | rot_matrix_fp2 = np.dot(flip_dc, rot_matrix_fp1)
956 |
957 | fp1 = self._find_strike_dip_rake(rot_matrix_fp1)
958 | fp2 = self._find_strike_dip_rake(rot_matrix_fp2)
959 |
960 | return [fp1, fp2]
961 |
962 | def _find_strike_dip_rake(self, rotation_matrix):
963 | """
964 | Returns angles strike, dip, slip-rake in degrees, describing the fault
965 | plane.
966 | """
967 | (alpha, beta, gamma) = self._matrix_to_euler(rotation_matrix)
968 | return (beta * rad2deg, alpha * rad2deg, -gamma * rad2deg)
969 |
970 | def _cvec(self, x, y, z):
971 | """
972 | Builds a column vector (matrix type) from a 3 tuple.
973 | """
974 | return np.matrix([[x, y, z]], dtype=np.float).T
975 |
976 | def _matrix_to_euler(self, rotmat):
977 | """
978 | Returns three Euler angles alpha, beta, gamma (in radians) from a
979 | rotation matrix.
980 | """
981 | ex = self._cvec(1., 0., 0.)
982 | ez = self._cvec(0., 0., 1.)
983 | exs = rotmat.T * ex
984 | ezs = rotmat.T * ez
985 | enodes = np.cross(ez.T, ezs.T).T
986 | if np.linalg.norm(enodes) < 1e-10:
987 | enodes = exs
988 | enodess = rotmat * enodes
989 | cos_alpha = float((ez.T * ezs))
990 | if cos_alpha > 1.:
991 | cos_alpha = 1.
992 | if cos_alpha < -1.:
993 | cos_alpha = -1.
994 | alpha = np.arccos(cos_alpha)
995 | beta = np.mod(np.arctan2(enodes[1, 0], enodes[0, 0]), np.pi * 2.)
996 | gamma = np.mod(-np.arctan2(enodess[1, 0], enodess[0, 0]), np.pi * 2.)
997 | return self._unique_euler(alpha, beta, gamma)
998 |
999 | def _unique_euler(self, alpha, beta, gamma):
1000 | """
1001 | Uniquify euler angle triplet.
1002 |
1003 | Puts euler angles into ranges compatible with (dip,strike,-rake) in
1004 | seismology:
1005 |
1006 | alpha (dip) : [0, pi/2]
1007 | beta (strike) : [0, 2*pi)
1008 | gamma (-rake) : [-pi, pi)
1009 |
1010 | If alpha is near to zero, beta is replaced by beta+gamma and gamma is
1011 | set to zero, to prevent that additional ambiguity.
1012 |
1013 | If alpha is near to pi/2, beta is put into the range [0,pi).
1014 | """
1015 | alpha = np.mod(alpha, 2.0 * pi)
1016 |
1017 | if 0.5 * pi < alpha and alpha <= pi:
1018 | alpha = pi - alpha
1019 | beta = beta + pi
1020 | gamma = 2.0 * pi - gamma
1021 | elif pi < alpha and alpha <= 1.5 * pi:
1022 | alpha = alpha - pi
1023 | gamma = pi - gamma
1024 | elif 1.5 * pi < alpha and alpha <= 2.0 * pi:
1025 | alpha = 2.0 * pi - alpha
1026 | beta = beta + pi
1027 | gamma = pi + gamma
1028 |
1029 | alpha = np.mod(alpha, 2.0 * pi)
1030 | beta = np.mod(beta, 2.0 * pi)
1031 | gamma = np.mod(gamma + pi, 2.0 * pi) - pi
1032 |
1033 | # If dip is exactly 90 degrees, one is still
1034 | # free to choose between looking at the plane from either side.
1035 | # Choose to look at such that beta is in the range [0,180)
1036 |
1037 | # This should prevent some problems, when dip is close to 90 degrees:
1038 | if abs(alpha - 0.5 * pi) < 1e-10:
1039 | alpha = 0.5 * pi
1040 | if abs(beta - pi) < 1e-10:
1041 | beta = pi
1042 | if abs(beta - 2. * pi) < 1e-10:
1043 | beta = 0.
1044 | if abs(beta) < 1e-10:
1045 | beta = 0.
1046 |
1047 | if alpha == 0.5 * pi and beta >= pi:
1048 | gamma = -gamma
1049 | beta = np.mod(beta - pi, 2.0 * pi)
1050 | gamma = np.mod(gamma + pi, 2.0 * pi) - pi
1051 | assert 0. <= beta < pi
1052 | assert -pi <= gamma < pi
1053 |
1054 | if alpha < 1e-7:
1055 | beta = np.mod(beta + gamma, 2.0 * pi)
1056 | gamma = 0.
1057 |
1058 | return (alpha, beta, gamma)
1059 |
1060 | def _matrix_w_style_and_system(self, M2return, system, style):
1061 | """
1062 | Transform matrix into the given basis system 'system'.
1063 |
1064 | If the argument 'style' is set to 'fancy', a 'print' of the return
1065 | value yields a nice shell output of the matrix for better
1066 | visual control.
1067 | """
1068 |
1069 | M2return = basis_transform_matrix(M2return, 'NED', system.upper())
1070 |
1071 | if style.lower() in ['f', 'fan', 'fancy', 'y']:
1072 | return fancy_matrix(M2return)
1073 | else:
1074 | return M2return
1075 |
1076 | def _vector_w_style_and_system(self, vectors, system, style='n'):
1077 | """
1078 | Transform vector(s) into the given basis system 'system'.
1079 |
1080 | If the argument 'style' is set to 'fancy', a 'print' of the return
1081 | value yields a nice shell output of the vector(s) for better
1082 | visual control.
1083 |
1084 | 'vectors' can be either a single array, tuple, matrix or a collection
1085 | in form of a list, array or matrix.
1086 | If it's a list, each entry will be checked, if it's 3D - if not, an
1087 | exception is raised.
1088 | If it's a matrix or array with column-length 3, the columns are
1089 | interpreted as vectors, otherwise, its transposed is used.
1090 | """
1091 |
1092 | fancy = style.lower() in ['f', 'fan', 'fancy', 'y']
1093 |
1094 | lo_vectors = []
1095 |
1096 | # if list of vectors
1097 | if type(vectors) == list:
1098 | lo_vectors = vectors
1099 |
1100 | else:
1101 | assert 3 in vectors.shape
1102 |
1103 | if np.shape(vectors)[0] == 3:
1104 | for ii in np.arange(np.shape(vectors)[1]):
1105 | lo_vectors.append(vectors[:, ii])
1106 | else:
1107 | for ii in np.arange(np.shape(vectors)[0]):
1108 | lo_vectors.append(vectors[:, ii].transpose())
1109 |
1110 | lo_vecs_to_show = []
1111 | for vec in lo_vectors:
1112 |
1113 | t_vec = basis_transform_vector(vec, 'NED', system.upper())
1114 |
1115 | if fancy:
1116 | lo_vecs_to_show.append(fancy_vector(t_vec))
1117 | else:
1118 | lo_vecs_to_show.append(t_vec)
1119 |
1120 | if len(lo_vecs_to_show) == 1:
1121 | return lo_vecs_to_show[0]
1122 |
1123 | else:
1124 | if fancy:
1125 | return ''.join(lo_vecs_to_show)
1126 | else:
1127 | return lo_vecs_to_show
1128 |
1129 | def get_M(self, system='NED', style='n'):
1130 | """
1131 | Returns the moment tensor in matrix representation.
1132 |
1133 | Call with arguments to set ouput in other basis system or in fancy
1134 | style (to be viewed with 'print')
1135 | """
1136 | return self._matrix_w_style_and_system(self._M, system, style)
1137 |
1138 | def get_decomposition(self, in_system='NED', out_system='NED', style='n'):
1139 | """
1140 | Returns a tuple of the decomposition results.
1141 |
1142 | Order:
1143 | - 1 - basis of the provided input (string)
1144 | - 2 - basis of the representation (string)
1145 | - 3 - chosen decomposition type (integer)
1146 |
1147 | - 4 - full moment tensor (matrix)
1148 |
1149 | - 5 - isotropic part (matrix)
1150 | - 6 - isotropic percentage (float)
1151 | - 7 - deviatoric part (matrix)
1152 | - 8 - deviatoric percentage (float)
1153 |
1154 | - 9 - DC part (matrix)
1155 | -10 - DC percentage (float)
1156 | -11 - DC2 part (matrix)
1157 | -12 - DC2 percentage (float)
1158 | -13 - DC3 part (matrix)
1159 | -14 - DC3 percentage (float)
1160 |
1161 | -15 - CLVD part (matrix)
1162 | -16 - CLVD percentage (matrix)
1163 |
1164 | -17 - seismic moment (float)
1165 | -18 - moment magnitude (float)
1166 |
1167 | -19 - eigenvectors (3-array)
1168 | -20 - eigenvalues (list)
1169 | -21 - p-axis (3-array)
1170 | -22 - neutral axis (3-array)
1171 | -23 - t-axis (3-array)
1172 | -24 - faultplanes (list of two 3-arrays)
1173 | """
1174 | return [in_system, out_system, self.get_decomp_type(),
1175 | self.get_M(system=out_system),
1176 | self.get_iso(system=out_system), self.get_iso_percentage(),
1177 | self.get_devi(system=out_system), self.get_devi_percentage(),
1178 | self.get_DC(system=out_system), self.get_DC_percentage(),
1179 | self.get_DC2(system=out_system), self.get_DC2_percentage(),
1180 | self.get_DC3(system=out_system), self.get_DC3_percentage(),
1181 | self.get_CLVD(system=out_system), self.get_CLVD_percentage(),
1182 | self.get_moment(), self.get_mag(),
1183 | self.get_eigvecs(system=out_system),
1184 | self.get_eigvals(system=out_system),
1185 | self.get_p_axis(system=out_system),
1186 | self.get_null_axis(system=out_system),
1187 | self.get_t_axis(system=out_system),
1188 | self.get_fps()]
1189 |
1190 | def __str__(self):
1191 | """
1192 | Nice compilation of decomposition result to be viewed in the shell
1193 | (call with 'print').
1194 | """
1195 |
1196 | mexp = pow(10, np.ceil(np.log10(np.max(np.abs(self._M)))))
1197 |
1198 | m = basis_transform_matrix(self._M / mexp, 'NED', self._output_basis)
1199 |
1200 | def b(i, j):
1201 | x = self._output_basis.lower()
1202 | return x[i] + x[j]
1203 |
1204 | s = '\nScalar Moment: M0 = %g Nm (Mw = %3.1f)\n'
1205 | s += 'Moment Tensor: M%s = %6.3f, M%s = %6.3f, M%s = %6.3f,\n'
1206 | s += ' M%s = %6.3f, M%s = %6.3f, M%s = %6.3f'
1207 | s += ' [ x %g ]\n\n'
1208 | s = s % (self._seismic_moment, self._moment_magnitude,
1209 | b(0, 0), m[0, 0],
1210 | b(1, 1), m[1, 1],
1211 | b(2, 2), m[2, 2],
1212 | b(0, 1), m[0, 1],
1213 | b(0, 2), m[0, 2],
1214 | b(1, 2), m[1, 2],
1215 | mexp)
1216 |
1217 | s += self._fault_planes_as_str()
1218 | return s
1219 |
1220 | def _fault_planes_as_str(self):
1221 | """
1222 | Internal setup of a nice string, containing information about the fault
1223 | planes.
1224 | """
1225 | s = '\n'
1226 | for i, sdr in enumerate(self.get_fps()):
1227 | s += 'Fault plane %i: ' % (i + 1)
1228 | s += 'strike = %3.0f°, dip = %3.0f°, slip-rake = %4.0f°\n' % \
1229 | (sdr[0], sdr[1], sdr[2])
1230 | return s
1231 |
1232 | def get_input_system(self, style='n', **kwargs):
1233 | """
1234 | Returns the basis system of the input.
1235 | """
1236 | return self._input_basis
1237 |
1238 | def get_output_system(self, style='n', **kwargs):
1239 | """
1240 | Returns the basis system of the output.
1241 | """
1242 | return self._output_basis
1243 |
1244 | def get_decomp_type(self, style='n', **kwargs):
1245 | """
1246 | Returns the decomposition type.
1247 | """
1248 |
1249 | if style == 'y':
1250 | return MomentTensor.decomp_dict[self._decomposition_key][0]
1251 |
1252 | return self._decomposition_key
1253 |
1254 | def get_iso(self, system='NED', style='n'):
1255 | """
1256 | Returns the isotropic part of the moment tensor in matrix
1257 | representation.
1258 |
1259 | Call with arguments to set ouput in other basis system or in fancy
1260 | style (to be viewed with 'print')
1261 | """
1262 | return self._matrix_w_style_and_system(self._isotropic, system, style)
1263 |
1264 | def get_devi(self, system='NED', style='n'):
1265 | """
1266 | Returns the deviatoric part of the moment tensor in matrix
1267 | representation.
1268 |
1269 | Call with arguments to set ouput in other basis system or in fancy
1270 | style (to be viewed with 'print')
1271 | """
1272 | return self._matrix_w_style_and_system(self._deviatoric, system, style)
1273 |
1274 | def get_DC(self, system='NED', style='n'):
1275 | """
1276 | Returns the Double Couple part of the moment tensor in matrix
1277 | representation.
1278 |
1279 | Call with arguments to set ouput in other basis system or in fancy
1280 | style (to be viewed with 'print')
1281 | """
1282 | return self._matrix_w_style_and_system(self._DC, system, style)
1283 |
1284 | def get_DC2(self, system='NED', style='n'):
1285 | """
1286 | Returns the second Double Couple part of the moment tensor in matrix
1287 | representation.
1288 |
1289 | Call with arguments to set ouput in other basis system or in fancy
1290 | style (to be viewed with 'print')
1291 | """
1292 |
1293 | if self._DC2 is None:
1294 | if style == 'y':
1295 | return 'not available in this decomposition type'
1296 | else:
1297 | return None
1298 |
1299 | return self._matrix_w_style_and_system(self._DC2, system, style)
1300 |
1301 | def get_DC3(self, system='NED', style='n'):
1302 | """
1303 | Returns the third Double Couple part of the moment tensor in matrix
1304 | representation.
1305 |
1306 | Call with arguments to set ouput in other basis system or in fancy
1307 | style (to be viewed with 'print')
1308 | """
1309 |
1310 | if self._DC3 is None:
1311 | if style == 'y':
1312 | return 'not available in this decomposition type'
1313 | else:
1314 | return None
1315 |
1316 | return self._matrix_w_style_and_system(self._DC3, system, style)
1317 |
1318 | def get_CLVD(self, system='NED', style='n'):
1319 | """
1320 | Returns the CLVD part of the moment tensor in matrix representation.
1321 |
1322 | Call with arguments to set ouput in other basis system or in fancy
1323 | style (to be viewed with 'print')
1324 | """
1325 | if self._CLVD is None:
1326 | if style == 'y':
1327 | return 'not available in this decomposition type'
1328 | else:
1329 | return None
1330 |
1331 | return self._matrix_w_style_and_system(self._CLVD, system, style)
1332 |
1333 | def get_DC_percentage(self, system='NED', style='n'):
1334 | """
1335 | Returns the percentage of the DC part of the moment tensor in matrix
1336 | representation.
1337 | """
1338 |
1339 | return self._DC_percentage
1340 |
1341 | def get_CLVD_percentage(self, system='NED', style='n'):
1342 | """
1343 | Returns the percentage of the DC part of the moment tensor in matrix
1344 | representation.
1345 | """
1346 |
1347 | if self._CLVD is None:
1348 | if style == 'y':
1349 | return 'not available in this decomposition type'
1350 | else:
1351 | return None
1352 |
1353 | return int(100 - self._iso_percentage - self._DC_percentage)
1354 |
1355 | def get_DC2_percentage(self, system='NED', style='n'):
1356 | """
1357 | Returns the percentage of the second DC part of the moment tensor in
1358 | matrix representation.
1359 | """
1360 |
1361 | if self._DC2 is None:
1362 | if style == 'y':
1363 | return 'not available in this decomposition type'
1364 | else:
1365 | return None
1366 |
1367 | return self._DC2_percentage
1368 |
1369 | def get_DC3_percentage(self, system='NED', style='n'):
1370 | """
1371 | Returns the percentage of the third DC part of the moment tensor in
1372 | matrix representation.
1373 | """
1374 |
1375 | if self._DC3 is None:
1376 | if style == 'y':
1377 | return 'not available in this decomposition type'
1378 | else:
1379 | return None
1380 |
1381 | return int(100 - self._DC2_percentage - self._DC_percentage)
1382 |
1383 | def get_iso_percentage(self, system='NED', style='n'):
1384 | """
1385 | Returns the percentage of the isotropic part of the moment tensor in
1386 | matrix representation.
1387 | """
1388 | return self._iso_percentage
1389 |
1390 | def get_devi_percentage(self, system='NED', style='n'):
1391 | """
1392 | Returns the percentage of the deviatoric part of the moment tensor in
1393 | matrix representation.
1394 | """
1395 | return int(100 - self._iso_percentage)
1396 |
1397 | def get_moment(self, system='NED', style='n'):
1398 | """
1399 | Returns the seismic moment (in Nm) of the moment tensor.
1400 | """
1401 | return self._seismic_moment
1402 |
1403 | def get_mag(self, system='NED', style='n'):
1404 | """
1405 | Returns the moment magnitude M_w of the moment tensor.
1406 | """
1407 | return self._moment_magnitude
1408 |
1409 | def get_decomposition_key(self, system='NED', style='n'):
1410 | """
1411 | 10 = standard decomposition (Jost & Herrmann)
1412 | """
1413 | return self._decomposition_key
1414 |
1415 | def get_eigvals(self, system='NED', style='n', **kwargs):
1416 | """
1417 | Returns a list of the eigenvalues of the moment tensor.
1418 | """
1419 | if style == 'y':
1420 | return '%g, %g, %g' % tuple(self._eigenvalues)
1421 |
1422 | # in the order HNS:
1423 | return self._eigenvalues
1424 |
1425 | def get_eigvecs(self, system='NED', style='n'):
1426 | """
1427 | Returns the eigenvectors of the moment tensor.
1428 |
1429 | Call with arguments to set ouput in other basis system or in fancy
1430 | style (to be viewed with 'print')
1431 | """
1432 | return self._vector_w_style_and_system(self._eigenvectors, system,
1433 | style)
1434 |
1435 | def get_null_axis(self, system='NED', style='n'):
1436 | """
1437 | Returns the neutral axis of the moment tensor.
1438 |
1439 | Call with arguments to set ouput in other basis system or in fancy
1440 | style (to be viewed with 'print')
1441 | """
1442 |
1443 | return self._vector_w_style_and_system(self._null_axis, system, style)
1444 |
1445 | def get_t_axis(self, system='NED', style='n'):
1446 | """
1447 | Returns the tension axis of the moment tensor.
1448 |
1449 | Call with arguments to set ouput in other basis system or in fancy
1450 | style (to be viewed with 'print')
1451 | """
1452 | return self._vector_w_style_and_system(self._t_axis, system, style)
1453 |
1454 | def get_p_axis(self, system='NED', style='n'):
1455 | """
1456 | Returns the pressure axis of the moment tensor.
1457 |
1458 | Call with arguments to set ouput in other basis system or in fancy
1459 | style (to be viewed with 'print')
1460 | """
1461 | return self._vector_w_style_and_system(self._p_axis, system, style)
1462 |
1463 | def get_transform_matrix(self, system='NED', style='n'):
1464 | """
1465 | Returns the transformation matrix (input system to principal axis
1466 | system.
1467 |
1468 | Call with arguments to set ouput in other basis system or in fancy
1469 | style (to be viewed with 'print')
1470 | """
1471 | return self._matrix_w_style_and_system(self._rotation_matrix, system,
1472 | style)
1473 |
1474 | def get_fps(self, **kwargs):
1475 | """
1476 | Returns a list of the two faultplane 3-tuples, each showing strike,
1477 | dip, slip-rake.
1478 | """
1479 | fancy_key = kwargs.get('style', '0')
1480 | if fancy_key[0].lower() == 'y':
1481 | return self._fault_planes_as_str()
1482 | else:
1483 | return self._faultplanes
1484 |
1485 | def get_colour_order(self, **kwargs):
1486 | """
1487 | Returns the value of the plotting order (only important in BeachBall
1488 | instances).
1489 | """
1490 | return self._plot_clr_order
1491 |
1492 | MomentTensor.decomp_dict = {
1493 | 1: ('ISO + DC + CLVD',
1494 | MomentTensor._standard_decomposition),
1495 | 2: ('ISO + major DC + minor DC',
1496 | MomentTensor._decomposition_w_2DC),
1497 | 3: ('ISO + DC1 + DC2 + DC3',
1498 | MomentTensor._decomposition_w_3DC),
1499 | 4: ('ISO + strike DC + dip DC + CLVD',
1500 | MomentTensor._decomposition_w_CLVD_2DC),
1501 | }
1502 |
1503 |
1504 | def fancy_matrix(m_in):
1505 | m = m_in.copy()
1506 |
1507 | norm_factor = round(max(abs(np.array(m).flatten())), 5)
1508 |
1509 | try:
1510 | if (norm_factor < 0.1) or (norm_factor >= 10):
1511 | if not abs(norm_factor) == 0:
1512 | m = m / norm_factor
1513 | out = "\n / %5.2F %5.2F %5.2F \\\n" % \
1514 | (m[0, 0], m[0, 1], m[0, 2])
1515 | out += " | %5.2F %5.2F %5.2F | x %F\n" % \
1516 | (m[1, 0], m[1, 1], m[1, 2], norm_factor)
1517 | out += " \\ %5.2F %5.2F %5.2F /\n" % \
1518 | (m[2, 0], m[2, 1], m[2, 2])
1519 | return out
1520 | except:
1521 | pass
1522 |
1523 | return "\n / %5.2F %5.2F %5.2F \\\n" % (m[0, 0], m[0, 1], m[0, 2]) + \
1524 | " | %5.2F %5.2F %5.2F | \n" % (m[1, 0], m[1, 1], m[1, 2]) + \
1525 | " \\ %5.2F %5.2F %5.2F /\n" % (m[2, 0], m[2, 1], m[2, 2])
1526 |
1527 |
1528 | def fancy_vector(v):
1529 | """
1530 | Returns a given 3-vector or array in a cute way on the shell, if you
1531 | use 'print' on the return value.
1532 | """
1533 | return "\n / %5.2F \\\n" % (v[0]) + \
1534 | " | %5.2F |\n" % (v[1]) + \
1535 | " \\ %5.2F /\n" % (v[2])
1536 |
1537 |
1538 | class BeachBall:
1539 |
1540 | """
1541 | Class for generating a beachball projection for a provided moment tensor
1542 | object.
1543 |
1544 | Input for instance generation: MomentTensor object [,keywords dictionary]
1545 |
1546 | Output can be plots of
1547 | - the eigensystem
1548 | - the complete sphere
1549 | - the projection to a unit sphere
1550 | ... either lower (standard) or upper half
1551 |
1552 | Beside the plots, the unit sphere projection may be saved in a given file.
1553 |
1554 | Alternatively, only the file can be provided without showing anything
1555 | directly.
1556 | """
1557 | def __init__(self, MT=MomentTensor, kwargs_dict={}, npoints=360):
1558 | self.MT = MT
1559 | self._M = MT._M
1560 | self._set_standard_attributes()
1561 | self._update_attributes(kwargs_dict)
1562 |
1563 | self._plot_n_points = npoints
1564 | self._nodallines_in_NED_system()
1565 | self.arange_1 = np.arange(3 * npoints) - 1
1566 | # self._identify_faultplanes()
1567 |
1568 | def ploBB(self, kwargs, ax=None):
1569 | """
1570 | Plots the projection of the beachball onto a unit sphere.
1571 | """
1572 | self._update_attributes(kwargs)
1573 | self._setup_BB()
1574 | self._plot_US(ax=ax)
1575 |
1576 | def save_BB(self, kwargs):
1577 | """
1578 | Method for saving the 2D projection of the beachball without showing
1579 | the plot.
1580 |
1581 | :param outfile: name of outfile, addressing w.r.t. current directory
1582 | :param format: if no implicit valid format is provided within the
1583 | filename, add file format
1584 | """
1585 | self._update_attributes(kwargs)
1586 | self._setup_BB()
1587 | self._just_save_bb()
1588 |
1589 | def _just_save_bb(self):
1590 | """
1591 | Internal method for saving the beachball unit sphere plot into a given
1592 | file.
1593 |
1594 | This method tries to setup the approprite backend according to the
1595 | requested file format first. 'AGG' is used in most cases.
1596 | """
1597 | import matplotlib
1598 |
1599 | if self._plot_outfile_format == 'svg':
1600 | try:
1601 | matplotlib.use('SVG')
1602 | except:
1603 | matplotlib.use('Agg')
1604 | elif self._plot_outfile_format == 'pdf':
1605 | try:
1606 | matplotlib.use('PDF')
1607 | except:
1608 | matplotlib.use('Agg')
1609 | pass
1610 | elif self._plot_outfile_format == 'ps':
1611 | try:
1612 | matplotlib.use('PS')
1613 | except:
1614 | matplotlib.use('Agg')
1615 | pass
1616 | elif self._plot_outfile_format == 'eps':
1617 | try:
1618 | matplotlib.use('Agg')
1619 | except:
1620 | matplotlib.use('PS')
1621 | pass
1622 | elif self._plot_outfile_format == 'png':
1623 | try:
1624 | matplotlib.use('AGG')
1625 | except:
1626 | mp_out = matplotlib.use('GTKCairo')
1627 | if mp_out:
1628 | mp_out2 = matplotlib.use('Cairo')
1629 | if mp_out2:
1630 | matplotlib.use('GDK')
1631 |
1632 | # finally generating the actual plot
1633 | import pylab as P
1634 |
1635 | plotfig = self._setup_plot_US(P)
1636 |
1637 | outfile_format = self._plot_outfile_format
1638 | outfile_name = self._plot_outfile
1639 |
1640 | outfile_abs_name = os.path.realpath(
1641 | os.path.abspath(os.path.join(os.curdir, outfile_name)))
1642 |
1643 | # save plot into file
1644 | try:
1645 | plotfig.savefig(outfile_abs_name, dpi=self._plot_dpi,
1646 | transparent=True, format=outfile_format)
1647 | except:
1648 | print 'ERROR!! -- Saving of plot not possible'
1649 | return
1650 | P.close(667)
1651 | del P
1652 | del matplotlib
1653 |
1654 | def get_psxy(self, kwargs):
1655 | """
1656 | Method returning one single string, which can be piped into the psxy
1657 | method of the GMT package.
1658 |
1659 | :param GMT_type: fill/lines/EVs (select type of string),
1660 | default is 'fill'
1661 | :param GMT_scaling: scale the beachball - default radius is 1.0
1662 | :param GMT_tension_colour: tension area of BB - colour flag for -Z in
1663 | psxy, default is 1
1664 | :param GMT_pressure_colour: pressure area of BB - colour flag for -Z in
1665 | psxy, default is 0
1666 | :param GMT_show_2FPs: flag, if both faultplanes are to be shown,
1667 | default is 0
1668 | :param GMT_show_1FP: flag, if one faultplane is to be shown, default
1669 | is 1
1670 | :param GMT_FP_index: 1 or 2, default is 2
1671 | """
1672 | self._GMT_type = 'fill'
1673 | self._GMT_2fps = False
1674 | self._GMT_1fp = 0
1675 |
1676 | self._GMT_psxy_fill = None
1677 | self._GMT_psxy_nodals = None
1678 | self._GMT_psxy_EVs = None
1679 | self._GMT_scaling = 1.
1680 |
1681 | self._GMT_tension_colour = 1
1682 | self._GMT_pressure_colour = 0
1683 |
1684 | self._update_attributes(kwargs)
1685 |
1686 | self._setup_BB()
1687 |
1688 | self._set_GMT_attributes()
1689 |
1690 | if self._GMT_type == 'fill':
1691 | self._GMT_psxy_fill.seek(0)
1692 | GMT_string = self._GMT_psxy_fill.getvalue()
1693 | elif self._GMT_type == 'lines':
1694 | self._GMT_psxy_nodals.seek(0)
1695 | GMT_string = self._GMT_psxy_nodals.getvalue()
1696 | else:
1697 | GMT_string = self._GMT_psxy_EVs.getvalue()
1698 |
1699 | return GMT_string
1700 |
1701 | def _add_2_GMT_string(self, FH_string, curve, colour):
1702 | """
1703 | Writes coordinate pair list of given curve as string into temporal
1704 | file handler.
1705 | """
1706 | colour_Z = colour
1707 | wstring = '> -Z%i\n' % (colour_Z)
1708 | FH_string.write(wstring)
1709 | np.savetxt(FH_string, self._GMT_scaling * curve.transpose())
1710 |
1711 | def _set_GMT_attributes(self):
1712 | """
1713 | Set the beachball lines and nodals as strings into a file handler.
1714 | """
1715 | neg_nodalline = self._nodalline_negative_final_US
1716 | pos_nodalline = self._nodalline_positive_final_US
1717 | FP1_2_plot = self._FP1_final_US
1718 | FP2_2_plot = self._FP2_final_US
1719 | EV_2_plot = self._all_EV_2D_US[:, :2].transpose()
1720 | US = self._unit_sphere
1721 |
1722 | tension_colour = self._GMT_tension_colour
1723 | pressure_colour = self._GMT_pressure_colour
1724 |
1725 | # build strings for possible GMT-output, used by 'psxy'
1726 | GMT_string_FH = StringIO()
1727 | GMT_linestring_FH = StringIO()
1728 | GMT_EVs_FH = StringIO()
1729 |
1730 | self._add_2_GMT_string(GMT_EVs_FH, EV_2_plot, tension_colour)
1731 | GMT_EVs_FH.flush()
1732 |
1733 | if self._plot_clr_order > 0:
1734 | self._add_2_GMT_string(GMT_string_FH, US, pressure_colour)
1735 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline,
1736 | tension_colour)
1737 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline,
1738 | tension_colour)
1739 | GMT_string_FH.flush()
1740 |
1741 | if self._plot_curve_in_curve != 0:
1742 | self._add_2_GMT_string(GMT_string_FH, US, tension_colour)
1743 |
1744 | if self._plot_curve_in_curve < 1:
1745 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline,
1746 | pressure_colour)
1747 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline,
1748 | tension_colour)
1749 | GMT_string_FH.flush()
1750 | else:
1751 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline,
1752 | pressure_colour)
1753 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline,
1754 | tension_colour)
1755 | GMT_string_FH.flush()
1756 | else:
1757 | self._add_2_GMT_string(GMT_string_FH, US, tension_colour)
1758 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline,
1759 | pressure_colour)
1760 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline,
1761 | pressure_colour)
1762 | GMT_string_FH.flush()
1763 |
1764 | if self._plot_curve_in_curve != 0:
1765 | self._add_2_GMT_string(GMT_string_FH, US, pressure_colour)
1766 | if self._plot_curve_in_curve < 1:
1767 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline,
1768 | tension_colour)
1769 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline,
1770 | pressure_colour)
1771 | GMT_string_FH.flush()
1772 | else:
1773 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline,
1774 | tension_colour)
1775 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline,
1776 | pressure_colour)
1777 |
1778 | GMT_string_FH.flush()
1779 |
1780 | # set all nodallines and faultplanes for plotting:
1781 | self._add_2_GMT_string(GMT_linestring_FH, neg_nodalline,
1782 | tension_colour)
1783 | self._add_2_GMT_string(GMT_linestring_FH, pos_nodalline,
1784 | tension_colour)
1785 |
1786 | if self._GMT_2fps:
1787 | self._add_2_GMT_string(GMT_linestring_FH, FP1_2_plot,
1788 | tension_colour)
1789 | self._add_2_GMT_string(GMT_linestring_FH, FP2_2_plot,
1790 | tension_colour)
1791 |
1792 | elif self._GMT_1fp:
1793 | if not int(self._GMT_1fp) in [1, 2]:
1794 | print 'no fault plane specified for being plotted...continue',
1795 | print 'without fault plane(s)'
1796 | pass
1797 | else:
1798 | if int(self._GMT_1fp) == 1:
1799 | self._add_2_GMT_string(GMT_linestring_FH, FP1_2_plot,
1800 | tension_colour)
1801 | else:
1802 | self._add_2_GMT_string(GMT_linestring_FH, FP2_2_plot,
1803 | tension_colour)
1804 |
1805 | self._add_2_GMT_string(GMT_linestring_FH, US, tension_colour)
1806 |
1807 | GMT_linestring_FH.flush()
1808 |
1809 | setattr(self, '_GMT_psxy_nodals', GMT_linestring_FH)
1810 | setattr(self, '_GMT_psxy_fill', GMT_string_FH)
1811 | setattr(self, '_GMT_psxy_EVs', GMT_EVs_FH)
1812 |
1813 | def get_MT(self):
1814 | """
1815 | Returns the original moment tensor object, handed over to the class at
1816 | generating this instance.
1817 | """
1818 | return self.MT
1819 |
1820 | def full_sphere_plot(self, kwargs):
1821 | """
1822 | Plot of the full beachball, projected on a circle with a radius 2.
1823 | """
1824 | self._update_attributes(kwargs)
1825 | self._setup_BB()
1826 | self._aux_plot()
1827 |
1828 | def _aux_plot(self):
1829 | """
1830 | Generates the final plot of the total sphere (according to the chosen
1831 | 2D-projection.
1832 | """
1833 | from matplotlib import interactive
1834 | import pylab as P
1835 |
1836 | P.close('all')
1837 | plotfig = P.figure(665, figsize=(self._plot_aux_plot_size,
1838 | self._plot_aux_plot_size))
1839 |
1840 | plotfig.subplots_adjust(left=0, bottom=0, right=1, top=1)
1841 | ax = plotfig.add_subplot(111, aspect='equal')
1842 | # P.axis([-1.1,1.1,-1.1,1.1],'equal')
1843 | ax.axison = False
1844 |
1845 | EV_2_plot = getattr(self, '_all_EV' + '_final')
1846 | BV_2_plot = getattr(self, '_all_BV' + '_final').transpose()
1847 | curve_pos_2_plot = getattr(self, '_nodalline_positive' + '_final')
1848 | curve_neg_2_plot = getattr(self, '_nodalline_negative' + '_final')
1849 | FP1_2_plot = getattr(self, '_FP1' + '_final')
1850 | FP2_2_plot = getattr(self, '_FP2' + '_final')
1851 |
1852 | tension_colour = self._plot_tension_colour
1853 | pressure_colour = self._plot_pressure_colour
1854 |
1855 | if self._plot_clr_order > 0:
1856 | if self._plot_fill_flag:
1857 |
1858 | alpha = self._plot_fill_alpha * self._plot_total_alpha
1859 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :],
1860 | fc=pressure_colour, alpha=alpha)
1861 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :],
1862 | fc=tension_colour, alpha=alpha)
1863 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :],
1864 | fc=tension_colour, alpha=alpha)
1865 |
1866 | if self._plot_curve_in_curve != 0:
1867 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :],
1868 | fc=tension_colour, alpha=alpha)
1869 | if self._plot_curve_in_curve < 1:
1870 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :],
1871 | fc=pressure_colour, alpha=alpha)
1872 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :],
1873 | fc=tension_colour, alpha=alpha)
1874 | else:
1875 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :],
1876 | fc=pressure_colour, alpha=alpha)
1877 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :],
1878 | fc=tension_colour, alpha=alpha)
1879 |
1880 | if self._plot_show_princ_axes:
1881 | alpha = self._plot_princ_axes_alpha * self._plot_total_alpha
1882 | ax.plot([EV_2_plot[0, 0]], [EV_2_plot[1, 0]], 'm^',
1883 | ms=self._plot_princ_axes_symsize,
1884 | lw=self._plot_princ_axes_lw, alpha=alpha)
1885 | ax.plot([EV_2_plot[0, 3]], [EV_2_plot[1, 3]], 'mv',
1886 | ms=self._plot_princ_axes_symsize,
1887 | lw=self._plot_princ_axes_lw, alpha=alpha)
1888 | ax.plot([EV_2_plot[0, 1]], [EV_2_plot[1, 1]], 'b^',
1889 | ms=self._plot_princ_axes_symsize,
1890 | lw=self._plot_princ_axes_lw, alpha=alpha)
1891 | ax.plot([EV_2_plot[0, 4]], [EV_2_plot[1, 4]], 'bv',
1892 | ms=self._plot_princ_axes_symsize,
1893 | lw=self._plot_princ_axes_lw, alpha=alpha)
1894 | ax.plot([EV_2_plot[0, 2]], [EV_2_plot[1, 2]], 'g^',
1895 | ms=self._plot_princ_axes_symsize,
1896 | lw=self._plot_princ_axes_lw, alpha=alpha)
1897 | ax.plot([EV_2_plot[0, 5]], [EV_2_plot[1, 5]], 'gv',
1898 | ms=self._plot_princ_axes_symsize,
1899 | lw=self._plot_princ_axes_lw, alpha=alpha)
1900 | else:
1901 | if self._plot_fill_flag:
1902 | alpha = self._plot_fill_alpha * self._plot_total_alpha
1903 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :],
1904 | fc=tension_colour, alpha=alpha)
1905 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :],
1906 | fc=pressure_colour, alpha=alpha)
1907 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :],
1908 | fc=pressure_colour, alpha=alpha)
1909 |
1910 | if self._plot_curve_in_curve != 0:
1911 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :],
1912 | fc=pressure_colour, alpha=alpha)
1913 | if self._plot_curve_in_curve < 0:
1914 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :],
1915 | fc=tension_colour, alpha=alpha)
1916 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :],
1917 | fc=pressure_colour, alpha=alpha)
1918 | pass
1919 | else:
1920 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :],
1921 | fc=tension_colour, alpha=alpha)
1922 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :],
1923 | fc=pressure_colour, alpha=alpha)
1924 | pass
1925 |
1926 | if self._plot_show_princ_axes:
1927 | alpha = self._plot_princ_axes_alpha * self._plot_total_alpha
1928 | ax.plot([EV_2_plot[0, 0]], [EV_2_plot[1, 0]], 'g^',
1929 | ms=self._plot_princ_axes_symsize,
1930 | lw=self._plot_princ_axes_lw, alpha=alpha)
1931 | ax.plot([EV_2_plot[0, 3]], [EV_2_plot[1, 3]], 'gv',
1932 | ms=self._plot_princ_axes_symsize,
1933 | lw=self._plot_princ_axes_lw, alpha=alpha)
1934 | ax.plot([EV_2_plot[0, 1]], [EV_2_plot[1, 1]], 'b^',
1935 | ms=self._plot_princ_axes_symsize,
1936 | lw=self._plot_princ_axes_lw, alpha=alpha)
1937 | ax.plot([EV_2_plot[0, 4]], [EV_2_plot[1, 4]], 'bv',
1938 | ms=self._plot_princ_axes_symsize,
1939 | lw=self._plot_princ_axes_lw, alpha=alpha)
1940 | ax.plot([EV_2_plot[0, 2]], [EV_2_plot[1, 2]], 'm^',
1941 | ms=self._plot_princ_axes_symsize,
1942 | lw=self._plot_princ_axes_lw, alpha=alpha)
1943 | ax.plot([EV_2_plot[0, 5]], [EV_2_plot[1, 5]], 'mv',
1944 | ms=self._plot_princ_axes_symsize,
1945 | lw=self._plot_princ_axes_lw, alpha=alpha)
1946 |
1947 | self._plot_nodalline_colour = 'y'
1948 |
1949 | ax.plot(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 'o',
1950 | c=self._plot_nodalline_colour, lw=self._plot_nodalline_width,
1951 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha,
1952 | ms=3)
1953 |
1954 | self._plot_nodalline_colour = 'b'
1955 |
1956 | ax.plot(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 'D',
1957 | c=self._plot_nodalline_colour, lw=self._plot_nodalline_width,
1958 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha,
1959 | ms=3)
1960 |
1961 | if self._plot_show_1faultplane:
1962 | if self._plot_show_FP_index == 1:
1963 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :], '+',
1964 | c=self._plot_faultplane_colour,
1965 | lw=self._plot_faultplane_width,
1966 | alpha=self._plot_faultplane_alpha *
1967 | self._plot_total_alpha, ms=5)
1968 | elif self._plot_show_FP_index == 2:
1969 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :], '+',
1970 | c=self._plot_faultplane_colour,
1971 | lw=self._plot_faultplane_width,
1972 | alpha=self._plot_faultplane_alpha *
1973 | self._plot_total_alpha, ms=5)
1974 |
1975 | elif self._plot_show_faultplanes:
1976 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :], '+',
1977 | c=self._plot_faultplane_colour,
1978 | lw=self._plot_faultplane_width,
1979 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha,
1980 | ms=4)
1981 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :], '+',
1982 | c=self._plot_faultplane_colour,
1983 | lw=self._plot_faultplane_width,
1984 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha,
1985 | ms=4)
1986 | else:
1987 | pass
1988 |
1989 | # if isotropic part shall be displayed, fill the circle completely with
1990 | # the appropriate colour
1991 | if self._pure_isotropic:
1992 | if abs(np.trace(self._M)) > epsilon:
1993 | if self._plot_clr_order < 0:
1994 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :],
1995 | fc=tension_colour, alpha=1, zorder=100)
1996 | else:
1997 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :],
1998 | fc=pressure_colour, alpha=1, zorder=100)
1999 |
2000 | # plot NED basis vectors
2001 | if self._plot_show_basis_axes:
2002 | plot_size_in_points = self._plot_size * 2.54 * 72
2003 | points_per_unit = plot_size_in_points / 2.
2004 |
2005 | fontsize = plot_size_in_points / 66.
2006 | symsize = plot_size_in_points / 77.
2007 |
2008 | direction_letters = list('NSEWDU')
2009 | for idx, val in enumerate(BV_2_plot):
2010 | x_coord = val[0]
2011 | y_coord = val[1]
2012 | np_letter = direction_letters[idx]
2013 |
2014 | rot_angle = -np.arctan2(y_coord, x_coord) + pi / 2.
2015 | original_rho = np.sqrt(x_coord ** 2 + y_coord ** 2)
2016 |
2017 | marker_x = (original_rho - (3 * symsize / points_per_unit)) * \
2018 | np.sin(rot_angle)
2019 | marker_y = (original_rho - (3 * symsize / points_per_unit)) * \
2020 | np.cos(rot_angle)
2021 | annot_x = (original_rho - (8.5 * fontsize / points_per_unit)) \
2022 | * np.sin(rot_angle)
2023 | annot_y = (original_rho - (8.5 * fontsize / points_per_unit)) \
2024 | * np.cos(rot_angle)
2025 |
2026 | ax.text(annot_x, annot_y, np_letter,
2027 | horizontalalignment='center', size=fontsize,
2028 | weight='bold', verticalalignment='center',
2029 | bbox=dict(edgecolor='white', facecolor='white',
2030 | alpha=1))
2031 |
2032 | if original_rho > epsilon:
2033 | ax.scatter([marker_x], [marker_y],
2034 | marker=(3, 0, rot_angle), s=symsize ** 2, c='k',
2035 | facecolor='k', zorder=300)
2036 | else:
2037 | ax.scatter([x_coord], [y_coord], marker=(4, 1, rot_angle),
2038 | s=symsize ** 2, c='k', facecolor='k',
2039 | zorder=300)
2040 |
2041 | # plot both circle lines (radius 1 and 2)
2042 | ax.plot(self._unit_sphere[0, :], self._unit_sphere[1, :],
2043 | c=self._plot_outerline_colour, lw=self._plot_outerline_width,
2044 | alpha=self._plot_outerline_alpha * self._plot_total_alpha)
2045 | ax.plot(self._outer_circle[0, :], self._outer_circle[1, :],
2046 | c=self._plot_outerline_colour, lw=self._plot_outerline_width,
2047 | alpha=self._plot_outerline_alpha * self._plot_total_alpha)
2048 |
2049 | # dummy points for setting plot plot size more accurately
2050 | ax.plot([0, 2.1, 0, -2.1], [2.1, 0, -2.1, 0], ',', alpha=0.)
2051 |
2052 | ax.autoscale_view(tight=True, scalex=True, scaley=True)
2053 | interactive(True)
2054 |
2055 | if self._plot_save_plot:
2056 | try:
2057 | plotfig.savefig(self._plot_outfile + '.' +
2058 | self._plot_outfile_format, dpi=self._plot_dpi,
2059 | transparent=True,
2060 | format=self._plot_outfile_format)
2061 | except:
2062 | print 'saving of plot not possible'
2063 |
2064 | P.show()
2065 |
2066 | def pa_plot(self, kwargs):
2067 | """
2068 | Plot of the solution in the principal axes system.
2069 | """
2070 | import pylab as P
2071 |
2072 | self._update_attributes(kwargs)
2073 |
2074 | r_hor = self._r_hor_for_pa_plot
2075 | r_hor_FP = self._r_hor_FP_for_pa_plot
2076 |
2077 | P.rc('grid', color='#316931', linewidth=0.5, linestyle='-.')
2078 | P.rc('xtick', labelsize=12)
2079 | P.rc('ytick', labelsize=10)
2080 |
2081 | width, height = P.rcParams['figure.figsize']
2082 | size = min(width, height)
2083 |
2084 | fig = P.figure(34, figsize=(size, size))
2085 | P.clf()
2086 | ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True, axisbg='#d5de9c')
2087 |
2088 | r_steps = [0.000001]
2089 | for i in (np.arange(4) + 1) * 0.2:
2090 | r_steps.append(i)
2091 | r_labels = ['S']
2092 | for ii in xrange(len(r_steps)):
2093 | if (ii + 1) % 2 == 0:
2094 | r_labels.append(str(r_steps[ii]))
2095 | else:
2096 | r_labels.append(' ')
2097 |
2098 | t_angles = np.arange(0., 360., 90)
2099 | t_labels = [' N ', ' H ', ' - N', ' - H']
2100 |
2101 | P.thetagrids(t_angles, labels=t_labels)
2102 |
2103 | ax.plot(self._phi_curve, r_hor, color='r', lw=3)
2104 | ax.plot(self._phi_curve, r_hor_FP, color='b', lw=1.5)
2105 | ax.set_rmax(1.0)
2106 | P.grid(True)
2107 |
2108 | P.rgrids((r_steps), labels=r_labels)
2109 |
2110 | ax.set_title("beachball in eigenvector system", fontsize=15)
2111 |
2112 | if self._plot_save_plot:
2113 | try:
2114 | fig.savefig(self._plot_outfile + '.' +
2115 | self._plot_outfile_format, dpi=self._plot_dpi,
2116 | transparent=True,
2117 | format=self._plot_outfile_format)
2118 | except:
2119 | print 'saving of plot not possible'
2120 | P.show()
2121 |
2122 | def _set_standard_attributes(self):
2123 | """
2124 | Sets default values of mandatory arguments.
2125 | """
2126 | # plot basis system and view point:
2127 | self._plot_basis = 'NED'
2128 | self._plot_projection = 'stereo'
2129 | self._plot_viewpoint = [0., 0., 0.]
2130 | self._plot_basis_change = None
2131 |
2132 | # flag, if upper hemisphere is seen instead
2133 | self._plot_show_upper_hemis = False
2134 |
2135 | # flag, if isotropic part shall be considered
2136 | self._plot_isotropic_part = False
2137 | self._pure_isotropic = False
2138 |
2139 | # number of minimum points per line and full circle (number/360 is
2140 | # minimum of points per degree at rounded lines)
2141 | self._plot_n_points = 360
2142 |
2143 | # nodal line of pressure and tension regimes:
2144 | self._plot_nodalline_width = 2
2145 | self._plot_nodalline_colour = 'k'
2146 | self._plot_nodalline_alpha = 1.
2147 |
2148 | # outer circle line
2149 | self._plot_outerline_width = 2
2150 | self._plot_outerline_colour = 'k'
2151 | self._plot_outerline_alpha = 1.
2152 |
2153 | # faultplane(s)
2154 | self._plot_faultplane_width = 4
2155 | self._plot_faultplane_colour = 'b'
2156 | self._plot_faultplane_alpha = 1.
2157 |
2158 | self._plot_show_faultplanes = False
2159 | self._plot_show_1faultplane = False
2160 | self._plot_show_FP_index = 1
2161 |
2162 | # principal axes:
2163 | self._plot_show_princ_axes = False
2164 | self._plot_princ_axes_symsize = 10
2165 | self._plot_princ_axes_lw = 3
2166 | self._plot_princ_axes_alpha = 0.5
2167 |
2168 | # NED basis:
2169 | self._plot_show_basis_axes = False
2170 |
2171 | # filling of the area:
2172 | self._plot_clr_order = self.MT.get_colour_order()
2173 | self._plot_curve_in_curve = 0
2174 | self._plot_fill_flag = True
2175 | self._plot_tension_colour = 'r'
2176 | self._plot_pressure_colour = 'w'
2177 | self._plot_fill_alpha = 1.
2178 |
2179 | # general plot options
2180 | self._plot_size = 5
2181 | self._plot_aux_plot_size = 5
2182 | self._plot_dpi = 200
2183 |
2184 | self._plot_total_alpha = 1.
2185 |
2186 | # possibility to add external data (e.g. measured polariations)
2187 | self._plot_external_data = False
2188 | self._external_data = None
2189 |
2190 | # if, howto, whereto save the plot
2191 | self._plot_save_plot = False
2192 | self._plot_outfile = './BB_plot_example'
2193 | self._plot_outfile_format = 'svg'
2194 |
2195 | def _update_attributes(self, kwargs):
2196 | """
2197 | Makes an internal update of the object's attributes with the
2198 | provided list of keyword arguments.
2199 |
2200 | If the keyword (extended by a leading _ ) is in the dict of
2201 | the object, the value is updated. Otherwise, the keyword is
2202 | ignored.
2203 | """
2204 | for key in kwargs.keys():
2205 | if key[0] == '_':
2206 | kw = key[1:]
2207 | else:
2208 | kw = key
2209 | if '_' + kw in dir(self):
2210 | setattr(self, '_' + kw, kwargs[key])
2211 |
2212 | if kwargs.get('plot_only_lines', False):
2213 | setattr(self, '_plot_fill_flag', False)
2214 |
2215 | def _setup_BB(self, unit_circle=True):
2216 | """
2217 | Setup of the beachball, when a plotting method is evoked.
2218 |
2219 | Contains all the technical stuff for generating the final view of the
2220 | beachball:
2221 |
2222 | - Finding a rotation matrix, describing the given viewpoint onto the
2223 | beachball projection
2224 | - Rotating all elements (lines, points) w.r.t. the given viewpoint
2225 | - Projecting the 3D sphere into the 2D plane
2226 | - Building circle lines in radius r=1 and r=2
2227 | - Correct the order of line points, yielding a consecutive set of
2228 | points for drawing lines
2229 | - Smoothing of all curves, avoiding nasty sectioning connection lines
2230 | - Checking, if the two nodalline curves are laying completely within
2231 | each other ( cahnges plotting order of overlay plot construction)
2232 | - Projection of final smooth solution onto the standard unit sphere
2233 | """
2234 | self._find_basis_change_2_new_viewpoint()
2235 | self._rotate_all_objects_2_new_view()
2236 | self._vertical_2D_projection()
2237 |
2238 | if unit_circle:
2239 | self._build_circles()
2240 |
2241 | if not self.MT._iso_percentage == 100:
2242 | self._correct_curves()
2243 | self._smooth_curves()
2244 | self._check_curve_in_curve()
2245 |
2246 | self._projection_2_unit_sphere()
2247 |
2248 | if self.MT._iso_percentage == 100:
2249 | if np.trace(self.MT.get_M()) < 0:
2250 | self._plot_clr_order = 1
2251 | else:
2252 | self._plot_clr_order = -1
2253 |
2254 | def _correct_curves(self):
2255 | """
2256 | Correcting potentially wrong curves.
2257 |
2258 | Checks, if the order of the given coordinates of the lines must be
2259 | re-arranged, allowing for an automatical line plotting.
2260 | """
2261 | list_of_curves_2_correct = ['nodalline_negative', 'nodalline_positive',
2262 | 'FP1', 'FP2']
2263 | n_curve_points = self._plot_n_points
2264 |
2265 | for obj in list_of_curves_2_correct:
2266 | obj2cor_name = '_' + obj + '_2D'
2267 | obj2cor = getattr(self, obj2cor_name)
2268 |
2269 | obj2cor_in_right_order = self._sort_curve_points(obj2cor)
2270 |
2271 | # logger.debug( 'curve: ', str(obj))
2272 | # check, if curve closed !!!!!!
2273 | start_r = np.sqrt(obj2cor_in_right_order[0, 0] ** 2 +
2274 | obj2cor_in_right_order[1, 0] ** 2)
2275 | r_last_point = np.sqrt(obj2cor_in_right_order[0, -1] ** 2 +
2276 | obj2cor_in_right_order[1, -1] ** 2)
2277 | dist_last_first_point = \
2278 | np.sqrt((obj2cor_in_right_order[0, -1] -
2279 | obj2cor_in_right_order[0, 0]) ** 2 +
2280 | (obj2cor_in_right_order[1, -1] -
2281 | obj2cor_in_right_order[1, 0]) ** 2)
2282 |
2283 | # check, if distance between last and first point is smaller than
2284 | # the distance between last point and the edge (at radius=2)
2285 | if dist_last_first_point > (2 - r_last_point):
2286 | # add points on edge to polygon, if it is an open curve
2287 | # logger.debug( str(obj)+' not closed - closing over edge... ')
2288 | phi_end = np.arctan2(obj2cor_in_right_order[0, -1],
2289 | obj2cor_in_right_order[1, -1]) % (2 * pi)
2290 | R_end = r_last_point
2291 | phi_start = np.arctan2(obj2cor_in_right_order[0, 0],
2292 | obj2cor_in_right_order[1, 0]) % (2 * pi)
2293 | R_start = start_r
2294 |
2295 | # add one point on the edge every fraction of degree given by
2296 | # input parameter, increase the radius linearily
2297 | phi_end_larger = np.sign(phi_end - phi_start)
2298 | angle_smaller_pi = np.sign(pi - np.abs(phi_end - phi_start))
2299 |
2300 | if phi_end_larger * angle_smaller_pi > 0:
2301 | go_ccw = True
2302 | openangle = (phi_end - phi_start) % (2 * pi)
2303 | else:
2304 | go_ccw = False
2305 | openangle = (phi_start - phi_end) % (2 * pi)
2306 |
2307 | radius_interval = R_start - R_end # closing from end to start
2308 |
2309 | n_edgepoints = int(openangle * rad2deg *
2310 | n_curve_points / 360.) - 1
2311 | # logger.debug( 'open angle %.2f degrees - filling with %i
2312 | # points on the edge\n'%(openangle/pi*180,n_edgepoints))
2313 | if go_ccw:
2314 | obj2cor_in_right_order = \
2315 | list(obj2cor_in_right_order.transpose())
2316 | for kk in xrange(n_edgepoints + 1):
2317 | current_phi = phi_end - kk * openangle / \
2318 | (n_edgepoints + 1)
2319 | current_radius = R_end + kk * radius_interval / \
2320 | (n_edgepoints + 1)
2321 | temp = [current_radius * math.sin(current_phi),
2322 | current_radius * np.cos(current_phi)]
2323 | obj2cor_in_right_order.append(temp)
2324 | obj2cor_in_right_order = \
2325 | np.array(obj2cor_in_right_order).transpose()
2326 | else:
2327 | obj2cor_in_right_order = \
2328 | list(obj2cor_in_right_order.transpose())
2329 | for kk in xrange(n_edgepoints + 1):
2330 | current_phi = phi_end + kk * openangle / \
2331 | (n_edgepoints + 1)
2332 | current_radius = R_end + kk * radius_interval / \
2333 | (n_edgepoints + 1)
2334 | temp = [current_radius * math.sin(current_phi),
2335 | current_radius * np.cos(current_phi)]
2336 | obj2cor_in_right_order.append(temp)
2337 | obj2cor_in_right_order = \
2338 | np.array(obj2cor_in_right_order).transpose()
2339 | setattr(self, '_' + obj + '_in_order', obj2cor_in_right_order)
2340 | return 1
2341 |
2342 | def _nodallines_in_NED_system(self):
2343 | """
2344 | The two nodal lines between the areas on a beachball are given by the
2345 | points, where tan²(alpha) = (-EWs/(EWN*cos(phi)**2 + EWh*sin(phi)**2))
2346 | is fulfilled.
2347 |
2348 | This solution is gained in the principal axes system and then expressed
2349 | in terms of the NED basis system
2350 |
2351 | output:
2352 | - set of points, building the first nodal line, coordinates in the
2353 | input basis system (standard NED)
2354 | - set of points, building the second nodal line, coordinates in the
2355 | input basis system (standard NED)
2356 | - array with 6 points, describing positive and negative part of 3
2357 | principal axes
2358 | - array with partition of full circle (angle values in degrees)
2359 | fraction is given by parametre n_curve_points
2360 | """
2361 | # build the nodallines of positive/negative areas in the principal axes
2362 | # system
2363 | n_curve_points = self._plot_n_points
2364 |
2365 | # phi is the angle between neutral axis and horizontal projection
2366 | # of the curve point to the surface, spanned by H- and
2367 | # N-axis. Running mathematically negative (clockwise) around the
2368 | # SIGMA-axis. Stepsize is given by the parametre for number of
2369 | # curve points
2370 | phi = (np.arange(n_curve_points) / float(n_curve_points) +
2371 | 1. / n_curve_points) * 2 * pi
2372 | self._phi_curve = phi
2373 |
2374 | # analytical/geometrical solution for separatrix curve - alpha is
2375 | # opening angle between principal axis SIGMA and point of curve. (alpha
2376 | # is 0, if curve lies directly on the SIGMA axis)
2377 |
2378 | # CASE: including isotropic part
2379 | # sigma axis flippes, if EWn flippes sign
2380 |
2381 | EWh_devi = self.MT.get_eigvals()[0] - 1. / 3 * np.trace(self._M)
2382 | EWn_devi = self.MT.get_eigvals()[1] - 1. / 3 * np.trace(self._M)
2383 | EWs_devi = self.MT.get_eigvals()[2] - 1. / 3 * np.trace(self._M)
2384 |
2385 | if not self._plot_isotropic_part:
2386 | EWh = EWh_devi
2387 | EWn = EWn_devi
2388 | EWs = EWs_devi
2389 | else:
2390 | EWh_tmp = self.MT.get_eigvals()[0]
2391 | EWn_tmp = self.MT.get_eigvals()[1]
2392 | EWs_tmp = self.MT.get_eigvals()[2]
2393 |
2394 | trace_m = np.sum(self.MT.get_eigvals())
2395 | EWh = EWh_tmp.copy()
2396 | EWs = EWs_tmp.copy()
2397 |
2398 | if trace_m != 0:
2399 | if (self._plot_clr_order > 0 and EWn_tmp >= 0 and
2400 | abs(EWs_tmp) > abs(EWh_tmp)) or \
2401 | (self._plot_clr_order < 0 and
2402 | EWn_tmp <= 0 and abs(EWs_tmp) > abs(EWh_tmp)):
2403 |
2404 | EWs = EWh_tmp.copy()
2405 | EWh = EWs_tmp.copy()
2406 | print 'changed order!!\n'
2407 | EVs_tmp = self.MT._rotation_matrix[:, 2].copy()
2408 | EVh_tmp = self.MT._rotation_matrix[:, 0].copy()
2409 |
2410 | self.MT._rotation_matrix[:, 0] = EVs_tmp
2411 | self.MT._rotation_matrix[:, 2] = EVh_tmp
2412 | self._plot_clr_order *= -1
2413 |
2414 | EWn = EWn_tmp.copy()
2415 |
2416 | if abs(EWn) < epsilon:
2417 | EWn = 0
2418 | norm_factor = max(np.abs([EWh, EWn, EWs]))
2419 |
2420 | [EWh, EWn, EWs] = [xx / norm_factor for xx in [EWh, EWn, EWs]]
2421 |
2422 | RHS = -EWs / (EWn * np.cos(phi) ** 2 + EWh * np.sin(phi) ** 2)
2423 |
2424 | if np.all([np.sign(xx) >= 0 for xx in RHS]):
2425 | alpha = np.arctan(np.sqrt(RHS)) * rad2deg
2426 | else:
2427 | alpha = phi.copy()
2428 | alpha[:] = 90
2429 | self._pure_isotropic = 1
2430 |
2431 | # fault planes:
2432 | RHS_FP = 1. / (np.sin(phi) ** 2)
2433 | alpha_FP = np.arctan(np.sqrt(RHS_FP)) * rad2deg
2434 |
2435 | # horizontal coordinates of curves
2436 | r_hor = np.sin(alpha / rad2deg)
2437 | r_hor_FP = np.sin(alpha_FP / rad2deg)
2438 |
2439 | self._r_hor_for_pa_plot = r_hor
2440 | self._r_hor_FP_for_pa_plot = r_hor_FP
2441 |
2442 | H_values = np.sin(phi) * r_hor
2443 | N_values = np.cos(phi) * r_hor
2444 | H_values_FP = np.sin(phi) * r_hor_FP
2445 | N_values_FP = np.cos(phi) * r_hor_FP
2446 |
2447 | # set vertical value of curve point coordinates - two symmetric curves
2448 | # exist
2449 | S_values_positive = np.cos(alpha / rad2deg)
2450 | S_values_negative = -np.cos(alpha / rad2deg)
2451 | S_values_positive_FP = np.cos(alpha_FP / rad2deg)
2452 | S_values_negative_FP = -np.cos(alpha_FP / rad2deg)
2453 |
2454 | # change basis back to original input reference system
2455 | chng_basis = self.MT._rotation_matrix
2456 |
2457 | line_tuple_pos = np.zeros((3, n_curve_points))
2458 | line_tuple_neg = np.zeros((3, n_curve_points))
2459 |
2460 | for ii in xrange(n_curve_points):
2461 | pos_vec_in_EV_basis = np.array([H_values[ii], N_values[ii],
2462 | S_values_positive[ii]]).transpose()
2463 | neg_vec_in_EV_basis = np.array([H_values[ii], N_values[ii],
2464 | S_values_negative[ii]]).transpose()
2465 | line_tuple_pos[:, ii] = np.dot(chng_basis, pos_vec_in_EV_basis)
2466 | line_tuple_neg[:, ii] = np.dot(chng_basis, neg_vec_in_EV_basis)
2467 |
2468 | EVh = self.MT.get_eigvecs()[0]
2469 | EVn = self.MT.get_eigvecs()[1]
2470 | EVs = self.MT.get_eigvecs()[2]
2471 |
2472 | all_EV = np.zeros((3, 6))
2473 |
2474 | all_EV[:, 0] = EVh.transpose()
2475 | all_EV[:, 1] = EVn.transpose()
2476 | all_EV[:, 2] = EVs.transpose()
2477 | all_EV[:, 3] = -EVh.transpose()
2478 | all_EV[:, 4] = -EVn.transpose()
2479 | all_EV[:, 5] = -EVs.transpose()
2480 |
2481 | # basis vectors:
2482 | all_BV = np.zeros((3, 6))
2483 | all_BV[:, 0] = np.array((1, 0, 0))
2484 | all_BV[:, 1] = np.array((-1, 0, 0))
2485 | all_BV[:, 2] = np.array((0, 1, 0))
2486 | all_BV[:, 3] = np.array((0, -1, 0))
2487 | all_BV[:, 4] = np.array((0, 0, 1))
2488 | all_BV[:, 5] = np.array((0, 0, -1))
2489 |
2490 | # re-sort the two 90 degree nodal lines to 2 fault planes - cut each at
2491 | # halves and merge pairs
2492 | # additionally change basis system to NED reference system
2493 |
2494 | midpoint_idx = int(n_curve_points / 2.)
2495 |
2496 | FP1 = np.zeros((3, n_curve_points))
2497 | FP2 = np.zeros((3, n_curve_points))
2498 |
2499 | for ii in xrange(midpoint_idx):
2500 | FP1_vec = np.array([H_values_FP[ii], N_values_FP[ii],
2501 | S_values_positive_FP[ii]]).transpose()
2502 | FP2_vec = np.array([H_values_FP[ii], N_values_FP[ii],
2503 | S_values_negative_FP[ii]]).transpose()
2504 | FP1[:, ii] = np.dot(chng_basis, FP1_vec)
2505 | FP2[:, ii] = np.dot(chng_basis, FP2_vec)
2506 |
2507 | for jj in xrange(midpoint_idx):
2508 | ii = n_curve_points - jj - 1
2509 |
2510 | FP1_vec = np.array([H_values_FP[ii], N_values_FP[ii],
2511 | S_values_negative_FP[ii]]).transpose()
2512 | FP2_vec = np.array([H_values_FP[ii], N_values_FP[ii],
2513 | S_values_positive_FP[ii]]).transpose()
2514 | FP1[:, ii] = np.dot(chng_basis, FP1_vec)
2515 | FP2[:, ii] = np.dot(chng_basis, FP2_vec)
2516 |
2517 | # identify with faultplane index, gotten from 'get_fps':
2518 | self._FP1 = FP1
2519 | self._FP2 = FP2
2520 |
2521 | self._all_EV = all_EV
2522 | self._all_BV = all_BV
2523 | self._nodalline_negative = line_tuple_neg
2524 | self._nodalline_positive = line_tuple_pos
2525 |
2526 | def _identify_faultplanes(self):
2527 | """
2528 | See, if the 2 faultplanes, given as attribute of the moment
2529 | tensor object, handed to this instance, are consistent with
2530 | the faultplane lines, obtained from the basis solution. If
2531 | not, interchange the indices of the newly found ones.
2532 | """
2533 | # TODO !!!!!!
2534 | pass
2535 |
2536 | def _find_basis_change_2_new_viewpoint(self):
2537 | """
2538 | Finding the Eulerian angles, if you want to rotate an object.
2539 |
2540 | Your original view point is the position (0,0,0). Input are the
2541 | coordinates of the new point of view, equivalent to geographical
2542 | coordinates.
2543 |
2544 | Example:
2545 |
2546 | Original view onto the Earth is from right above lat=0, lon=0 with
2547 | north=upper edge, south=lower edge. Now you want to see the Earth
2548 | from a position somewhere near Baku. So lat=45,
2549 | lon=45, azimuth=0.
2550 |
2551 | The Earth must be rotated around some axis, not to be determined.
2552 | The rotation matrixx is the matrix for the change of basis to the
2553 | new local orthonormal system.
2554 |
2555 | input:
2556 | - latitude in degrees from -90 (south) to 90 (north)
2557 | - longitude in degrees from -180 (west) to 180 (east)
2558 | - azimuth in degrees from 0 (heading north) to 360 (north again)
2559 | """
2560 |
2561 | new_latitude = self._plot_viewpoint[0]
2562 | new_longitude = self._plot_viewpoint[1]
2563 | new_azimuth = self._plot_viewpoint[2]
2564 |
2565 | s_lat = np.sin(new_latitude / rad2deg)
2566 | if abs(s_lat) < epsilon:
2567 | s_lat = 0
2568 | c_lat = np.cos(new_latitude / rad2deg)
2569 | if abs(c_lat) < epsilon:
2570 | c_lat = 0
2571 | s_lon = np.sin(new_longitude / rad2deg)
2572 | if abs(s_lon) < epsilon:
2573 | s_lon = 0
2574 | c_lon = np.cos(new_longitude / rad2deg)
2575 | if abs(c_lon) < epsilon:
2576 | c_lon = 0
2577 |
2578 | # assume input basis as NED!!!
2579 |
2580 | # original point of view therein is (0,0,-1)
2581 | # new point at lat=latitude, lon=longitude, az=0, given in old
2582 | # NED-coordinates:
2583 | # (cos(latitude), sin(latitude)*sin(longitude),
2584 | # sin(latitude)*cos(longitude) )
2585 | #
2586 | # new " down' " is given by the negative position vector, so pointing
2587 | # inwards to the centre point
2588 | # down_prime = - ( np.array( ( s_lat, c_lat*c_lon, -c_lat*s_lon ) ) )
2589 | down_prime = -(np.array((s_lat, c_lat * s_lon, -c_lat * c_lon)))
2590 |
2591 | #normalise:
2592 | down_prime /= np.sqrt(np.dot(down_prime, down_prime))
2593 |
2594 | # get second local basis vector " north' " by orthogonalising
2595 | # (Gram-Schmidt method) the original north w.r.t. the new " down' "
2596 | north_prime_not_normalised = np.array((1., 0., 0.)) - \
2597 | (np.dot(down_prime, np.array((1., 0., 0.))) /
2598 | (np.dot(down_prime, down_prime)) * down_prime)
2599 |
2600 | len_north_prime_not_normalised = \
2601 | np.sqrt(np.dot(north_prime_not_normalised,
2602 | north_prime_not_normalised))
2603 | # check for poles:
2604 | if np.abs(len_north_prime_not_normalised) < epsilon:
2605 | # case: north pole
2606 | if s_lat > 0:
2607 | north_prime = np.array((0., 0., 1.))
2608 | # case: south pole
2609 | else:
2610 | north_prime = np.array((0., 0., -1.))
2611 | else:
2612 | north_prime = \
2613 | north_prime_not_normalised / len_north_prime_not_normalised
2614 |
2615 | # third basis vector is obtained by a cross product of the first two
2616 | east_prime = np.cross(down_prime, north_prime)
2617 |
2618 | # normalise:
2619 | east_prime /= np.sqrt(np.dot(east_prime, east_prime))
2620 |
2621 | rotmat_pos_raw = np.zeros((3, 3))
2622 | rotmat_pos_raw[:, 0] = north_prime
2623 | rotmat_pos_raw[:, 1] = east_prime
2624 | rotmat_pos_raw[:, 2] = down_prime
2625 |
2626 | rotmat_pos = np.asmatrix(rotmat_pos_raw).T
2627 | # this matrix gives the coordinates of a given point in the old
2628 | # coordinates w.r.t. the new system
2629 |
2630 | # up to here, only the position has changed, the angle of view
2631 | # (azimuth) has to be added by an additional rotation around the
2632 | # down'-axis (in the frame of the new coordinates)
2633 | # set up the local rotation around the new down'-axis by the given
2634 | # angle 'azimuth'. Positive values turn view counterclockwise from the
2635 | # new north'
2636 | only_rotation = np.zeros((3, 3))
2637 | s_az = np.sin(new_azimuth / rad2deg)
2638 | if abs(s_az) < epsilon:
2639 | s_az = 0.
2640 | c_az = np.cos(new_azimuth / rad2deg)
2641 | if abs(c_az) < epsilon:
2642 | c_az = 0.
2643 |
2644 | only_rotation[2, 2] = 1
2645 | only_rotation[0, 0] = c_az
2646 | only_rotation[1, 1] = c_az
2647 | only_rotation[0, 1] = -s_az
2648 | only_rotation[1, 0] = s_az
2649 |
2650 | local_rotation = np.asmatrix(only_rotation)
2651 |
2652 | # apply rotation from left!!
2653 | total_rotation_matrix = np.dot(local_rotation, rotmat_pos)
2654 |
2655 | # yields the complete matrix for representing the old coordinates in
2656 | # the new (rotated) frame:
2657 | self._plot_basis_change = total_rotation_matrix
2658 |
2659 | def _rotate_all_objects_2_new_view(self):
2660 | """
2661 | Rotate all relevant parts of the solution - namely the
2662 | eigenvector-projections, the 2 nodallines, and the faultplanes
2663 | - so that they are seen from the new viewpoint.
2664 | """
2665 | objects_2_rotate = ['all_EV', 'all_BV', 'nodalline_negative',
2666 | 'nodalline_positive', 'FP1', 'FP2']
2667 |
2668 | for obj in objects_2_rotate:
2669 | object2rotate = getattr(self, '_' + obj).transpose()
2670 |
2671 | rotated_thing = object2rotate.copy()
2672 | for i in xrange(len(object2rotate)):
2673 | rotated_thing[i] = np.dot(self._plot_basis_change,
2674 | object2rotate[i])
2675 |
2676 | rotated_object = rotated_thing.copy()
2677 | setattr(self, '_' + obj + '_rotated', rotated_object.transpose())
2678 |
2679 | def _vertical_2D_projection(self):
2680 | """
2681 | Start the vertical projection of the 3D beachball onto the 2D plane.
2682 | The projection is chosen according to the attribute '_plot_projection'
2683 | """
2684 | list_of_possible_projections = ['stereo', 'ortho', 'lambert', 'gnom']
2685 |
2686 | if not self._plot_projection in list_of_possible_projections:
2687 | print 'desired projection not possible - choose from:\n ',
2688 | print list_of_possible_projections
2689 | raise MTError(' !! ')
2690 |
2691 | if self._plot_projection == 'stereo':
2692 | if not self._stereo_vertical():
2693 | print 'ERROR in stereo_vertical'
2694 | raise MTError(' !! ')
2695 | elif self._plot_projection == 'ortho':
2696 | if not self._orthographic_vertical():
2697 | print 'ERROR in stereo_vertical'
2698 | raise MTError(' !! ')
2699 | elif self._plot_projection == 'lambert':
2700 | if not self._lambert_vertical():
2701 | print 'ERROR in stereo_vertical'
2702 | raise MTError(' !! ')
2703 | elif self._plot_projection == 'gnom':
2704 | if not self._gnomonic_vertical():
2705 | print 'ERROR in stereo_vertical'
2706 | raise MTError(' !! ')
2707 |
2708 | def _stereo_vertical(self):
2709 | """
2710 | Stereographic/azimuthal conformal 2D projection onto a plane, tangent
2711 | to the lowest point (0,0,1).
2712 |
2713 | Keeps the angles constant!
2714 |
2715 | The parts in the lower hemisphere are projected to the unit
2716 | sphere, the upper half to an annular region between radii r=1
2717 | and r=2. If the attribute '_show_upper_hemis' is set, the
2718 | projection is reversed.
2719 | """
2720 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative',
2721 | 'nodalline_positive', 'FP1', 'FP2']
2722 |
2723 | available_coord_systems = ['NED']
2724 |
2725 | if not self._plot_basis in available_coord_systems:
2726 | print 'desired plotting projection not possible - choose from :\n',
2727 | print available_coord_systems
2728 | raise MTError(' !! ')
2729 |
2730 | plot_upper_hem = self._plot_show_upper_hemis
2731 |
2732 | for obj in objects_2_project:
2733 | obj_name = '_' + obj + '_rotated'
2734 | o2proj = getattr(self, obj_name)
2735 | coords = o2proj.copy()
2736 |
2737 | n_points = len(o2proj[0, :])
2738 | stereo_coords = np.zeros((2, n_points))
2739 |
2740 | for ll in xrange(n_points):
2741 | # second component is EAST
2742 | co_x = coords[1, ll]
2743 | # first component is NORTH
2744 | co_y = coords[0, ll]
2745 | # z given in DOWN
2746 | co_z = -coords[2, ll]
2747 |
2748 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2)
2749 |
2750 | if rho_hor == 0:
2751 | new_y = 0
2752 | new_x = 0
2753 | if plot_upper_hem:
2754 | if co_z < 0:
2755 | new_x = 2
2756 | else:
2757 | if co_z > 0:
2758 | new_x = 2
2759 | else:
2760 | if co_z < 0:
2761 | new_rho = rho_hor / (1. - co_z)
2762 | if plot_upper_hem:
2763 | new_rho = 2 - (rho_hor / (1. - co_z))
2764 |
2765 | new_x = co_x / rho_hor * new_rho
2766 | new_y = co_y / rho_hor * new_rho
2767 | else:
2768 | new_rho = 2 - (rho_hor / (1. + co_z))
2769 | if plot_upper_hem:
2770 | new_rho = rho_hor / (1. + co_z)
2771 |
2772 | new_x = co_x / rho_hor * new_rho
2773 | new_y = co_y / rho_hor * new_rho
2774 |
2775 | stereo_coords[0, ll] = new_x
2776 | stereo_coords[1, ll] = new_y
2777 |
2778 | setattr(self, '_' + obj + '_2D', stereo_coords)
2779 | setattr(self, '_' + obj + '_final', stereo_coords)
2780 |
2781 | return 1
2782 |
2783 | def _orthographic_vertical(self):
2784 | """
2785 | Orthographic 2D projection onto a plane, tangent to the lowest
2786 | point (0,0,1).
2787 |
2788 | Shows the natural view on a 2D sphere from large distances (assuming
2789 | parallel projection)
2790 |
2791 | The parts in the lower hemisphere are projected to the unit
2792 | sphere, the upper half to an annular region between radii r=1
2793 | and r=2. If the attribute '_show_upper_hemis' is set, the
2794 | projection is reversed.
2795 | """
2796 |
2797 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative',
2798 | 'nodalline_positive', 'FP1', 'FP2']
2799 |
2800 | available_coord_systems = ['NED']
2801 |
2802 | if not self._plot_basis in available_coord_systems:
2803 | print 'desired plotting projection not possible - choose from :\n',
2804 | print available_coord_systems
2805 | raise MTError(' !! ')
2806 |
2807 | plot_upper_hem = self._plot_show_upper_hemis
2808 |
2809 | for obj in objects_2_project:
2810 | obj_name = '_' + obj + '_rotated'
2811 | o2proj = getattr(self, obj_name)
2812 | coords = o2proj.copy()
2813 |
2814 | n_points = len(o2proj[0, :])
2815 | coords2D = np.zeros((2, n_points))
2816 |
2817 | for ll in xrange(n_points):
2818 | # second component is EAST
2819 | co_x = coords[1, ll]
2820 | # first component is NORTH
2821 | co_y = coords[0, ll]
2822 | # z given in DOWN
2823 | co_z = -coords[2, ll]
2824 |
2825 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2)
2826 |
2827 | if rho_hor == 0:
2828 | new_y = 0
2829 | new_x = 0
2830 | if plot_upper_hem:
2831 | if co_z < 0:
2832 | new_x = 2
2833 | else:
2834 | if co_z > 0:
2835 | new_x = 2
2836 | else:
2837 | if co_z < 0:
2838 | new_rho = rho_hor
2839 | if plot_upper_hem:
2840 | new_rho = 2 - rho_hor
2841 |
2842 | new_x = co_x / rho_hor * new_rho
2843 | new_y = co_y / rho_hor * new_rho
2844 | else:
2845 | new_rho = 2 - rho_hor
2846 | if plot_upper_hem:
2847 | new_rho = rho_hor
2848 |
2849 | new_x = co_x / rho_hor * new_rho
2850 | new_y = co_y / rho_hor * new_rho
2851 |
2852 | coords2D[0, ll] = new_x
2853 | coords2D[1, ll] = new_y
2854 |
2855 | setattr(self, '_' + obj + '_2D', coords2D)
2856 | setattr(self, '_' + obj + '_final', coords2D)
2857 |
2858 | return 1
2859 |
2860 | def _lambert_vertical(self):
2861 | """
2862 | Lambert azimuthal equal-area 2D projection onto a plane, tangent to the
2863 | lowest point (0,0,1).
2864 |
2865 | Keeps the area constant!
2866 |
2867 | The parts in the lower hemisphere are projected to the unit
2868 | sphere (only here the area is kept constant), the upper half to an
2869 | annular region between radii r=1 and r=2. If the attribute
2870 | '_show_upper_hemis' is set, the projection is reversed.
2871 | """
2872 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative',
2873 | 'nodalline_positive', 'FP1', 'FP2']
2874 |
2875 | available_coord_systems = ['NED']
2876 |
2877 | if not self._plot_basis in available_coord_systems:
2878 | print 'desired plotting projection not possible - choose from :\n',
2879 | print available_coord_systems
2880 | raise MTError(' !! ')
2881 |
2882 | plot_upper_hem = self._plot_show_upper_hemis
2883 |
2884 | for obj in objects_2_project:
2885 | obj_name = '_' + obj + '_rotated'
2886 | o2proj = getattr(self, obj_name)
2887 | coords = o2proj.copy()
2888 |
2889 | n_points = len(o2proj[0, :])
2890 | coords2D = np.zeros((2, n_points))
2891 |
2892 | for ll in xrange(n_points):
2893 | # second component is EAST
2894 | co_x = coords[1, ll]
2895 | # first component is NORTH
2896 | co_y = coords[0, ll]
2897 | # z given in DOWN
2898 | co_z = -coords[2, ll]
2899 |
2900 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2)
2901 |
2902 | if rho_hor == 0:
2903 | new_y = 0
2904 | new_x = 0
2905 | if plot_upper_hem:
2906 | if co_z < 0:
2907 | new_x = 2
2908 | else:
2909 | if co_z > 0:
2910 | new_x = 2
2911 | else:
2912 | if co_z < 0:
2913 | new_rho = rho_hor / np.sqrt(1. - co_z)
2914 |
2915 | if plot_upper_hem:
2916 | new_rho = 2 - (rho_hor / np.sqrt(1. - co_z))
2917 |
2918 | new_x = co_x / rho_hor * new_rho
2919 | new_y = co_y / rho_hor * new_rho
2920 |
2921 | else:
2922 | new_rho = 2 - (rho_hor / np.sqrt(1. + co_z))
2923 |
2924 | if plot_upper_hem:
2925 | new_rho = rho_hor / np.sqrt(1. + co_z)
2926 |
2927 | new_x = co_x / rho_hor * new_rho
2928 | new_y = co_y / rho_hor * new_rho
2929 |
2930 | coords2D[0, ll] = new_x
2931 | coords2D[1, ll] = new_y
2932 |
2933 | setattr(self, '_' + obj + '_2D', coords2D)
2934 | setattr(self, '_' + obj + '_final', coords2D)
2935 |
2936 | return 1
2937 |
2938 | def _gnomonic_vertical(self):
2939 | """
2940 | Gnomonic 2D projection onto a plane, tangent to the lowest
2941 | point (0,0,1).
2942 |
2943 | Keeps the great circles as straight lines (geodetics constant) !
2944 |
2945 | The parts in the lower hemisphere are projected to the unit
2946 | sphere, the upper half to an annular region between radii r=1
2947 | and r=2. If the attribute '_show_upper_hemis' is set, the
2948 | projection is reversed.
2949 | """
2950 |
2951 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative',
2952 | 'nodalline_positive', 'FP1', 'FP2']
2953 |
2954 | available_coord_systems = ['NED']
2955 |
2956 | if not self._plot_basis in available_coord_systems:
2957 | print 'desired plotting projection not possible - choose from :\n',
2958 | print available_coord_systems
2959 | raise MTError(' !! ')
2960 |
2961 | plot_upper_hem = self._plot_show_upper_hemis
2962 |
2963 | for obj in objects_2_project:
2964 | obj_name = '_' + obj + '_rotated'
2965 | o2proj = getattr(self, obj_name)
2966 | coords = o2proj.copy()
2967 |
2968 | n_points = len(o2proj[0, :])
2969 | coords2D = np.zeros((2, n_points))
2970 |
2971 | for ll in xrange(n_points):
2972 | # second component is EAST
2973 | co_x = coords[1, ll]
2974 | # first component is NORTH
2975 | co_y = coords[0, ll]
2976 | # z given in DOWN
2977 | co_z = -coords[2, ll]
2978 |
2979 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2)
2980 |
2981 | if rho_hor == 0:
2982 | new_y = 0
2983 | new_x = 0
2984 | if co_z > 0:
2985 | new_x = 2
2986 | if plot_upper_hem:
2987 | new_x = 0
2988 | else:
2989 | if co_z < 0:
2990 | new_rho = np.cos(np.arcsin(rho_hor)) * \
2991 | np.tan(np.arcsin(rho_hor))
2992 |
2993 | if plot_upper_hem:
2994 | new_rho = 2 - (np.cos(np.arcsin(rho_hor)) *
2995 | np.tan(np.arcsin(rho_hor)))
2996 |
2997 | new_x = co_x / rho_hor * new_rho
2998 | new_y = co_y / rho_hor * new_rho
2999 |
3000 | else:
3001 | new_rho = 2 - (np.cos(np.arcsin(rho_hor)) *
3002 | np.tan(np.arcsin(rho_hor)))
3003 |
3004 | if plot_upper_hem:
3005 | new_rho = np.cos(np.arcsin(rho_hor)) * \
3006 | np.tan(np.arcsin(rho_hor))
3007 |
3008 | new_x = co_x / rho_hor * new_rho
3009 | new_y = co_y / rho_hor * new_rho
3010 |
3011 | coords2D[0, ll] = new_x
3012 | coords2D[1, ll] = new_y
3013 |
3014 | setattr(self, '_' + obj + '_2D', coords2D)
3015 | setattr(self, '_' + obj + '_final', coords2D)
3016 |
3017 | return 1
3018 |
3019 | def _build_circles(self):
3020 | """
3021 | Sets two sets of points, describing the unit sphere and the outer
3022 | circle with r=2.
3023 |
3024 | Added as attributes '_unit_sphere' and '_outer_circle'.
3025 | """
3026 | phi = self._phi_curve
3027 |
3028 | UnitSphere = np.zeros((2, len(phi)))
3029 | UnitSphere[0, :] = np.cos(phi)
3030 | UnitSphere[1, :] = np.sin(phi)
3031 |
3032 | # outer circle ( radius for stereographic projection is set to 2 )
3033 | outer_circle_points = 2 * UnitSphere
3034 |
3035 | self._unit_sphere = UnitSphere
3036 | self._outer_circle = outer_circle_points
3037 |
3038 | def _sort_curve_points(self, curve):
3039 | """
3040 | Checks, if curve points are in right order for line plotting.
3041 |
3042 | If not, a re-arranging is carried out.
3043 | """
3044 | sorted_curve = np.zeros((2, len(curve[0, :])))
3045 | # in polar coordinates
3046 | r_phi_curve = np.zeros((len(curve[0, :]), 2))
3047 | for ii in xrange(curve.shape[1]):
3048 | r_phi_curve[ii, 0] = \
3049 | math.sqrt(curve[0, ii] ** 2 + curve[1, ii] ** 2)
3050 | r_phi_curve[ii, 1] = \
3051 | math.atan2(curve[0, ii], curve[1, ii]) % (2 * pi)
3052 | # find index with highest r
3053 | largest_r_idx = np.argmax(r_phi_curve[:, 0])
3054 |
3055 | # check, if perhaps more values with same r - if so, take point with
3056 | # lowest phi
3057 | other_idces = \
3058 | list(np.where(r_phi_curve[:, 0] == r_phi_curve[largest_r_idx, 0]))
3059 | if len(other_idces) > 1:
3060 | best_idx = np.argmin(r_phi_curve[other_idces, 1])
3061 | start_idx_curve = other_idces[best_idx]
3062 | else:
3063 | start_idx_curve = largest_r_idx
3064 |
3065 | if not start_idx_curve == 0:
3066 | pass
3067 |
3068 | # check orientation - want to go inwards
3069 | start_r = r_phi_curve[start_idx_curve, 0]
3070 | next_idx = (start_idx_curve + 1) % len(r_phi_curve[:, 0])
3071 | prep_idx = (start_idx_curve - 1) % len(r_phi_curve[:, 0])
3072 | next_r = r_phi_curve[next_idx, 0]
3073 |
3074 | keep_direction = True
3075 | if next_r <= start_r:
3076 | # check, if next R is on other side of area - look at total
3077 | # distance - if yes, reverse direction
3078 | dist_first_next = \
3079 | (curve[0, next_idx] - curve[0, start_idx_curve]) ** 2 + \
3080 | (curve[1, next_idx] - curve[1, start_idx_curve]) ** 2
3081 | dist_first_other = \
3082 | (curve[0, prep_idx] - curve[0, start_idx_curve]) ** 2 + \
3083 | (curve[1, prep_idx] - curve[1, start_idx_curve]) ** 2
3084 |
3085 | if dist_first_next > dist_first_other:
3086 | keep_direction = False
3087 |
3088 | if keep_direction:
3089 | # direction is kept
3090 | for jj in xrange(curve.shape[1]):
3091 | running_idx = (start_idx_curve + jj) % len(curve[0, :])
3092 | sorted_curve[0, jj] = curve[0, running_idx]
3093 | sorted_curve[1, jj] = curve[1, running_idx]
3094 | else:
3095 | # direction is reversed
3096 | for jj in xrange(curve.shape[1]):
3097 | running_idx = (start_idx_curve - jj) % len(curve[0, :])
3098 | sorted_curve[0, jj] = curve[0, running_idx]
3099 | sorted_curve[1, jj] = curve[1, running_idx]
3100 |
3101 | # check if step of first to second point does not have large angle
3102 | # step (problem caused by projection of point (pole) onto whole
3103 | # edge - if this first angle step is larger than the one between
3104 | # points 2 and three, correct position of first point: keep R, but
3105 | # take angle with same difference as point 2 to point 3
3106 |
3107 | angle_point_1 = (math.atan2(sorted_curve[0, 0],
3108 | sorted_curve[1, 0]) % (2 * pi))
3109 | angle_point_2 = (math.atan2(sorted_curve[0, 1],
3110 | sorted_curve[1, 1]) % (2 * pi))
3111 | angle_point_3 = (math.atan2(sorted_curve[0, 2],
3112 | sorted_curve[1, 2]) % (2 * pi))
3113 |
3114 | angle_diff_23 = (angle_point_3 - angle_point_2)
3115 | if angle_diff_23 > pi:
3116 | angle_diff_23 = (-angle_diff_23) % (2 * pi)
3117 |
3118 | angle_diff_12 = (angle_point_2 - angle_point_1)
3119 | if angle_diff_12 > pi:
3120 | angle_diff_12 = (-angle_diff_12) % (2 * pi)
3121 |
3122 | if abs(angle_diff_12) > abs(angle_diff_23):
3123 | r_old = \
3124 | math.sqrt(sorted_curve[0, 0] ** 2 + sorted_curve[1, 0] ** 2)
3125 | new_angle = (angle_point_2 - angle_diff_23) % (2 * pi)
3126 | sorted_curve[0, 0] = r_old * math.sin(new_angle)
3127 | sorted_curve[1, 0] = r_old * math.cos(new_angle)
3128 |
3129 | return sorted_curve
3130 |
3131 | def _smooth_curves(self):
3132 | """
3133 | Corrects curves for potential large gaps, resulting in strange
3134 | intersection lines on nodals of round and irreagularly shaped
3135 | areas.
3136 |
3137 | At least one coordinte point on each degree on the circle is assured.
3138 | """
3139 | list_of_curves_2_smooth = ['nodalline_negative', 'nodalline_positive',
3140 | 'FP1', 'FP2']
3141 |
3142 | points_per_degree = self._plot_n_points / 360.
3143 |
3144 | for curve2smooth in list_of_curves_2_smooth:
3145 | obj_name = curve2smooth + '_in_order'
3146 | obj = getattr(self, '_' + obj_name).transpose()
3147 |
3148 | smoothed_array = np.zeros((1, 2))
3149 | smoothed_array[0, :] = obj[0]
3150 | smoothed_list = [smoothed_array]
3151 |
3152 | # now in shape (n_points,2)
3153 | for idx, val in enumerate(obj[:-1]):
3154 | r1 = math.sqrt(val[0] ** 2 + val[1] ** 2)
3155 | r2 = math.sqrt(obj[idx + 1][0] ** 2 + obj[idx + 1][1] ** 2)
3156 | phi1 = math.atan2(val[0], val[1])
3157 | phi2 = math.atan2(obj[idx + 1][0], obj[idx + 1][1])
3158 |
3159 | phi2_larger = np.sign(phi2 - phi1)
3160 | angle_smaller_pi = np.sign(pi - abs(phi2 - phi1))
3161 |
3162 | if phi2_larger * angle_smaller_pi > 0:
3163 | go_cw = True
3164 | openangle = (phi2 - phi1) % (2 * pi)
3165 | else:
3166 | go_cw = False
3167 | openangle = (phi1 - phi2) % (2 * pi)
3168 |
3169 | openangle_deg = openangle * rad2deg
3170 | radius_diff = r2 - r1
3171 |
3172 | if openangle_deg > 1. / points_per_degree:
3173 |
3174 | n_fillpoints = int(openangle_deg * points_per_degree)
3175 | fill_array = np.zeros((n_fillpoints, 2))
3176 | if go_cw:
3177 | angles = ((np.arange(n_fillpoints) + 1) * openangle /
3178 | (n_fillpoints + 1) + phi1) % (2 * pi)
3179 | else:
3180 | angles = (phi1 - (np.arange(n_fillpoints) + 1) *
3181 | openangle / (n_fillpoints + 1)) % (2 * pi)
3182 |
3183 | radii = (np.arange(n_fillpoints) + 1) * \
3184 | radius_diff / (n_fillpoints + 1) + r1
3185 |
3186 | fill_array[:, 0] = radii * np.sin(angles)
3187 | fill_array[:, 1] = radii * np.cos(angles)
3188 |
3189 | smoothed_list.append(fill_array)
3190 |
3191 | smoothed_list.append([obj[idx + 1]])
3192 |
3193 | smoothed_array = np.vstack(smoothed_list)
3194 | setattr(self, '_' + curve2smooth + '_final',
3195 | smoothed_array.transpose())
3196 |
3197 | def _check_curve_in_curve(self):
3198 | """
3199 | Checks, if one of the two nodallines contains the other one
3200 | completely. If so, the order of colours is re-adapted,
3201 | assuring the correct order when doing the overlay plotting.
3202 | """
3203 | lo_points_in_pos_curve = \
3204 | list(self._nodalline_positive_final.transpose())
3205 | lo_points_in_pos_curve_array = \
3206 | self._nodalline_positive_final.transpose()
3207 | lo_points_in_neg_curve = \
3208 | list(self._nodalline_negative_final.transpose())
3209 | lo_points_in_neg_curve_array = \
3210 | self._nodalline_negative_final.transpose()
3211 |
3212 | # check, if negative curve completely within positive curve
3213 | mask_neg_in_pos = 0
3214 | for neg_point in lo_points_in_neg_curve:
3215 | mask_neg_in_pos += self._pnpoly(lo_points_in_pos_curve_array,
3216 | neg_point[:2])
3217 | if mask_neg_in_pos > len(lo_points_in_neg_curve) - 3:
3218 | self._plot_curve_in_curve = 1
3219 |
3220 | # check, if positive curve completely within negative curve
3221 | mask_pos_in_neg = 0
3222 | for pos_point in lo_points_in_pos_curve:
3223 | mask_pos_in_neg += self._pnpoly(lo_points_in_neg_curve_array,
3224 | pos_point[:2])
3225 | if mask_pos_in_neg > len(lo_points_in_pos_curve) - 3:
3226 | self._plot_curve_in_curve = -1
3227 |
3228 | # correct for ONE special case: double couple with its
3229 | # eigensystem = NED basis system:
3230 | testarray = [1., 0, 0, 0, 1, 0, 0, 0, 1]
3231 | if np.prod(self.MT._rotation_matrix.A1 == testarray) and \
3232 | (self.MT._eigenvalues[1] == 0):
3233 | self._plot_curve_in_curve = -1
3234 | self._plot_clr_order = 1
3235 |
3236 | def _point_inside_polygon(self, x, y, poly):
3237 | """
3238 | Determine if a point is inside a given polygon or not.
3239 |
3240 | Polygon is a list of (x,y) pairs.
3241 | """
3242 | n = len(poly)
3243 | inside = False
3244 |
3245 | p1x, p1y = poly[0]
3246 | for i in range(n + 1):
3247 | p2x, p2y = poly[i % n]
3248 | if y > min(p1y, p2y):
3249 | if y <= max(p1y, p2y):
3250 | if x <= max(p1x, p2x):
3251 | if p1y != p2y:
3252 | xinters = \
3253 | (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
3254 | if p1x == p2x or x <= xinters:
3255 | inside = not inside
3256 | p1x, p1y = p2x, p2y
3257 |
3258 | return inside
3259 |
3260 | def _pnpoly(self, verts, point):
3261 | """
3262 | Check whether point is in the polygon defined by verts.
3263 |
3264 | verts - 2xN array
3265 | point - (2,) array
3266 |
3267 | See
3268 | http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
3269 | """
3270 | # using take instead of getitem, about ten times faster, see
3271 | # http://wesmckinney.com/blog/?p=215
3272 | verts = np.require(verts, dtype=np.float64)
3273 | x, y = point
3274 |
3275 | xpi = verts[:, 0]
3276 | ypi = verts[:, 1]
3277 | # shift
3278 | xpj = xpi.take(self.arange_1[:xpi.size])
3279 | ypj = ypi.take(self.arange_1[:ypi.size])
3280 |
3281 | possible_crossings = \
3282 | ((ypi <= y) & (y < ypj)) | ((ypj <= y) & (y < ypi))
3283 |
3284 | xpi = xpi[possible_crossings]
3285 | ypi = ypi[possible_crossings]
3286 | xpj = xpj[possible_crossings]
3287 | ypj = ypj[possible_crossings]
3288 |
3289 | crossings = x < (xpj - xpi) * (y - ypi) / (ypj - ypi) + xpi
3290 |
3291 | return crossings.sum() % 2
3292 |
3293 | def _projection_2_unit_sphere(self):
3294 | """
3295 | Brings the complete solution (from stereographic projection)
3296 | onto the unit sphere by just shrinking the maximum radius of
3297 | all points to 1.
3298 |
3299 | This keeps the area definitions, so the colouring is not affected.
3300 | """
3301 | list_of_objects_2_project = ['nodalline_positive_final',
3302 | 'nodalline_negative_final']
3303 | lo_fps = ['FP1_final', 'FP2_final']
3304 |
3305 | for obj2proj in list_of_objects_2_project:
3306 | obj = getattr(self, '_' + obj2proj).transpose().copy()
3307 | for idx, val in enumerate(obj):
3308 | old_radius = np.sqrt(val[0] ** 2 + val[1] ** 2)
3309 | if old_radius > 1:
3310 | obj[idx, 0] = val[0] / old_radius
3311 | obj[idx, 1] = val[1] / old_radius
3312 |
3313 | setattr(self, '_' + obj2proj + '_US', obj.transpose())
3314 |
3315 | for fp in lo_fps:
3316 | obj = getattr(self, '_' + fp).transpose().copy()
3317 |
3318 | tmp_obj = []
3319 | for idx, val in enumerate(obj):
3320 | old_radius = np.sqrt(val[0] ** 2 + val[1] ** 2)
3321 | if old_radius <= 1 + epsilon:
3322 | tmp_obj.append(val)
3323 | tmp_obj2 = np.array(tmp_obj).transpose()
3324 | tmp_obj3 = self._sort_curve_points(tmp_obj2)
3325 |
3326 | setattr(self, '_' + fp + '_US', tmp_obj3)
3327 |
3328 | lo_visible_EV = []
3329 |
3330 | for idx, val in enumerate(self._all_EV_2D.transpose()):
3331 | r_ev = np.sqrt(val[0] ** 2 + val[1] ** 2)
3332 | if r_ev <= 1:
3333 | lo_visible_EV.append([val[0], val[1], idx])
3334 | visible_EVs = np.array(lo_visible_EV)
3335 |
3336 | self._all_EV_2D_US = visible_EVs
3337 |
3338 | lo_visible_BV = []
3339 | dummy_list1 = []
3340 | direction_letters = list('NSEWDU')
3341 |
3342 | for idx, val in enumerate(self._all_BV_2D.transpose()):
3343 | r_bv = math.sqrt(val[0] ** 2 + val[1] ** 2)
3344 | if r_bv <= 1:
3345 | if idx == 1 and 'N' in dummy_list1:
3346 | continue
3347 | elif idx == 3 and 'E' in dummy_list1:
3348 | continue
3349 | elif idx == 5 and 'D' in dummy_list1:
3350 | continue
3351 | else:
3352 | lo_visible_BV.append([val[0], val[1], idx])
3353 | dummy_list1.append(direction_letters[idx])
3354 |
3355 | visible_BVs = np.array(lo_visible_BV)
3356 |
3357 | self._all_BV_2D_US = visible_BVs
3358 |
3359 | def _plot_US(self, ax=None):
3360 | """
3361 | Generates the final plot of the beachball projection on the unit
3362 | sphere.
3363 |
3364 | Additionally, the plot can be saved in a file on the fly.
3365 | """
3366 | import pylab as P
3367 |
3368 | plotfig = self._setup_plot_US(P, ax=ax)
3369 |
3370 | if self._plot_save_plot:
3371 | try:
3372 | plotfig.savefig(self._plot_outfile + '.' +
3373 | self._plot_outfile_format, dpi=self._plot_dpi,
3374 | transparent=True,
3375 | format=self._plot_outfile_format)
3376 | except:
3377 | print 'saving of plot not possible'
3378 | P.show()
3379 | P.close('all')
3380 |
3381 | def _setup_plot_US(self, P, ax=None):
3382 | """
3383 | Setting up the figure with the final plot of the unit sphere.
3384 |
3385 | Either called by _plot_US or by _just_save_bb
3386 | """
3387 | P.close(667)
3388 | if ax is None:
3389 | plotfig = P.figure(667, figsize=(self._plot_size, self._plot_size))
3390 | plotfig.subplots_adjust(left=0, bottom=0, right=1, top=1)
3391 | ax = plotfig.add_subplot(111, aspect='equal')
3392 |
3393 | ax.axison = False
3394 |
3395 | neg_nodalline = self._nodalline_negative_final_US
3396 | pos_nodalline = self._nodalline_positive_final_US
3397 | FP1_2_plot = self._FP1_final_US
3398 | FP2_2_plot = self._FP2_final_US
3399 |
3400 | US = self._unit_sphere
3401 |
3402 | tension_colour = self._plot_tension_colour
3403 | pressure_colour = self._plot_pressure_colour
3404 |
3405 | if self._plot_fill_flag:
3406 | if self._plot_clr_order > 0:
3407 | alpha = self._plot_fill_alpha * self._plot_total_alpha
3408 |
3409 | ax.fill(US[0, :], US[1, :], fc=pressure_colour, alpha=alpha)
3410 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :],
3411 | fc=tension_colour, alpha=alpha)
3412 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :],
3413 | fc=tension_colour, alpha=alpha)
3414 |
3415 | if self._plot_curve_in_curve != 0:
3416 | ax.fill(US[0, :], US[1, :], fc=tension_colour,
3417 | alpha=alpha)
3418 |
3419 | if self._plot_curve_in_curve < 1:
3420 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :],
3421 | fc=pressure_colour, alpha=alpha)
3422 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :],
3423 | fc=tension_colour, alpha=alpha)
3424 | pass
3425 | else:
3426 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :],
3427 | fc=pressure_colour, alpha=alpha)
3428 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :],
3429 | fc=tension_colour, alpha=alpha)
3430 | pass
3431 |
3432 | EV_sym = ['m^', 'b^', 'g^', 'mv', 'bv', 'gv']
3433 |
3434 | if self._plot_show_princ_axes:
3435 | alpha = \
3436 | self._plot_princ_axes_alpha * self._plot_total_alpha
3437 |
3438 | for val in self._all_EV_2D_US:
3439 | ax.plot([val[0]], [val[1]], EV_sym[int(val[2])],
3440 | ms=self._plot_princ_axes_symsize,
3441 | lw=self._plot_princ_axes_lw, alpha=alpha)
3442 |
3443 | else:
3444 | alpha = self._plot_fill_alpha * self._plot_total_alpha
3445 |
3446 | ax.fill(US[0, :], US[1, :], fc=tension_colour, alpha=alpha)
3447 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :],
3448 | fc=pressure_colour, alpha=alpha)
3449 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :],
3450 | fc=pressure_colour, alpha=alpha)
3451 |
3452 | if self._plot_curve_in_curve != 0:
3453 | ax.fill(US[0, :], US[1, :], fc=pressure_colour,
3454 | alpha=alpha)
3455 |
3456 | if self._plot_curve_in_curve < 1:
3457 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :],
3458 | fc=tension_colour, alpha=alpha)
3459 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :],
3460 | fc=pressure_colour, alpha=alpha)
3461 | pass
3462 | else:
3463 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :],
3464 | fc=tension_colour, alpha=alpha)
3465 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :],
3466 | fc=pressure_colour, alpha=alpha)
3467 | pass
3468 |
3469 | EV_sym = ['g^', 'b^', 'm^', 'gv', 'bv', 'mv']
3470 | if self._plot_show_princ_axes:
3471 | alpha = self._plot_princ_axes_alpha * self._plot_total_alpha
3472 |
3473 | for val in self._all_EV_2D_US:
3474 | ax.plot([val[0]], [val[1]], EV_sym[int(val[2])],
3475 | ms=self._plot_princ_axes_symsize,
3476 | lw=self._plot_princ_axes_lw, alpha=alpha)
3477 |
3478 | #
3479 | # set all nodallines and faultplanes for plotting:
3480 | #
3481 |
3482 | ax.plot(neg_nodalline[0, :], neg_nodalline[1, :],
3483 | c=self._plot_nodalline_colour, ls='-',
3484 | lw=self._plot_nodalline_width,
3485 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha)
3486 | #ax.plot( neg_nodalline[0,:] ,neg_nodalline[1,:],'go')
3487 |
3488 | ax.plot(pos_nodalline[0, :], pos_nodalline[1, :],
3489 | c=self._plot_nodalline_colour, ls='-',
3490 | lw=self._plot_nodalline_width,
3491 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha)
3492 |
3493 | if self._plot_show_faultplanes:
3494 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :],
3495 | c=self._plot_faultplane_colour, ls='-',
3496 | lw=self._plot_faultplane_width,
3497 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha)
3498 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :],
3499 | c=self._plot_faultplane_colour, ls='-',
3500 | lw=self._plot_faultplane_width,
3501 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha)
3502 |
3503 | elif self._plot_show_1faultplane:
3504 | if not self._plot_show_FP_index in [1, 2]:
3505 | print 'no fault plane specified for being plotted... ',
3506 | print 'continue without faultplane'
3507 | pass
3508 | else:
3509 | alpha = self._plot_faultplane_alpha * self._plot_total_alpha
3510 | if self._plot_show_FP_index == 1:
3511 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :],
3512 | c=self._plot_faultplane_colour, ls='-',
3513 | lw=self._plot_faultplane_width, alpha=alpha)
3514 | else:
3515 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :],
3516 | c=self._plot_faultplane_colour, ls='-',
3517 | lw=self._plot_faultplane_width, alpha=alpha)
3518 |
3519 | # if isotropic part shall be displayed, fill the circle completely with
3520 | # the appropriate colour
3521 |
3522 | if self._pure_isotropic:
3523 | # f abs( np.trace( self._M )) > epsilon:
3524 | if self._plot_clr_order < 0:
3525 | ax.fill(US[0, :], US[1, :], fc=tension_colour, alpha=1,
3526 | zorder=100)
3527 | else:
3528 | ax.fill(US[0, :], US[1, :], fc=pressure_colour, alpha=1,
3529 | zorder=100)
3530 |
3531 | # plot outer circle line of US
3532 | ax.plot(US[0, :], US[1, :], c=self._plot_outerline_colour, ls='-',
3533 | lw=self._plot_outerline_width,
3534 | alpha=self._plot_outerline_alpha * self._plot_total_alpha)
3535 |
3536 | # plot NED basis vectors
3537 | if self._plot_show_basis_axes:
3538 | plot_size_in_points = self._plot_size * 2.54 * 72
3539 | points_per_unit = plot_size_in_points / 2.
3540 |
3541 | fontsize = plot_size_in_points / 40.
3542 | symsize = plot_size_in_points / 61.
3543 |
3544 | direction_letters = list('NSEWDU')
3545 |
3546 | for val in self._all_BV_2D_US:
3547 | x_coord = val[0]
3548 | y_coord = val[1]
3549 | np_letter = direction_letters[int(val[2])]
3550 |
3551 | rot_angle = -np.arctan2(y_coord, x_coord) + pi / 2.
3552 | original_rho = np.sqrt(x_coord ** 2 + y_coord ** 2)
3553 |
3554 | marker_x = (original_rho - (1.5 * symsize / points_per_unit)) \
3555 | * np.sin(rot_angle)
3556 | marker_y = (original_rho - (1.5 * symsize / points_per_unit)) \
3557 | * np.cos(rot_angle)
3558 | annot_x = (original_rho - (4.5 * fontsize / points_per_unit)) \
3559 | * np.sin(rot_angle)
3560 | annot_y = (original_rho - (4.5 * fontsize / points_per_unit)) \
3561 | * np.cos(rot_angle)
3562 |
3563 | ax.text(annot_x, annot_y, np_letter,
3564 | horizontalalignment='center', size=fontsize,
3565 | weight='bold', verticalalignment='center',
3566 | bbox=dict(edgecolor='white', facecolor='white',
3567 | alpha=1))
3568 |
3569 | if original_rho > epsilon:
3570 | ax.scatter([marker_x], [marker_y],
3571 | marker=(3, 0, rot_angle), s=symsize ** 2,
3572 | c='k', facecolor='k', zorder=300)
3573 | else:
3574 | ax.scatter([x_coord], [y_coord], marker=(4, 1, rot_angle),
3575 | s=symsize ** 2, c='k', facecolor='k',
3576 | zorder=300)
3577 |
3578 | # plot 4 fake points, guaranteeing full visibilty of the sphere
3579 | ax.plot([0, 1.05, 0, -1.05], [1.05, 0, -1.05, 0], ',', alpha=0.)
3580 | # scaling behaviour
3581 | ax.autoscale_view(tight=True, scalex=True, scaley=True)
3582 |
3583 | return plotfig
3584 |
3585 |
3586 | if __name__ == "__main__":
3587 |
3588 | from optparse import OptionParser, OptionGroup
3589 |
3590 | decomp_attrib_map_keys = ('in', 'out', 'type',
3591 | 'full',
3592 | 'iso', 'iso_perc',
3593 | 'dev', 'devi', 'devi_perc',
3594 | 'dc', 'dc_perc',
3595 | 'dc2', 'dc2_perc',
3596 | 'dc3', 'dc3_perc',
3597 | 'clvd', 'clvd_perc',
3598 | 'mom', 'mag',
3599 | 'eigvals', 'eigvecs',
3600 | 't', 'n', 'p')
3601 |
3602 | decomp_attrib_map = dict(zip(decomp_attrib_map_keys,
3603 | ('input_system', 'output_system',
3604 | 'decomp_type', 'M',
3605 | 'iso', 'iso_percentage',
3606 | 'devi', 'devi', 'devi_percentage',
3607 | 'DC', 'DC_percentage',
3608 | 'DC2', 'DC2_percentage',
3609 | 'DC3', 'DC3_percentage',
3610 | 'CLVD', 'CLVD_percentage',
3611 | 'moment', 'mag',
3612 | 'eigvals', 'eigvecs',
3613 | 't_axis', 'null_axis', 'p_axis')
3614 | ))
3615 |
3616 | lo_allowed_systems = ['NED', 'USE', 'XYZ', 'NWU']
3617 |
3618 | def _handle_input(call, M_in, call_args, optparser):
3619 | """
3620 | take the original method and its arguments, the source mechanism,
3621 | and the dictionary with proper parsers for each call,
3622 | """
3623 |
3624 | # construct a dict with consistent keywordargs suited for the current
3625 | # call
3626 | kwargs = _parse_arguments(call, call_args, optparser)
3627 |
3628 | # set the fitting input basis system
3629 | in_system = kwargs.get('in_system', 'NED')
3630 | out_system = kwargs.get('out_system', 'NED')
3631 |
3632 | # build the moment tensor object
3633 | mt = MomentTensor(
3634 | M=M_in, in_system=in_system, out_system=out_system)
3635 |
3636 | # if only parts of M are to be plotted, M must be reduced to this part
3637 | # already here!
3638 | if call == 'plot' and kwargs['plot_part_of_m']:
3639 | if kwargs['plot_part_of_m'] == 'iso':
3640 | mt = MomentTensor(
3641 | M=mt.get_iso(), in_system=in_system, out_system=out_system)
3642 |
3643 | if kwargs['plot_part_of_m'] == 'devi':
3644 | mt = MomentTensor(
3645 | M=mt.get_devi(), in_system=in_system,
3646 | out_system=out_system)
3647 |
3648 | if kwargs['plot_part_of_m'] == 'dc':
3649 | mt = MomentTensor(
3650 | M=mt.get_DC(), in_system=in_system, out_system=out_system)
3651 |
3652 | if kwargs['plot_part_of_m'] == 'clvd':
3653 | mt = MomentTensor(
3654 | M=mt.get_CLVD(), in_system=in_system,
3655 | out_system=out_system)
3656 |
3657 | # call the main routine to handle the moment tensor
3658 | return _call_main(mt, call, kwargs)
3659 |
3660 | def _call_main(MT, main_call, kwargs_dict):
3661 |
3662 | if main_call == 'plot':
3663 | return _call_plot(MT, kwargs_dict)
3664 |
3665 | elif main_call == 'gmt':
3666 | return _call_gmt(MT, kwargs_dict)
3667 |
3668 | elif main_call == 'decompose':
3669 | return _call_decompose(MT, kwargs_dict)
3670 |
3671 | elif main_call == 'describe':
3672 | return _call_describe(MT, kwargs_dict)
3673 |
3674 | def _call_plot(MT, kwargs_dict):
3675 |
3676 | bb2plot = BeachBall(MT, kwargs_dict)
3677 |
3678 | if kwargs_dict['plot_save_plot']:
3679 | bb2plot.save_BB(kwargs_dict)
3680 | return
3681 |
3682 | if kwargs_dict['plot_pa_plot']:
3683 | bb2plot.pa_plot(kwargs_dict)
3684 | return
3685 |
3686 | if kwargs_dict['plot_full_sphere']:
3687 | bb2plot.full_sphere_plot(kwargs_dict)
3688 | return
3689 |
3690 | bb2plot.ploBB(kwargs_dict)
3691 |
3692 | return
3693 |
3694 | def _call_gmt(MT, kwargs_dict):
3695 | bb = BeachBall(MT, kwargs_dict)
3696 | return bb.get_psxy(kwargs_dict)
3697 |
3698 | def _call_decompose(MT, kwargs_dict):
3699 |
3700 | MT._isotropic = None
3701 | MT._deviatoric = None
3702 | MT._DC = None
3703 | MT._iso_percentage = None
3704 | MT._DC_percentage = None
3705 | MT._DC2 = None
3706 | MT._DC3 = None
3707 | MT._DC2_percentage = None
3708 | MT._CLVD = None
3709 | MT._seismic_moment = None
3710 | MT._moment_magnitude = None
3711 |
3712 | out_system = kwargs_dict['out_system']
3713 | MT._output_basis = out_system
3714 | MT._decomposition_key = kwargs_dict['decomposition_key']
3715 |
3716 | MT._decompose_M()
3717 |
3718 | print
3719 |
3720 | # build argument for local call within MT object:
3721 | lo_args = kwargs_dict['decomp_out_part']
3722 |
3723 | if not lo_args:
3724 | lo_args = decomp_attrib_map_keys
3725 |
3726 | labels = '''Basis system of the input
3727 | Basis system of the output
3728 | Decomposition type
3729 | Full moment tensor
3730 | Isotropic part
3731 | Isotropic percentage
3732 | Deviatoric part
3733 | Deviatoric part
3734 | Deviatoric percentage
3735 | Double Couple part
3736 | Double Couple percentage
3737 | Second Double Couple part
3738 | Second Double Couple's percentage
3739 | Third Double Couple part
3740 | Third Double Couple's percentage
3741 | CLVD part in
3742 | CLVD percentage
3743 | Seismic moment (in Nm)
3744 | Moment magnitude )
3745 | Eigenvalues
3746 | Eigenvectors
3747 | Tension-axis
3748 | Null-axis
3749 | Pressure-axis'''.splitlines()
3750 |
3751 | # for list of elements:
3752 | for label, arg in zip(labels, lo_args):
3753 | getter = getattr(MT, 'get_' + decomp_attrib_map[arg])
3754 | x = getter(style='y', system=out_system)
3755 | print '%s: %s' % (label, x)
3756 |
3757 | def _call_describe(MT, kwargs_dict):
3758 | print MT
3759 |
3760 | def _build_gmt_dict(options, optparser):
3761 | """
3762 | """
3763 | consistent_kwargs_dict = {}
3764 | temp_dict = {}
3765 | lo_allowed_options = ['GMT_string_type', 'GMT_scaling',
3766 | 'GMT_tension_colour', 'GMT_pressure_colour',
3767 | 'GMT_show_2FP2', 'GMT_show_1FP',
3768 | 'plot_viewpoint', 'GMT_plot_isotropic_part',
3769 | 'GMT_projection']
3770 |
3771 | # check for allowed options:
3772 | for ao in lo_allowed_options:
3773 | if hasattr(options, ao):
3774 | temp_dict[ao] = getattr(options, ao)
3775 |
3776 | if temp_dict['GMT_show_1FP']:
3777 | try:
3778 | if int(float(temp_dict['GMT_show_1FP'])) in [1, 2]:
3779 | consistent_kwargs_dict['_GMT_1fp'] = \
3780 | int(float(temp_dict['GMT_show_1FP']))
3781 | except:
3782 | pass
3783 |
3784 | if temp_dict['GMT_show_2FP2']:
3785 | temp_dict['GMT_show_1FP'] = 0
3786 |
3787 | consistent_kwargs_dict['_GMT_2fps'] = True
3788 | consistent_kwargs_dict['_GMT_1fp'] = 0
3789 |
3790 | if temp_dict['GMT_string_type'][0].lower() not in ['f', 'l', 'e']:
3791 | print 'type of desired string not known - taking "fill" instead'
3792 | consistent_kwargs_dict['_GMT_type'] = 'fill'
3793 |
3794 | else:
3795 | if temp_dict['GMT_string_type'][0] == 'f':
3796 | consistent_kwargs_dict['_GMT_type'] = 'fill'
3797 | elif temp_dict['GMT_string_type'][0] == 'l':
3798 | consistent_kwargs_dict['_GMT_type'] = 'lines'
3799 | else:
3800 | consistent_kwargs_dict['_GMT_type'] = 'EVs'
3801 |
3802 | if float(temp_dict['GMT_scaling']) < epsilon:
3803 | print 'GMT scaling factor must be a factor larger than'
3804 | print '%f - set to 1, due to obviously stupid input value' % \
3805 | (epsilon)
3806 | temp_dict['GMT_scaling'] = 1
3807 |
3808 | if temp_dict['plot_viewpoint']:
3809 | try:
3810 | vp = temp_dict['plot_viewpoint'].split(',')
3811 | if not len(vp) == 3:
3812 | raise
3813 | if not -90 <= float(vp[0]) <= 90:
3814 | raise
3815 | if not -180 <= float(vp[1]) <= 180:
3816 | raise
3817 | if not 0 <= float(vp[2]) % 360 <= 360:
3818 | raise
3819 | consistent_kwargs_dict['plot_viewpoint'] = \
3820 | [float(vp[0]), float(vp[1]), float(vp[2])]
3821 | except:
3822 | pass
3823 |
3824 | if temp_dict['GMT_projection']:
3825 | lo_allowed_projections = ['stereo', 'ortho', 'lambert'] # ,'gnom']
3826 | do_allowed_projections = dict(zip(('s', 'o', 'l', 'g'),
3827 | ('stereo', 'ortho',
3828 | 'lambert', 'gnom')))
3829 | try:
3830 | gmtp = temp_dict['GMT_projection'].lower()
3831 | if gmtp in lo_allowed_projections:
3832 | consistent_kwargs_dict['plot_projection'] = gmtp
3833 | elif gmtp in do_allowed_projections.keys():
3834 | consistent_kwargs_dict['plot_projection'] = \
3835 | do_allowed_projections[gmtp]
3836 | else:
3837 | consistent_kwargs_dict['plot_projection'] = 'stereo'
3838 | except:
3839 | pass
3840 |
3841 | consistent_kwargs_dict['_GMT_scaling'] = \
3842 | temp_dict['GMT_scaling']
3843 | consistent_kwargs_dict['_GMT_tension_colour'] = \
3844 | temp_dict['GMT_tension_colour']
3845 | consistent_kwargs_dict['_GMT_pressure_colour'] = \
3846 | temp_dict['GMT_pressure_colour']
3847 | consistent_kwargs_dict['_plot_isotropic_part'] = \
3848 | temp_dict['GMT_plot_isotropic_part']
3849 |
3850 | return consistent_kwargs_dict
3851 |
3852 | def _build_decompose_dict(options, optparser):
3853 |
3854 | consistent_kwargs_dict = {}
3855 | temp_dict = {}
3856 | lo_allowed_options = ['decomp_out_complete', 'decomp_out_fancy',
3857 | 'decomp_out_part', 'in_system', 'out_system',
3858 | 'decomp_key']
3859 |
3860 | # check for allowed options:
3861 | for ao in lo_allowed_options:
3862 | if hasattr(options, ao):
3863 | temp_dict[ao] = getattr(options, ao)
3864 |
3865 | for k in 'in_system', 'out_system':
3866 | s = getattr(options, k).upper()
3867 | if s not in lo_allowed_systems:
3868 | sys.exit('Unavailable coordinate system: %s' % s)
3869 |
3870 | consistent_kwargs_dict[k] = s
3871 |
3872 | consistent_kwargs_dict['decomposition_key'] = int(
3873 | temp_dict['decomp_key'])
3874 |
3875 | if temp_dict['decomp_out_part'] is None:
3876 | consistent_kwargs_dict['decomp_out_part'] = None
3877 | else:
3878 | parts = [x.strip().lower()
3879 | for x in temp_dict['decomp_out_part'].split(',')]
3880 | for part in parts:
3881 | if part not in decomp_attrib_map_keys:
3882 | sys.exit('Unavailable decomposition part: %s' % part)
3883 |
3884 | consistent_kwargs_dict['decomp_out_part'] = parts
3885 |
3886 | consistent_kwargs_dict['style'] = 'y'
3887 |
3888 | return consistent_kwargs_dict
3889 |
3890 | def _build_plot_dict(options, optparser):
3891 | consistent_kwargs_dict = {}
3892 | temp_dict = {}
3893 |
3894 | lo_allowed_options = [
3895 | 'plot_outfile', 'plot_pa_plot',
3896 | 'plot_full_sphere', 'plot_part_of_m', 'plot_viewpoint',
3897 | 'plot_projection', 'plot_show_upper_hemis', 'plot_n_points',
3898 | 'plot_size', 'plot_tension_colour', 'plot_pressure_colour',
3899 | 'plot_total_alpha', 'plot_show_faultplanes',
3900 | 'plot_show_1faultplane', 'plot_show_princ_axes',
3901 | 'plot_show_basis_axes', 'plot_outerline', 'plot_nodalline',
3902 | 'plot_dpi', 'plot_only_lines', 'plot_input_system',
3903 | 'plot_isotropic_part']
3904 |
3905 | # check for allowed options:
3906 | for ao in lo_allowed_options:
3907 | if hasattr(options, ao):
3908 | temp_dict[ao] = getattr(options, ao)
3909 |
3910 | consistent_kwargs_dict['plot_save_plot'] = False
3911 | if temp_dict['plot_outfile']:
3912 | consistent_kwargs_dict['plot_save_plot'] = True
3913 | lo_possible_formats = ['svg', 'png', 'eps', 'pdf', 'ps']
3914 |
3915 | try:
3916 | (filepath, filename) = os.path.split(temp_dict['plot_outfile'])
3917 | if not filename:
3918 | filename = 'dummy_filename.svg'
3919 | (shortname, extension) = os.path.splitext(filename)
3920 | if not shortname:
3921 | shortname = 'dummy_shortname'
3922 |
3923 | if extension[1:].lower() in lo_possible_formats:
3924 | consistent_kwargs_dict['plot_outfile_format'] = \
3925 | extension[1:].lower()
3926 |
3927 | if shortname.endswith('.'):
3928 | consistent_kwargs_dict['plot_outfile'] = \
3929 | os.path.realpath(os.path.abspath(os.path.join(
3930 | os.curdir, filepath,
3931 | shortname + extension[1:].lower())))
3932 | else:
3933 | consistent_kwargs_dict['plot_outfile'] = \
3934 | os.path.realpath(os.path.abspath(os.path.join(
3935 | os.curdir, filepath, shortname + '.' +
3936 | extension[1:].lower())))
3937 | else:
3938 | if filename.endswith('.'):
3939 | consistent_kwargs_dict['plot_outfile'] = \
3940 | os.path.realpath(os.path.abspath(os.path.join(
3941 | os.curdir, filepath,
3942 | filename + lo_possible_formats[0])))
3943 | else:
3944 | consistent_kwargs_dict['plot_outfile'] = \
3945 | os.path.realpath(os.path.abspath(os.path.join(
3946 | os.curdir, filepath, filename + '.' +
3947 | lo_possible_formats[0])))
3948 | consistent_kwargs_dict['plot_outfile_format'] = \
3949 | lo_possible_formats[0]
3950 |
3951 | except:
3952 | msg = 'please provide valid filename: . !!\n'
3953 | msg += ' must be svg, png, eps, pdf, or ps '
3954 | exit(msg)
3955 |
3956 | if temp_dict['plot_pa_plot']:
3957 | consistent_kwargs_dict['plot_pa_plot'] = True
3958 | else:
3959 | consistent_kwargs_dict['plot_pa_plot'] = False
3960 |
3961 | if temp_dict['plot_full_sphere']:
3962 | consistent_kwargs_dict['plot_full_sphere'] = True
3963 | consistent_kwargs_dict['plot_pa_plot'] = False
3964 | else:
3965 | consistent_kwargs_dict['plot_full_sphere'] = False
3966 |
3967 | if temp_dict['plot_part_of_m']:
3968 | try:
3969 | plottable_part_raw = temp_dict['plot_part_of_m'].lower()[:2]
3970 | if plottable_part_raw == 'is':
3971 | plottable_part = 'iso'
3972 | elif plottable_part_raw == 'de':
3973 | plottable_part = 'devi'
3974 | elif plottable_part_raw == 'dc':
3975 | plottable_part = 'dc'
3976 | elif plottable_part_raw == 'cl':
3977 | plottable_part = 'clvd'
3978 | else:
3979 | plottable_part = False
3980 |
3981 | consistent_kwargs_dict['plot_part_of_m'] = plottable_part
3982 |
3983 | except:
3984 | consistent_kwargs_dict['plot_part_of_m'] = False
3985 |
3986 | else:
3987 | consistent_kwargs_dict['plot_part_of_m'] = False
3988 |
3989 | if temp_dict['plot_viewpoint']:
3990 | try:
3991 | vp = temp_dict['plot_viewpoint'].split(',')
3992 | if not len(vp) == 3:
3993 | raise
3994 | if not -90 <= float(vp[0]) <= 90:
3995 | raise
3996 | if not -180 <= float(vp[1]) <= 180:
3997 | raise
3998 | if not 0 <= float(vp[2]) % 360 <= 360:
3999 | raise
4000 | consistent_kwargs_dict['plot_viewpoint'] = \
4001 | [float(vp[0]), float(vp[1]), float(vp[2])]
4002 | except:
4003 | pass
4004 |
4005 | if temp_dict['plot_projection']:
4006 | lo_allowed_projections = ['stereo', 'ortho', 'lambert'] # ,'gnom']
4007 | do_allowed_projections = dict(zip(('s', 'o', 'l', 'g'),
4008 | ('stereo', 'ortho',
4009 | 'lambert', 'gnom')))
4010 | try:
4011 | ppl = temp_dict['plot_projection'].lower()
4012 | if ppl in lo_allowed_projections:
4013 | consistent_kwargs_dict['plot_projection'] = ppl
4014 | elif ppl in do_allowed_projections.keys():
4015 | consistent_kwargs_dict['plot_projection'] = \
4016 | do_allowed_projections[ppl]
4017 | else:
4018 | consistent_kwargs_dict['plot_projection'] = 'stereo'
4019 | except:
4020 | pass
4021 |
4022 | if temp_dict['plot_show_upper_hemis']:
4023 | consistent_kwargs_dict['plot_show_upper_hemis'] = True
4024 |
4025 | if temp_dict['plot_n_points']:
4026 | try:
4027 | if temp_dict['plot_n_points'] > 360:
4028 | consistent_kwargs_dict['plot_n_points'] = \
4029 | int(temp_dict['plot_n_points'])
4030 | except:
4031 | pass
4032 |
4033 | if temp_dict['plot_size']:
4034 | try:
4035 | if 0.01 < temp_dict['plot_size'] <= 1:
4036 | consistent_kwargs_dict['plot_size'] = \
4037 | temp_dict['plot_size'] * 10 / 2.54
4038 | elif 1 < temp_dict['plot_size'] < 45:
4039 | consistent_kwargs_dict['plot_size'] = \
4040 | temp_dict['plot_size'] / 2.54
4041 | else:
4042 | consistent_kwargs_dict['plot_size'] = 5
4043 | consistent_kwargs_dict['plot_aux_plot_size'] = \
4044 | consistent_kwargs_dict['plot_size']
4045 | except:
4046 | pass
4047 |
4048 | if temp_dict['plot_pressure_colour']:
4049 | try:
4050 | sec_colour_raw = temp_dict['plot_pressure_colour'].split(',')
4051 | if len(sec_colour_raw) == 1:
4052 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'):
4053 | consistent_kwargs_dict['plot_pressure_colour'] = \
4054 | sec_colour_raw[0].lower()[0]
4055 | else:
4056 | raise
4057 | elif len(sec_colour_raw) == 3:
4058 | for sc in sec_colour_raw:
4059 | if not 0 <= (int(sc)) <= 255:
4060 | raise
4061 | consistent_kwargs_dict['plot_pressure_colour'] = \
4062 | (float(sec_colour_raw[0]) / 255.,
4063 | float(sec_colour_raw[1]) / 255.,
4064 | float(sec_colour_raw[2]) / 255.)
4065 | else:
4066 | raise
4067 | except:
4068 | pass
4069 |
4070 | if temp_dict['plot_tension_colour']:
4071 | try:
4072 | sec_colour_raw = temp_dict['plot_tension_colour'].split(',')
4073 | if len(sec_colour_raw) == 1:
4074 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'):
4075 | consistent_kwargs_dict['plot_tension_colour'] = \
4076 | sec_colour_raw[0].lower()[0]
4077 | else:
4078 | raise
4079 | elif len(sec_colour_raw) == 3:
4080 | for sc in sec_colour_raw:
4081 | if not 0 <= (int(float(sc))) <= 255:
4082 | raise
4083 | consistent_kwargs_dict['plot_tension_colour'] = \
4084 | (float(sec_colour_raw[0]) / 255.,
4085 | float(sec_colour_raw[1]) / 255.,
4086 | float(sec_colour_raw[2]) / 255.)
4087 | else:
4088 | raise
4089 | except:
4090 | pass
4091 |
4092 | if temp_dict['plot_total_alpha']:
4093 | try:
4094 | if not 0 <= float(temp_dict['plot_total_alpha']) <= 1:
4095 | consistent_kwargs_dict['plot_total_alpha'] = 1
4096 | else:
4097 | consistent_kwargs_dict['plot_total_alpha'] = \
4098 | float(temp_dict['plot_total_alpha'])
4099 | except:
4100 | pass
4101 |
4102 | if temp_dict['plot_show_1faultplane']:
4103 | consistent_kwargs_dict['plot_show_1faultplane'] = True
4104 | try:
4105 | fp_args = temp_dict['plot_show_1faultplane']
4106 |
4107 | if not int(fp_args[0]) in [1, 2]:
4108 | consistent_kwargs_dict['plot_show_FP_index'] = 1
4109 | else:
4110 | consistent_kwargs_dict['plot_show_FP_index'] = \
4111 | int(fp_args[0])
4112 |
4113 | if not 0 < float(fp_args[1]) <= 20:
4114 | consistent_kwargs_dict['plot_faultplane_width'] = 2
4115 | else:
4116 | consistent_kwargs_dict['plot_faultplane_width'] = \
4117 | float(fp_args[1])
4118 |
4119 | try:
4120 | sec_colour_raw = fp_args[2].split(',')
4121 | if len(sec_colour_raw) == 1:
4122 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'):
4123 | consistent_kwargs_dict['plot_faultplane_colour'] =\
4124 | sec_colour_raw[0].lower()[0]
4125 | else:
4126 | raise
4127 | elif len(sec_colour_raw) == 3:
4128 | for sc in sec_colour_raw:
4129 | if not 0 <= (int(sc)) <= 255:
4130 | raise
4131 | consistent_kwargs_dict['plot_faultplane_colour'] = \
4132 | (float(sec_colour_raw[0]) / 255.,
4133 | float(sec_colour_raw[1]) / 255.,
4134 | float(sec_colour_raw[2]) / 255.)
4135 | else:
4136 | raise
4137 | except:
4138 | consistent_kwargs_dict['plot_faultplane_colour'] = 'k'
4139 |
4140 | try:
4141 | if 0 <= float(fp_args[3]) <= 1:
4142 | consistent_kwargs_dict['plot_faultplane_alpha'] = \
4143 | float(fp_args[3])
4144 | except:
4145 | consistent_kwargs_dict['plot_faultplane_alpha'] = 1
4146 | except:
4147 | pass
4148 |
4149 | if temp_dict['plot_show_faultplanes']:
4150 | consistent_kwargs_dict['plot_show_faultplanes'] = True
4151 | consistent_kwargs_dict['plot_show_1faultplane'] = False
4152 |
4153 | if temp_dict['plot_dpi']:
4154 | try:
4155 | if 200 <= int(temp_dict['plot_dpi']) <= 2000:
4156 | consistent_kwargs_dict['plot_dpi'] = \
4157 | int(temp_dict['plot_dpi'])
4158 | else:
4159 | raise
4160 | except:
4161 | pass
4162 |
4163 | if temp_dict['plot_only_lines']:
4164 | consistent_kwargs_dict['plot_fill_flag'] = False
4165 |
4166 | if temp_dict['plot_outerline']:
4167 | consistent_kwargs_dict['plot_outerline'] = True
4168 | try:
4169 | fp_args = temp_dict['plot_outerline']
4170 | if not 0 < float(fp_args[0]) <= 20:
4171 | consistent_kwargs_dict['plot_outerline_width'] = 2
4172 | else:
4173 | consistent_kwargs_dict['plot_outerline_width'] = \
4174 | float(fp_args[0])
4175 | try:
4176 | sec_colour_raw = fp_args[1].split(',')
4177 | if len(sec_colour_raw) == 1:
4178 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'):
4179 | consistent_kwargs_dict['plot_outerline_colour'] = \
4180 | sec_colour_raw[0].lower()[0]
4181 | else:
4182 | raise
4183 | elif len(sec_colour_raw) == 3:
4184 | for sc in sec_colour_raw:
4185 | if not 0 <= (int(sc)) <= 255:
4186 | raise
4187 | consistent_kwargs_dict['plot_outerline_colour'] = \
4188 | (float(sec_colour_raw[0]) / 255.,
4189 | float(sec_colour_raw[1]) / 255.,
4190 | float(sec_colour_raw[2]) / 255.)
4191 | else:
4192 | raise
4193 | except:
4194 | consistent_kwargs_dict['plot_outerline_colour'] = 'k'
4195 |
4196 | try:
4197 | if 0 <= float(fp_args[2]) <= 1:
4198 | consistent_kwargs_dict['plot_outerline_alpha'] = \
4199 | float(fp_args[2])
4200 | except:
4201 | consistent_kwargs_dict['plot_outerline_alpha'] = 1
4202 | except:
4203 | pass
4204 |
4205 | if temp_dict['plot_nodalline']:
4206 | consistent_kwargs_dict['plot_nodalline'] = True
4207 | try:
4208 | fp_args = temp_dict['plot_nodalline']
4209 |
4210 | if not 0 < float(fp_args[0]) <= 20:
4211 | consistent_kwargs_dict['plot_nodalline_width'] = 2
4212 | else:
4213 | consistent_kwargs_dict['plot_nodalline_width'] = \
4214 | float(fp_args[0])
4215 | try:
4216 | sec_colour_raw = fp_args[1].split(',')
4217 | if len(sec_colour_raw) == 1:
4218 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'):
4219 | consistent_kwargs_dict['plot_nodalline_colour'] = \
4220 | sec_colour_raw[0].lower()[0]
4221 | else:
4222 | raise
4223 | elif len(sec_colour_raw) == 3:
4224 | for sc in sec_colour_raw:
4225 | if not 0 <= (int(sc)) <= 255:
4226 | raise
4227 | consistent_kwargs_dict['plot_nodalline_colour'] = \
4228 | (float(sec_colour_raw[0]) / 255.,
4229 | float(sec_colour_raw[1]) / 255.,
4230 | float(sec_colour_raw[2]) / 255.)
4231 | else:
4232 | raise
4233 | except:
4234 | consistent_kwargs_dict['plot_nodalline_colour'] = 'k'
4235 | try:
4236 | if 0 <= float(fp_args[2]) <= 1:
4237 | consistent_kwargs_dict['plot_nodalline_alpha'] = \
4238 | float(fp_args[2])
4239 | except:
4240 | consistent_kwargs_dict['plot_nodalline_alpha'] = 1
4241 | except:
4242 | pass
4243 |
4244 | if temp_dict['plot_show_princ_axes']:
4245 | consistent_kwargs_dict['plot_show_princ_axes'] = True
4246 | try:
4247 | fp_args = temp_dict['plot_show_princ_axes']
4248 |
4249 | if not 0 < float(fp_args[0]) <= 40:
4250 | consistent_kwargs_dict['plot_princ_axes_symsize'] = 10
4251 | else:
4252 | consistent_kwargs_dict['plot_princ_axes_symsize'] = \
4253 | float(fp_args[0])
4254 |
4255 | if not 0 < float(fp_args[1]) <= 20:
4256 | consistent_kwargs_dict['plot_princ_axes_lw '] = 3
4257 | else:
4258 | consistent_kwargs_dict['plot_princ_axes_lw '] = \
4259 | float(fp_args[1])
4260 | try:
4261 | if 0 <= float(fp_args[2]) <= 1:
4262 | consistent_kwargs_dict['plot_princ_axes_alpha'] = \
4263 | float(fp_args[2])
4264 | except:
4265 | consistent_kwargs_dict['plot_princ_axes_alpha'] = 1
4266 | except:
4267 | pass
4268 |
4269 | if temp_dict['plot_show_basis_axes']:
4270 | consistent_kwargs_dict['plot_show_basis_axes'] = True
4271 |
4272 | if temp_dict['plot_input_system']:
4273 | lo_allowed_systems = ['XYZ', 'NED', 'USE', 'NWU']
4274 | try:
4275 | tpis = temp_dict['plot_input_system'][:3].upper()
4276 | if tpis in lo_allowed_systems:
4277 | consistent_kwargs_dict['in_system'] = tpis
4278 | else:
4279 | raise
4280 | except:
4281 | pass
4282 |
4283 | if temp_dict['plot_isotropic_part']:
4284 | consistent_kwargs_dict['plot_isotropic_part'] = \
4285 | temp_dict['plot_isotropic_part']
4286 |
4287 | return consistent_kwargs_dict
4288 |
4289 | def _build_describe_dict(options, optparser):
4290 | consistent_kwargs_dict = {}
4291 |
4292 | for k in 'in_system', 'out_system':
4293 | s = getattr(options, k).upper()
4294 | if s not in lo_allowed_systems:
4295 | sys.exit('Unavailable coordinate system: %s' % s)
4296 |
4297 | consistent_kwargs_dict[k] = s
4298 |
4299 | return consistent_kwargs_dict
4300 |
4301 | def _parse_arguments(main_call, its_arguments, optparser):
4302 | """
4303 | """
4304 | # todo:
4305 | # print '\n', main_call,its_arguments,'\n'
4306 | (options, args) = optparser.parse_args(its_arguments)
4307 |
4308 | # todo
4309 | # check, if arguments do not start with "-" - if so, there is a lack of
4310 | # arguments for the previous option
4311 | for val2check in options.__dict__.values():
4312 | if str(val2check).startswith('-'):
4313 | try:
4314 | val2check_split = val2check.split(',')
4315 | for ii in val2check_split:
4316 | float(ii)
4317 | except:
4318 | sys.exit('ERROR - check carefully number of arguments ' +
4319 | 'for all options\n')
4320 |
4321 | if main_call == 'plot':
4322 | consistent_kwargs_dict = _build_plot_dict(options, optparser)
4323 |
4324 | elif main_call == 'gmt':
4325 | consistent_kwargs_dict = _build_gmt_dict(options, optparser)
4326 |
4327 | elif main_call == 'decompose':
4328 | consistent_kwargs_dict = _build_decompose_dict(options, optparser)
4329 |
4330 | elif main_call == 'describe':
4331 | consistent_kwargs_dict = _build_describe_dict(options, optparser)
4332 |
4333 | return consistent_kwargs_dict
4334 |
4335 | def _add_group_system(parent):
4336 | group_system = OptionGroup(parent, 'Basis systems')
4337 | group_system.add_option('-i', '--input-system',
4338 | action="store",
4339 | dest='in_system',
4340 | metavar='',
4341 | default='NED',
4342 | help='''
4343 | Define the coordinate system of the source mechanism [Default: NED].
4344 |
4345 | Available coordinate systems:
4346 |
4347 | * NED: North, East, Down
4348 | * USE: Up, South, East (Global CMT)
4349 | * XYZ: East, North, Up (Jost and Herrmann)
4350 | * NWU: North, West, Up (Stein and Wysession)
4351 | '''.lstrip())
4352 |
4353 | group_system.add_option('-o', '--output-system',
4354 | action="store",
4355 | dest='out_system',
4356 | metavar='',
4357 | default='NED',
4358 | help='''
4359 | Define the coordinate system of the output. See '--input-system' for a list of
4360 | available coordinate systems [Default: NED].'''.lstrip())
4361 |
4362 | parent.add_option_group(group_system)
4363 |
4364 | def _build_optparsers():
4365 |
4366 | _do_parsers = {}
4367 |
4368 | desc = """
4369 | Generate a beachball representation which can be plotted with GMT.
4370 |
4371 | This tool produces output which can be fed into the GMT command `psxy`. The
4372 | output consists of coordinates which describe the lines of the beachball in
4373 | standard cartesian coordinates, centered at zero.
4374 |
4375 | In order to generate a beachball diagram, this tool has to be called twice with
4376 | different arguments of the --type option. First to define the colored areas
4377 | (--type=fill) and second for the nodal and border lines (--type=lines).
4378 |
4379 | Example:
4380 |
4381 | mopad gmt 30,60,90 --type=fill | \
4382 | psxy -Jx4/4 -R-2/2/-2/2 -P -Cpsxy_fill.cpt -M -L -K > out.ps
4383 |
4384 | mopad gmt 30,60,90 --type=lines | \
4385 | psxy -Jx4/4 -R-2/2/-2/2 -P -Cpsxy_lines.cpt -W2p -P -M -O >> out.ps
4386 |
4387 | """
4388 |
4389 | parser_gmt = OptionParser(
4390 | usage='mopad.py gmt [options]',
4391 | description=desc, formatter=MopadHelpFormatter())
4392 |
4393 | group_type = OptionGroup(parser_gmt, 'Output')
4394 | group_show = OptionGroup(parser_gmt, 'Appearance')
4395 | group_geo = OptionGroup(parser_gmt, 'Geometry')
4396 |
4397 | group_type.add_option('-t', '--type',
4398 | type='string',
4399 | dest='GMT_string_type',
4400 | action='store',
4401 | default='fill',
4402 | help='Chosing the respective psxy data set: '
4403 | 'area to fill (fill), nodal lines (lines), '
4404 | 'or eigenvector positions (ev) [Default: fill]',
4405 | metavar='')
4406 |
4407 | group_show.add_option('-s', '--scaling',
4408 | dest='GMT_scaling',
4409 | action='store',
4410 | default='1',
4411 | type='float',
4412 | metavar='',
4413 | help='Spatial scaling factor of the beachball '
4414 | '[Default: 1]')
4415 | group_show.add_option('-r', '--colour1',
4416 | dest='GMT_tension_colour',
4417 | type='int',
4418 | action='store',
4419 | metavar='',
4420 | default='1',
4421 | help="-Z option's key (see help for 'psxy') "
4422 | 'for the tension colour of the beachball - '
4423 | 'type: integer [Default: 1]')
4424 | group_show.add_option('-w', '--colour2',
4425 | dest='GMT_pressure_colour',
4426 | type='int',
4427 | action='store',
4428 | metavar='',
4429 | default='0',
4430 | help="-Z option's key (see help for 'psxy') "
4431 | 'for the pressure colour of the beachball - '
4432 | 'type: integer [Default: 0]')
4433 | group_show.add_option('-D', '--faultplanes',
4434 | dest='GMT_show_2FP2',
4435 | action='store_true',
4436 | default=False,
4437 | help='Key, if 2 faultplanes shall be shown '
4438 | '[Default: deactivated]')
4439 | group_show.add_option('-d', '--show_1fp',
4440 | type='choice',
4441 | dest='GMT_show_1FP',
4442 | choices=['1', '2'],
4443 | metavar='',
4444 | action='store',
4445 | default=False,
4446 | help='Key for plotting 1 specific faultplane - '
4447 | 'value: 1,2 [Default: None]')
4448 | group_geo.add_option('-v', '--viewpoint',
4449 | action='store',
4450 | dest='plot_viewpoint',
4451 | metavar='',
4452 | default=None,
4453 | help='Coordinates (in degrees) of the viewpoint '
4454 | 'onto the projection - type: comma separated '
4455 | '3-tuple [Default: None]')
4456 | group_geo.add_option('-p', '--projection',
4457 | action='store',
4458 | dest='GMT_projection',
4459 | metavar='',
4460 | default=None,
4461 | help='Two-dimensional projection of the sphere - '
4462 | 'value: (s)tereographic, (l)ambert, '
4463 | '(o)rthographic [Default: (s)tereographic]')
4464 | group_show.add_option('-I', '--show_isotropic_part',
4465 | dest='GMT_plot_isotropic_part',
4466 | action='store_true',
4467 | default=False,
4468 | help='Key for considering the isotropic part '
4469 | 'for plotting [Default: deactivated]')
4470 |
4471 | parser_gmt.add_option_group(group_type)
4472 | parser_gmt.add_option_group(group_show)
4473 | parser_gmt.add_option_group(group_geo)
4474 |
4475 | _do_parsers['gmt'] = parser_gmt
4476 |
4477 | # plot
4478 | desc_plot = """
4479 | Plot a beachball diagram of the provided mechanism.
4480 |
4481 | Several styles and configurations are available. Also saving
4482 | on the fly can be enabled.
4483 | ONLY THE DEVIATORIC COMPONENT WILL BE PLOTTED by default;
4484 | for including the isotropic part, use the '--show_isotropic_part'
4485 | option!
4486 | """
4487 | parser_plot = OptionParser(
4488 | usage="mopad.py plot [options]",
4489 | description=desc_plot, formatter=MopadHelpFormatter())
4490 |
4491 | group_save = OptionGroup(parser_plot, 'Saving')
4492 | group_type = OptionGroup(parser_plot, 'Type of plot')
4493 | group_quality = OptionGroup(parser_plot, 'Quality')
4494 | group_colours = OptionGroup(parser_plot, 'Colours')
4495 | group_misc = OptionGroup(parser_plot, 'Miscellaneous')
4496 | group_dc = OptionGroup(parser_plot, 'Fault planes')
4497 | group_geo = OptionGroup(parser_plot, 'Geometry')
4498 | group_app = OptionGroup(parser_plot, 'Appearance')
4499 |
4500 | group_save.add_option('-f', '--output_file',
4501 | action="store",
4502 | dest='plot_outfile',
4503 | metavar='',
4504 | default=None,
4505 | nargs=1,
4506 | help='(Absolute) filename for saving '
4507 | '[Default: None]')
4508 |
4509 | group_type.add_option('-E', '--eigen_system',
4510 | action="store_true",
4511 | dest='plot_pa_plot',
4512 | default=False,
4513 | help='Key for plotting principal axis '
4514 | 'system/eigensystem [Default: deactivated]')
4515 |
4516 | group_type.add_option('-O', '--full_sphere',
4517 | action="store_true",
4518 | dest='plot_full_sphere',
4519 | default=False,
4520 | help='Key for plotting the full sphere '
4521 | '[Default: deactivated]')
4522 |
4523 | group_type.add_option('-P', '--partial',
4524 | action="store",
4525 | dest='plot_part_of_m',
4526 | metavar='',
4527 | default=None,
4528 | help='Key for plotting only a specific part of '
4529 | 'M - values: iso,devi,dc,clvd [Default: None] ')
4530 |
4531 | group_geo.add_option('-v', '--viewpoint',
4532 | action="store",
4533 | dest='plot_viewpoint',
4534 | metavar='',
4535 | default=None,
4536 | help='Coordinates (in degrees) of the viewpoint '
4537 | 'onto the projection - type: comma separated '
4538 | '3-tuple [Default: None]')
4539 |
4540 | group_geo.add_option('-p', '--projection',
4541 | action="store",
4542 | dest='plot_projection',
4543 | metavar='',
4544 | default=None,
4545 | help='Two-dimensional projection of the sphere '
4546 | '- value: (s)tereographic, (l)ambert, '
4547 | '(o)rthographic [Default: (s)tereographic]')
4548 |
4549 | group_type.add_option('-U', '--upper',
4550 | action="store_true",
4551 | dest='plot_show_upper_hemis',
4552 | default=False,
4553 | help='Key for plotting the upper hemisphere '
4554 | '[Default: deactivated]')
4555 |
4556 | group_quality.add_option('-N', '--points',
4557 | action="store",
4558 | metavar='',
4559 | dest='plot_n_points',
4560 | type="int",
4561 | default=None,
4562 | help='Minimum number of points, used for '
4563 | 'nodal lines [Default: None]')
4564 |
4565 | group_app.add_option('-s', '--size',
4566 | action="store",
4567 | dest='plot_size',
4568 | metavar='',
4569 | type="float",
4570 | default=None,
4571 | help='Size of plot (diameter) in cm '
4572 | '[Default: None]')
4573 |
4574 | group_colours.add_option('-w', '--pressure_colour',
4575 | action="store",
4576 | dest='plot_pressure_colour',
4577 | metavar='',
4578 | default=None,
4579 | help='Colour of the tension area - values: '
4580 | 'comma separated RGB 3-tuples OR MATLAB '
4581 | 'conform colour names [Default: None]')
4582 |
4583 | group_colours.add_option('-r', '--tension_colour',
4584 | action="store",
4585 | dest='plot_tension_colour',
4586 | metavar='',
4587 | default=None,
4588 | help='Colour of the pressure area values: '
4589 | 'comma separated RGB 3-tuples OR MATLAB '
4590 | 'conform colour names [Default: None]')
4591 |
4592 | group_app.add_option('-a', '--alpha',
4593 | action="store",
4594 | dest='plot_total_alpha',
4595 | metavar='',
4596 | type='float',
4597 | default=None,
4598 | help='Alpha value for the total plot - value: '
4599 | 'float between 1=opaque to 0=transparent '
4600 | '[Default: None]')
4601 |
4602 | group_dc.add_option('-D', '--dc',
4603 | action="store_true",
4604 | dest='plot_show_faultplanes',
4605 | default=False,
4606 | help='Key for plotting both double couple '
4607 | 'faultplanes (blue) [Default: deactivated]')
4608 |
4609 | group_dc.add_option('-d', '--show1fp',
4610 | action="store",
4611 | metavar=' ',
4612 | dest='plot_show_1faultplane',
4613 | default=None,
4614 | nargs=4,
4615 | help='Key for plotting 1 specific faultplane - '
4616 | '4 arguments as space separated list - '
4617 | 'index values: 1,2, linewidth value: float, '
4618 | 'line colour value: string or RGB-3-tuple, alpha '
4619 | 'value: float between 0 and 1 [Default: None] ')
4620 |
4621 | group_misc.add_option('-e', '--eigenvectors',
4622 | action="store",
4623 | dest='plot_show_princ_axes',
4624 | metavar=' ',
4625 | default=None,
4626 | nargs=3,
4627 | help='Key for showing eigenvectors - '
4628 | '3 arguments as space separated list - symbol '
4629 | 'size value: float, symbol linewidth value: '
4630 | 'float, symbol alpha value: float between 0 '
4631 | 'and 1 [Default: None]')
4632 |
4633 | group_misc.add_option('-b', '--basis_vectors',
4634 | action="store_true",
4635 | dest='plot_show_basis_axes',
4636 | default=False,
4637 | help='Key for showing NED basis axes in plot '
4638 | '[Default: deactivated]')
4639 |
4640 | group_app.add_option('-l', '--lines',
4641 | action="store",
4642 | dest='plot_outerline',
4643 | metavar=' ',
4644 | nargs=3,
4645 | default=None,
4646 | help='Define the style of the outer line - '
4647 | '3 arguments as space separated list - '
4648 | 'linewidth value: float, line colour value: '
4649 | 'string or RGB-3-tuple), alpha value: float '
4650 | 'between 0 and 1 [Default: None]')
4651 |
4652 | group_app.add_option('-n', '--nodals',
4653 | action="store",
4654 | dest='plot_nodalline',
4655 | metavar=' ',
4656 | default=None,
4657 | nargs=3,
4658 | help='Define the style of the nodal lines - 3 '
4659 | 'arguments as space separated list - linewidth '
4660 | 'value: float, line colour value: string or '
4661 | 'RGB-3-tuple), alpha value: float between 0 and '
4662 | '1 [Default: None]')
4663 |
4664 | group_quality.add_option('-Q', '--quality',
4665 | action="store",
4666 | dest='plot_dpi',
4667 | metavar='',
4668 | type="int",
4669 | default=None,
4670 | help='Set the quality for the plot in '
4671 | 'terms of dpi (minimum=200) [Default: None] ')
4672 |
4673 | group_type.add_option('-L', '--lines_only',
4674 | action="store_true",
4675 | dest='plot_only_lines',
4676 | default=False,
4677 | help='Key for plotting lines only (no filling '
4678 | '- this overwrites all "fill"-related options) '
4679 | '[Default: deactivated] ')
4680 |
4681 | group_misc.add_option('-i', '--input-system',
4682 | action="store",
4683 | dest='plot_input_system',
4684 | metavar='',
4685 | default=False,
4686 | help='Define the coordinate system of the '
4687 | 'source mechanism - value: NED,USE,XYZ,NWU '
4688 | '[Default: NED] ')
4689 |
4690 | group_type.add_option('-I', '--show_isotropic_part',
4691 | dest='plot_isotropic_part',
4692 | action='store_true',
4693 | default=False,
4694 | help='Key for considering the isotropic part '
4695 | 'for plotting [Default: deactivated]')
4696 |
4697 | parser_plot.add_option_group(group_save)
4698 | parser_plot.add_option_group(group_type)
4699 | parser_plot.add_option_group(group_quality)
4700 | parser_plot.add_option_group(group_colours)
4701 | parser_plot.add_option_group(group_misc)
4702 | parser_plot.add_option_group(group_dc)
4703 | parser_plot.add_option_group(group_geo)
4704 | parser_plot.add_option_group(group_app)
4705 |
4706 | _do_parsers['plot'] = parser_plot
4707 |
4708 | desc_decomp = """
4709 | Decompose moment tensor into additive contributions.
4710 |
4711 | This method implements four different decompositions following the conventions
4712 | given by Jost & Herrmann (1998), and Dahm (1997). The type of decomposition can
4713 | be selected with the '--type' option. Use the '--partial' option, if only parts
4714 | of the full decomposition are required.
4715 |
4716 | By default, the decomposition results are printed in the following order:
4717 |
4718 | * 01 - basis of the provided input (string)
4719 | * 02 - basis of the representation (string)
4720 | * 03 - chosen decomposition type (integer)
4721 |
4722 | * 04 - full moment tensor (matrix)
4723 |
4724 | * 05 - isotropic part (matrix)
4725 | * 06 - isotropic percentage (float)
4726 | * 07 - deviatoric part (matrix)
4727 | * 08 - deviatoric percentage (float)
4728 |
4729 | * 09 - DC part (matrix)
4730 | * 10 - DC percentage (float)
4731 | * 11 - DC2 part (matrix)
4732 | * 12 - DC2 percentage (float)
4733 | * 13 - DC3 part (matrix)
4734 | * 14 - DC3 percentage (float)
4735 |
4736 | * 15 - CLVD part (matrix)
4737 | * 16 - CLVD percentage (matrix)
4738 |
4739 | * 17 - seismic moment (float)
4740 | * 18 - moment magnitude (float)
4741 |
4742 | * 19 - eigenvectors (3-array)
4743 | * 20 - eigenvalues (list)
4744 | * 21 - p-axis (3-array)
4745 | * 22 - neutral axis (3-array)
4746 | * 23 - t-axis (3-array)
4747 | """
4748 |
4749 | parser_decompose = OptionParser(
4750 | usage="mopad decompose [options]",
4751 | description=desc_decomp, formatter=MopadHelpFormatter())
4752 |
4753 | group_type = OptionGroup(
4754 | parser_decompose, 'Type of decomposition')
4755 | group_part = OptionGroup(
4756 | parser_decompose, 'Output selection')
4757 |
4758 | group_part.add_option('-p', '--partial',
4759 | action="store",
4760 | dest='decomp_out_part',
4761 | default=None,
4762 | metavar='',
4763 | help='''
4764 | Print a subset of the decomposition results.
4765 |
4766 | Give a comma separated list of what parts of the results should be
4767 | printed [Default: None]. The following parts are available:
4768 |
4769 | %s
4770 | ''' % ', '.join(decomp_attrib_map_keys))
4771 |
4772 | group_type.add_option('-t', '--type',
4773 | action="store",
4774 | dest='decomp_key',
4775 | metavar='',
4776 | default=1,
4777 | type='int',
4778 | help='''
4779 | Choose type of decomposition - values 1,2,3,4 \n[Default: 1]:
4780 |
4781 | %s
4782 | ''' % '\n'.join([' * %s - %s' % (k, v[0]) for (k, v)
4783 | in MomentTensor.decomp_dict.items()]))
4784 |
4785 | parser_decompose.add_option_group(group_type)
4786 | parser_decompose.add_option_group(group_part)
4787 | _add_group_system(parser_decompose)
4788 |
4789 | _do_parsers['decompose'] = parser_decompose
4790 |
4791 | parser_describe = OptionParser(
4792 | usage="mopad describe [options]",
4793 | description='''
4794 | Print the detailed description of a source mechanism
4795 |
4796 |
4797 | For a given source mechanism, orientations of the fault planes, moment,
4798 | magnitude, and moment tensor are printed. Input and output coordinate basis
4799 | systems can be specified.'''.lstrip(),
4800 | formatter=MopadHelpFormatter())
4801 |
4802 | _add_group_system(parser_describe)
4803 |
4804 | _do_parsers['describe'] = parser_describe
4805 |
4806 | return _do_parsers
4807 |
4808 | if len(sys.argv) < 2:
4809 | call = 'help'
4810 |
4811 | else:
4812 |
4813 | call = sys.argv[1].lower()
4814 | abbrev = dict(zip(('p', 'g', 'd', 'i', '--help', '-h'), (
4815 | 'plot', 'gmt', 'decompose', 'describe', 'help', 'help')))
4816 |
4817 | if call in abbrev:
4818 | call = abbrev[call]
4819 |
4820 | if call not in abbrev.values():
4821 | sys.exit('no such method: %s' % call)
4822 |
4823 | if call == 'help':
4824 | helpstring = """
4825 |
4826 | Usage: mopad [options]
4827 |
4828 |
4829 | Type 'mopad --help' for help on a specific method.
4830 |
4831 |
4832 | MoPaD (version %.1f) - Moment Tensor Plotting and Decomposition Tool
4833 |
4834 | MoPaD is a tool to plot and decompose moment tensor representations of seismic
4835 | sources which are commonly used in seismology. This tool is completely
4836 | controlled via command line parameters, which consist of a and a
4837 | argument and zero or more options. The argument
4838 | tells MoPaD what to do and the argument specifies an input
4839 | moment tensor source in one of the formats described below.
4840 |
4841 | Available methods:
4842 |
4843 | * plot: plot a beachball representation of a source mechanism
4844 | * describe: print detailed description of a source mechanism
4845 | * decompose: decompose a source mechanism according to various conventions
4846 | * gmt: output beachball representation in a format suitable for
4847 | plotting with GMT
4848 |
4849 | The source-mechanism is given as a comma separated list (NO BLANK SPACES!) of
4850 | values which is interpreted differently, according to the number of values in
4851 | the list. The following source-mechanism representations are available:
4852 |
4853 | * strike,dip,rake
4854 | * strike,dip,rake,moment
4855 | * M11,M22,M33,M12,M13,M23
4856 | * M11,M22,M33,M12,M13,M23,moment
4857 | * M11,M12,M13,M21,M22,M23,M31,M32,M33
4858 |
4859 | Angles are given in degrees, moment tensor components and scalar moment are
4860 | given in [Nm] for a coordinate system with axes pointing North, East, and Down
4861 | by default.
4862 | _______________________________________________________________________________
4863 |
4864 | EXAMPLES
4865 | --------
4866 |
4867 | 'plot' :
4868 | --
4869 | To generate the "beachball" representation of a pure normal faulting event with
4870 | a strike angle of 0 degrees and a dip of 45 degrees, use either of the
4871 | following commands:
4872 |
4873 | mopad plot 0,45,-90
4874 |
4875 | mopad plot 0,1,-1,0,0,0
4876 |
4877 |
4878 | 'describe':
4879 | --
4880 | To see the seismic moment tensor entries (in GlobalCMT's USE basis) and the
4881 | orientation of the auxilliary plane for a shear crack with the
4882 | (strike,dip,slip-rake) tuple (90,45,45) use:
4883 |
4884 | mopad describe 90,45,45 -o USE
4885 |
4886 |
4887 | 'decompose':
4888 | --
4889 | Get the deviatoric part of a seismic moment tensor M=(1,2,3,4,5,6) together
4890 | with the respective double-couple- and CLVD-components by using:
4891 |
4892 | mopad decompose 1,2,3,4,5,6 -p devi,dc,clvd
4893 |
4894 |
4895 | """ % (MOPAD_VERSION)
4896 |
4897 | print helpstring
4898 |
4899 | sys.exit()
4900 |
4901 | try:
4902 | M_raw = [float(xx) for xx in sys.argv[2].split(',')]
4903 | except:
4904 | dummy_list = []
4905 | dummy_list.append(sys.argv[0])
4906 | dummy_list.append(sys.argv[1])
4907 | dummy_list.append('0,0,0')
4908 | dummy_list.append('-h')
4909 |
4910 | sys.argv = dummy_list
4911 | M_raw = [float(xx) for xx in sys.argv[2].split(',')]
4912 |
4913 | if not len(M_raw) in [3, 4, 6, 7, 9]:
4914 | print '\nERROR!! Provide proper source mechanism\n\n'
4915 | sys.exit()
4916 | if len(M_raw) in [4, 6, 7, 9] and len(np.array(M_raw).nonzero()[0]) == 0:
4917 | print '\nERROR!! Provide proper source mechanism\n\n'
4918 | sys.exit()
4919 |
4920 | aa = _handle_input(call, M_raw, sys.argv[3:], _build_optparsers()[call])
4921 | if aa is not None:
4922 | print aa
4923 |
--------------------------------------------------------------------------------